mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
1019 lines
32 KiB
C++
1019 lines
32 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "gui/predictivedialog.h"
|
|
#include "gui/widget.h"
|
|
#include "gui/widgets/edittext.h"
|
|
#include "gui/gui-manager.h"
|
|
#include "gui/ThemeEval.h"
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/translation.h"
|
|
#include "common/events.h"
|
|
#include "common/debug.h"
|
|
#include "common/system.h"
|
|
#include "common/keyboard.h"
|
|
#include "common/file.h"
|
|
#include "common/savefile.h"
|
|
|
|
namespace GUI {
|
|
|
|
enum {
|
|
kCancelCmd = 'CNCL',
|
|
kOkCmd = '__OK',
|
|
kBut1Cmd = 'BUT1',
|
|
kBut2Cmd = 'BUT2',
|
|
kBut3Cmd = 'BUT3',
|
|
kBut4Cmd = 'BUT4',
|
|
kBut5Cmd = 'BUT5',
|
|
kBut6Cmd = 'BUT6',
|
|
kBut7Cmd = 'BUT7',
|
|
kBut8Cmd = 'BUT8',
|
|
kBut9Cmd = 'BUT9',
|
|
kBut0Cmd = 'BUT0',
|
|
kNextCmd = 'NEXT',
|
|
kAddCmd = '_ADD',
|
|
kModeCmd = 'MODE',
|
|
kDelCmd = '_DEL',
|
|
kTestCmd = 'TEST'
|
|
};
|
|
|
|
enum {
|
|
kModePre = 0,
|
|
kModeNum = 1,
|
|
kModeAbc = 2
|
|
};
|
|
|
|
PredictiveDialog::PredictiveDialog() : Dialog("Predictive") {
|
|
new StaticTextWidget(this, "Predictive.Headline", _("Enter Text"));
|
|
|
|
_button[kCancelAct] = new ButtonWidget(this, "Predictive.Cancel", _("Cancel") , Common::U32String(), kCancelCmd);
|
|
_button[kOkAct] = new ButtonWidget(this, "Predictive.OK", _("OK") , Common::U32String(), kOkCmd);
|
|
|
|
if (g_gui.useRTL()) {
|
|
/** If using RTL, swap the internal name of odd columns, to be flipped again when drawing.
|
|
We flip them back to orignal, because the keyboard layout stays the same in LTR & RTL.
|
|
The rest, like okButton, cancel, etc are all flipped.
|
|
*/
|
|
|
|
_button[kButton3Act] = new ButtonWidget(this, "Predictive.Button1", Common::U32String("3 def" ), Common::U32String(), kBut3Cmd);
|
|
_button[kButton2Act] = new ButtonWidget(this, "Predictive.Button2", Common::U32String("2 abc" ), Common::U32String(), kBut2Cmd);
|
|
_button[kButton1Act] = new ButtonWidget(this, "Predictive.Button3", Common::U32String("1 `-.&" ), Common::U32String(), kBut1Cmd);
|
|
_button[kButton6Act] = new ButtonWidget(this, "Predictive.Button4", Common::U32String("6 mno" ), Common::U32String(), kBut6Cmd);
|
|
_button[kButton5Act] = new ButtonWidget(this, "Predictive.Button5", Common::U32String("5 jkl" ), Common::U32String(), kBut5Cmd);
|
|
_button[kButton4Act] = new ButtonWidget(this, "Predictive.Button6", Common::U32String("4 ghi" ), Common::U32String(), kBut4Cmd);
|
|
_button[kButton9Act] = new ButtonWidget(this, "Predictive.Button7", Common::U32String("9 wxyz" ), Common::U32String(), kBut9Cmd);
|
|
_button[kButton8Act] = new ButtonWidget(this, "Predictive.Button8", Common::U32String("8 tuv" ), Common::U32String(), kBut8Cmd);
|
|
_button[kButton7Act] = new ButtonWidget(this, "Predictive.Button9", Common::U32String("7 pqrs" ), Common::U32String(), kBut7Cmd);
|
|
_button[kButton0Act] = new ButtonWidget(this, "Predictive.Button0", Common::U32String("0" ), Common::U32String(), kBut0Cmd);
|
|
} else {
|
|
_button[kButton1Act] = new ButtonWidget(this, "Predictive.Button1", Common::U32String("1 `-.&" ), Common::U32String(), kBut1Cmd);
|
|
_button[kButton2Act] = new ButtonWidget(this, "Predictive.Button2", Common::U32String("2 abc" ), Common::U32String(), kBut2Cmd);
|
|
_button[kButton3Act] = new ButtonWidget(this, "Predictive.Button3", Common::U32String("3 def" ), Common::U32String(), kBut3Cmd);
|
|
_button[kButton4Act] = new ButtonWidget(this, "Predictive.Button4", Common::U32String("4 ghi" ), Common::U32String(), kBut4Cmd);
|
|
_button[kButton5Act] = new ButtonWidget(this, "Predictive.Button5", Common::U32String("5 jkl" ), Common::U32String(), kBut5Cmd);
|
|
_button[kButton6Act] = new ButtonWidget(this, "Predictive.Button6", Common::U32String("6 mno" ), Common::U32String(), kBut6Cmd);
|
|
_button[kButton7Act] = new ButtonWidget(this, "Predictive.Button7", Common::U32String("7 pqrs" ), Common::U32String(), kBut7Cmd);
|
|
_button[kButton8Act] = new ButtonWidget(this, "Predictive.Button8", Common::U32String("8 tuv" ), Common::U32String(), kBut8Cmd);
|
|
_button[kButton9Act] = new ButtonWidget(this, "Predictive.Button9", Common::U32String("9 wxyz" ), Common::U32String(), kBut9Cmd);
|
|
_button[kButton0Act] = new ButtonWidget(this, "Predictive.Button0", Common::U32String("0" ), Common::U32String(), kBut0Cmd);
|
|
}
|
|
|
|
// I18N: You must leave "#" as is, only word 'next' is translatable
|
|
_button[kNextAct] = new ButtonWidget(this, "Predictive.Next", _("# next") , Common::U32String(), kNextCmd);
|
|
_button[kAddAct] = new ButtonWidget(this, "Predictive.Add", _("add") , Common::U32String(), kAddCmd);
|
|
_button[kAddAct]->setEnabled(false);
|
|
|
|
#ifndef DISABLE_FANCY_THEMES
|
|
if (g_gui.xmlEval()->getVar("Globals.Predictive.ShowDeletePic") == 1 && g_gui.theme()->supportsImages()) {
|
|
_button[kDelAct] = new PicButtonWidget(this, "Predictive.Delete", _("Delete char"), kDelCmd);
|
|
((PicButtonWidget *)_button[kDelAct])->useThemeTransparency(true);
|
|
((PicButtonWidget *)_button[kDelAct])->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDelButton));
|
|
} else
|
|
#endif
|
|
_button[kDelAct] = new ButtonWidget(this, "Predictive.Delete" , _("<") , Common::U32String(), kDelCmd);
|
|
// I18N: Pre means 'Predictive', leave '*' as is
|
|
_button[kModeAct] = new ButtonWidget(this, "Predictive.Pre", _("* Pre"), Common::U32String(), kModeCmd);
|
|
_editText = new EditTextWidget(this, "Predictive.Word", _search, Common::U32String(), 0, 0);
|
|
|
|
_userDictHasChanged = false;
|
|
|
|
_predictiveDict.nameDict = "predictive_dictionary";
|
|
_predictiveDict.defaultFilename = "pred.dic";
|
|
|
|
_userDict.nameDict = "user_dictionary";
|
|
_userDict.defaultFilename = "user.dic";
|
|
|
|
if (!_predictiveDict.dictText) {
|
|
loadAllDictionary(_predictiveDict);
|
|
if (!_predictiveDict.dictText)
|
|
debug(5, "Predictive Dialog: pred.dic not loaded");
|
|
}
|
|
|
|
if (!_userDict.dictText) {
|
|
loadAllDictionary(_userDict);
|
|
if (!_userDict.dictText)
|
|
debug(5, "Predictive Dialog: user.dic not loaded");
|
|
}
|
|
|
|
mergeDicts();
|
|
|
|
memset(_repeatcount, 0, sizeof(_repeatcount));
|
|
|
|
_prefix.clear();
|
|
_currentCode.clear();
|
|
_currentWord.clear();
|
|
_wordNumber = 0;
|
|
_numMatchingWords = 0;
|
|
memset(_predictiveResult, 0, sizeof(_predictiveResult));
|
|
|
|
_lastButton = kNoAct;
|
|
_mode = kModePre;
|
|
|
|
_lastTime = 0;
|
|
_curTime = 0;
|
|
_lastPressedButton = kNoAct;
|
|
|
|
_memoryList[0] = _predictiveDict.dictText;
|
|
_memoryList[1] = _userDict.dictText;
|
|
_numMemory = 0;
|
|
|
|
_navigationWithKeys = false;
|
|
|
|
_curPressedButton = kNoAct;
|
|
_needRefresh = true;
|
|
_isPressed = false;
|
|
|
|
}
|
|
|
|
PredictiveDialog::~PredictiveDialog() {
|
|
for (int i = 0; i < _numMemory; i++) {
|
|
free(_memoryList[i]);
|
|
}
|
|
free(_userDict.dictLine);
|
|
free(_predictiveDict.dictLine);
|
|
free(_unitedDict.dictLine);
|
|
}
|
|
|
|
void PredictiveDialog::reflowLayout() {
|
|
#ifndef DISABLE_FANCY_THEMES
|
|
removeWidget(_button[kDelAct]);
|
|
_button[kDelAct]->setNext(nullptr);
|
|
delete _button[kDelAct];
|
|
_button[kDelAct] = nullptr;
|
|
|
|
if (g_gui.xmlEval()->getVar("Globals.Predictive.ShowDeletePic") == 1 && g_gui.theme()->supportsImages()) {
|
|
_button[kDelAct] = new PicButtonWidget(this, "Predictive.Delete", _("Delete char"), kDelCmd);
|
|
((PicButtonWidget *)_button[kDelAct])->useThemeTransparency(true);
|
|
((PicButtonWidget *)_button[kDelAct])->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDelButton));
|
|
} else {
|
|
_button[kDelAct] = new ButtonWidget(this, "Predictive.Delete" , _("<") , Common::U32String(), kDelCmd);
|
|
}
|
|
#endif
|
|
|
|
Dialog::reflowLayout();
|
|
}
|
|
|
|
void PredictiveDialog::saveUserDictToFile() {
|
|
if (_userDictHasChanged) {
|
|
ConfMan.registerDefault("user_dictionary", "user.dic");
|
|
|
|
Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(ConfMan.get("user_dictionary"));
|
|
|
|
for (int i = 0; i < _userDict.dictLineCount; i++) {
|
|
file->writeString(_userDict.dictLine[i]);
|
|
file->writeString("\n");
|
|
}
|
|
|
|
file->finalize();
|
|
delete file;
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::handleKeyUp(Common::KeyState state) {
|
|
if (_curPressedButton != kNoAct && !_needRefresh) {
|
|
_button[_curPressedButton]->setUnpressedState();
|
|
processButton(_curPressedButton);
|
|
}
|
|
|
|
_isPressed = false;
|
|
}
|
|
|
|
void PredictiveDialog::handleKeyDown(Common::KeyState state) {
|
|
if (_isPressed) {
|
|
return;
|
|
}
|
|
|
|
_isPressed = true;
|
|
_curPressedButton = kNoAct;
|
|
_needRefresh = false;
|
|
|
|
if (getFocusWidget() == _editText) {
|
|
setFocusWidget(_button[kAddAct]);
|
|
}
|
|
|
|
if (_lastButton == kNoAct) {
|
|
_lastButton = kButton5Act;
|
|
}
|
|
|
|
switch (state.keycode) {
|
|
case Common::KEYCODE_ESCAPE:
|
|
saveUserDictToFile();
|
|
close();
|
|
return;
|
|
case Common::KEYCODE_LEFT:
|
|
_navigationWithKeys = true;
|
|
if (_lastButton == kButton1Act || _lastButton == kButton4Act || _lastButton == kButton7Act)
|
|
_curPressedButton = ButtonId(_lastButton + 2);
|
|
else if (_lastButton == kDelAct)
|
|
_curPressedButton = kButton1Act;
|
|
else if (_lastButton == kModeAct)
|
|
_curPressedButton = kNextAct;
|
|
else if (_lastButton == kNextAct)
|
|
_curPressedButton = kButton0Act;
|
|
else if (_lastButton == kAddAct)
|
|
_curPressedButton = kOkAct;
|
|
else if (_lastButton == kCancelAct)
|
|
_curPressedButton = kAddAct;
|
|
else
|
|
_curPressedButton = ButtonId(_lastButton - 1);
|
|
|
|
|
|
if (_mode != kModeAbc && _lastButton == kCancelAct)
|
|
_curPressedButton = kOkAct;
|
|
|
|
_needRefresh = true;
|
|
break;
|
|
case Common::KEYCODE_RIGHT:
|
|
_navigationWithKeys = true;
|
|
if (_lastButton == kButton3Act || _lastButton == kButton6Act || _lastButton == kButton9Act || _lastButton == kOkAct)
|
|
_curPressedButton = ButtonId(_lastButton - 2);
|
|
else if (_lastButton == kDelAct)
|
|
_curPressedButton = kButton3Act;
|
|
else if (_lastButton == kButton0Act)
|
|
_curPressedButton = kNextAct;
|
|
else if (_lastButton == kNextAct)
|
|
_curPressedButton = kModeAct;
|
|
else if (_lastButton == kAddAct)
|
|
_curPressedButton = kCancelAct;
|
|
else if (_lastButton == kOkAct)
|
|
_curPressedButton = kAddAct;
|
|
else
|
|
_curPressedButton = ButtonId(_lastButton + 1);
|
|
|
|
if (_mode != kModeAbc && _lastButton == kOkAct)
|
|
_curPressedButton = kCancelAct;
|
|
_needRefresh = true;
|
|
break;
|
|
case Common::KEYCODE_UP:
|
|
_navigationWithKeys = true;
|
|
if (_lastButton <= kButton3Act)
|
|
_curPressedButton = kDelAct;
|
|
else if (_lastButton == kDelAct)
|
|
_curPressedButton = kOkAct;
|
|
else if (_lastButton == kModeAct)
|
|
_curPressedButton = kButton7Act;
|
|
else if (_lastButton == kButton0Act)
|
|
_curPressedButton = kButton8Act;
|
|
else if (_lastButton == kNextAct)
|
|
_curPressedButton = kButton9Act;
|
|
else if (_lastButton == kAddAct)
|
|
_curPressedButton = kModeAct;
|
|
else if (_lastButton == kCancelAct)
|
|
_curPressedButton = kButton0Act;
|
|
else if (_lastButton == kOkAct)
|
|
_curPressedButton = kNextAct;
|
|
else
|
|
_curPressedButton = ButtonId(_lastButton - 3);
|
|
_needRefresh = true;
|
|
break;
|
|
case Common::KEYCODE_DOWN:
|
|
_navigationWithKeys = true;
|
|
if (_lastButton == kDelAct)
|
|
_curPressedButton = kButton3Act;
|
|
else if (_lastButton == kButton7Act)
|
|
_curPressedButton = kModeAct;
|
|
else if (_lastButton == kButton8Act)
|
|
_curPressedButton = kButton0Act;
|
|
else if (_lastButton == kButton9Act)
|
|
_curPressedButton = kNextAct;
|
|
else if (_lastButton == kModeAct)
|
|
_curPressedButton = kAddAct;
|
|
else if (_lastButton == kButton0Act)
|
|
_curPressedButton = kCancelAct;
|
|
else if (_lastButton == kNextAct)
|
|
_curPressedButton = kOkAct;
|
|
else if (_lastButton == kAddAct || _lastButton == kCancelAct || _lastButton == kOkAct)
|
|
_curPressedButton = kDelAct;
|
|
else
|
|
_curPressedButton = ButtonId(_lastButton + 3);
|
|
|
|
if (_mode != kModeAbc && _lastButton == kModeAct)
|
|
_curPressedButton = kCancelAct;
|
|
|
|
_needRefresh = true;
|
|
break;
|
|
case Common::KEYCODE_KP_ENTER:
|
|
case Common::KEYCODE_RETURN:
|
|
if (state.flags & Common::KBD_CTRL) {
|
|
_curPressedButton = kOkAct;
|
|
break;
|
|
}
|
|
if (_navigationWithKeys) {
|
|
// when the user has utilized arrow key navigation,
|
|
// interpret enter as 'click' on the _curPressedButton button
|
|
_curPressedButton = _lastButton;
|
|
_needRefresh = false;
|
|
} else {
|
|
// else it is a shortcut for 'Ok'
|
|
_curPressedButton = kOkAct;
|
|
}
|
|
break;
|
|
case Common::KEYCODE_KP_PLUS:
|
|
_curPressedButton = kAddAct;
|
|
break;
|
|
case Common::KEYCODE_BACKSPACE:
|
|
case Common::KEYCODE_KP_MINUS:
|
|
_curPressedButton = kDelAct;
|
|
break;
|
|
case Common::KEYCODE_KP_DIVIDE:
|
|
_curPressedButton = kNextAct;
|
|
break;
|
|
case Common::KEYCODE_KP_MULTIPLY:
|
|
_curPressedButton = kModeAct;
|
|
break;
|
|
case Common::KEYCODE_KP0:
|
|
_curPressedButton = kButton0Act;
|
|
break;
|
|
case Common::KEYCODE_KP1:
|
|
case Common::KEYCODE_KP2:
|
|
case Common::KEYCODE_KP3:
|
|
case Common::KEYCODE_KP4:
|
|
case Common::KEYCODE_KP5:
|
|
case Common::KEYCODE_KP6:
|
|
case Common::KEYCODE_KP7:
|
|
case Common::KEYCODE_KP8:
|
|
case Common::KEYCODE_KP9:
|
|
_curPressedButton = ButtonId(state.keycode - Common::KEYCODE_KP1);
|
|
break;
|
|
default:
|
|
Dialog::handleKeyDown(state);
|
|
}
|
|
|
|
if (_lastButton != _curPressedButton)
|
|
_button[_lastButton]->setUnpressedState();
|
|
|
|
if (_curPressedButton != kNoAct && !_needRefresh)
|
|
_button[_curPressedButton]->setPressedState();
|
|
else
|
|
updateHighLightedButton(_curPressedButton);
|
|
}
|
|
|
|
void PredictiveDialog::updateHighLightedButton(ButtonId act) {
|
|
if (_curPressedButton != kNoAct) {
|
|
_button[_lastButton]->setHighLighted(false);
|
|
_lastButton = act;
|
|
_button[_lastButton]->setHighLighted(true);
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
|
_curPressedButton = kNoAct;
|
|
|
|
_navigationWithKeys = false;
|
|
|
|
if (_lastButton != kNoAct)
|
|
_button[_lastButton]->setHighLighted(false);
|
|
|
|
switch (cmd) {
|
|
case kDelCmd:
|
|
_curPressedButton = kDelAct;
|
|
break;
|
|
case kNextCmd:
|
|
_curPressedButton = kNextAct;
|
|
break;
|
|
case kAddCmd:
|
|
_curPressedButton = kAddAct;
|
|
break;
|
|
case kModeCmd:
|
|
_curPressedButton = kModeAct;
|
|
break;
|
|
case kBut1Cmd:
|
|
_curPressedButton = kButton1Act;
|
|
break;
|
|
case kBut2Cmd:
|
|
_curPressedButton = kButton2Act;
|
|
break;
|
|
case kBut3Cmd:
|
|
_curPressedButton = kButton3Act;
|
|
break;
|
|
case kBut4Cmd:
|
|
_curPressedButton = kButton4Act;
|
|
break;
|
|
case kBut5Cmd:
|
|
_curPressedButton = kButton5Act;
|
|
break;
|
|
case kBut6Cmd:
|
|
_curPressedButton = kButton6Act;
|
|
break;
|
|
case kBut7Cmd:
|
|
_curPressedButton = kButton7Act;
|
|
break;
|
|
case kBut8Cmd:
|
|
_curPressedButton = kButton8Act;
|
|
break;
|
|
case kBut9Cmd:
|
|
_curPressedButton = kButton9Act;
|
|
break;
|
|
case kBut0Cmd:
|
|
_curPressedButton = kButton0Act;
|
|
break;
|
|
case kCancelCmd:
|
|
saveUserDictToFile();
|
|
close();
|
|
// When we cancel the dialog no result should be returned. Thus, we
|
|
// will invalidate any result here.
|
|
_predictiveResult[0] = 0;
|
|
return;
|
|
case kOkCmd:
|
|
_curPressedButton = kOkAct;
|
|
break;
|
|
default:
|
|
Dialog::handleCommand(sender, cmd, data);
|
|
}
|
|
|
|
if (_curPressedButton != kNoAct) {
|
|
processButton(_curPressedButton);
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::processButton(ButtonId button) {
|
|
static const char *const buttonStr[] = {
|
|
"1", "2", "3",
|
|
"4", "5", "6",
|
|
"7", "8", "9",
|
|
"0"
|
|
};
|
|
|
|
static const char *const buttons[] = {
|
|
"'-.&", "abc", "def",
|
|
"ghi", "jkl", "mno",
|
|
"pqrs", "tuv", "wxyz",
|
|
"next", "add",
|
|
"<",
|
|
"Cancel", "OK",
|
|
"Pre", "(0) ", nullptr
|
|
};
|
|
|
|
if (_mode == kModeAbc) {
|
|
if (button >= kButton1Act && button <= kButton9Act) {
|
|
if (!_lastTime)
|
|
_lastTime = g_system->getMillis();
|
|
if (_lastPressedButton == button) {
|
|
_curTime = g_system->getMillis();
|
|
if ((_curTime - _lastTime) < kRepeatDelay) {
|
|
button = kNextAct;
|
|
_lastTime = _curTime;
|
|
} else {
|
|
_lastTime = 0;
|
|
}
|
|
} else {
|
|
_lastPressedButton = button;
|
|
_lastTime = g_system->getMillis();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (button >= kButton1Act) {
|
|
_lastButton = button;
|
|
if (button == kButton0Act && _mode != kModeNum) { // Space
|
|
// bring MRU word at the top of the list when changing words
|
|
if (_mode == kModePre && _unitedDict.dictActLine && _numMatchingWords > 1 && _wordNumber != 0)
|
|
bringWordtoTop(_unitedDict.dictActLine, _wordNumber);
|
|
|
|
strncpy(_temp, _currentWord.c_str(), _currentCode.size());
|
|
_temp[_currentCode.size()] = 0;
|
|
_prefix += _temp;
|
|
_prefix += " ";
|
|
_currentCode.clear();
|
|
_currentWord.clear();
|
|
_numMatchingWords = 0;
|
|
memset(_repeatcount, 0, sizeof(_repeatcount));
|
|
_lastTime = 0;
|
|
_lastPressedButton = kNoAct;
|
|
_curTime = 0;
|
|
} else if (button < kNextAct || button == kDelAct || button == kButton0Act) { // number or backspace
|
|
if (button == kDelAct) { // backspace
|
|
if (_currentCode.size()) {
|
|
_repeatcount[_currentCode.size() - 1] = 0;
|
|
_currentCode.deleteLastChar();
|
|
if (_currentCode.empty())
|
|
_currentWord.clear();
|
|
} else {
|
|
if (_prefix.size())
|
|
_prefix.deleteLastChar();
|
|
}
|
|
} else if (_prefix.size() + _currentCode.size() < kMaxWordLen - 1) { // don't overflow the dialog line
|
|
if (button == kButton0Act) { // zero
|
|
_currentCode += buttonStr[9];
|
|
} else {
|
|
_currentCode += buttonStr[button];
|
|
}
|
|
}
|
|
|
|
switch (_mode) {
|
|
case kModeNum:
|
|
_currentWord = _currentCode;
|
|
break;
|
|
case kModePre:
|
|
if (!matchWord() && _currentCode.size()) {
|
|
_currentCode.deleteLastChar();
|
|
matchWord();
|
|
}
|
|
_numMatchingWords = countWordsInString(_unitedDict.dictActLine);
|
|
break;
|
|
case kModeAbc:
|
|
for (uint x = 0; x < _currentCode.size(); x++)
|
|
if (_currentCode[x] >= '1')
|
|
_temp[x] = buttons[_currentCode[x] - '1'][_repeatcount[x]];
|
|
_temp[_currentCode.size()] = 0;
|
|
_currentWord = _temp;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (button == kNextAct) { // next
|
|
if (_mode == kModePre) {
|
|
if (_unitedDict.dictActLine && _numMatchingWords > 1) {
|
|
_wordNumber = (_wordNumber + 1) % _numMatchingWords;
|
|
char tmp[kMaxLineLen];
|
|
strncpy(tmp, _unitedDict.dictActLine, kMaxLineLen);
|
|
tmp[kMaxLineLen - 1] = 0;
|
|
char *tok = strtok(tmp, " ");
|
|
for (uint8 i = 0; i <= _wordNumber; i++)
|
|
tok = strtok(nullptr, " ");
|
|
_currentWord = Common::String(tok, _currentCode.size());
|
|
}
|
|
} else if (_mode == kModeAbc) {
|
|
uint x = _currentCode.size();
|
|
if (x) {
|
|
if (_currentCode.lastChar() == '1' || _currentCode.lastChar() == '7' || _currentCode.lastChar() == '9')
|
|
_repeatcount[x - 1] = (_repeatcount[x - 1] + 1) % 4;
|
|
else
|
|
_repeatcount[x - 1] = (_repeatcount[x - 1] + 1) % 3;
|
|
|
|
if (_currentCode.lastChar() >= '1')
|
|
_currentWord.setChar(buttons[_currentCode[x - 1] - '1'][_repeatcount[x - 1]], x - 1);
|
|
}
|
|
}
|
|
} else if (button == kAddAct) { // add
|
|
if (_mode == kModeAbc)
|
|
addWordToDict();
|
|
else
|
|
debug(5, "Predictive Dialog: button Add doesn't work in this mode");
|
|
} else if (button == kOkAct) { // Ok
|
|
// bring MRU word at the top of the list when ok'ed out of the dialog
|
|
if (_mode == kModePre && _unitedDict.dictActLine && _numMatchingWords > 1 && _wordNumber != 0)
|
|
bringWordtoTop(_unitedDict.dictActLine, _wordNumber);
|
|
} else if (button == kModeAct) { // Mode
|
|
_mode++;
|
|
_button[kAddAct]->setEnabled(false);
|
|
if (_mode > kModeAbc) {
|
|
_mode = kModePre;
|
|
// I18N: Pre means 'Predictive', leave '*' as is
|
|
_button[kModeAct]->setLabel(_("* Pre"));
|
|
} else if (_mode == kModeNum) {
|
|
// I18N: 'Num' means Numbers
|
|
_button[kModeAct]->setLabel(_("* Num"));
|
|
} else {
|
|
// I18N: 'Abc' means Latin alphabet input
|
|
_button[kModeAct]->setLabel(_("* Abc"));
|
|
_button[kAddAct]->setEnabled(true);
|
|
}
|
|
|
|
// truncate current input at mode change
|
|
strncpy(_temp, _currentWord.c_str(), _currentCode.size());
|
|
_temp[_currentCode.size()] = 0;
|
|
_prefix += _temp;
|
|
_currentCode.clear();
|
|
_currentWord.clear();
|
|
memset(_repeatcount, 0, sizeof(_repeatcount));
|
|
|
|
_lastTime = 0;
|
|
_lastPressedButton = kNoAct;
|
|
_curTime = 0;
|
|
}
|
|
}
|
|
|
|
pressEditText();
|
|
|
|
if (button == kOkAct)
|
|
close();
|
|
|
|
if (button == kCancelAct) {
|
|
saveUserDictToFile();
|
|
close();
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::mergeDicts() {
|
|
_unitedDict.dictLineCount = _predictiveDict.dictLineCount + _userDict.dictLineCount;
|
|
_unitedDict.dictLine = (char **)calloc(_unitedDict.dictLineCount, sizeof(char *));
|
|
|
|
if (!_unitedDict.dictLine) {
|
|
debug(5, "Predictive Dialog: cannot allocate memory for united dic");
|
|
return;
|
|
}
|
|
|
|
int lenUserDictCode, lenPredictiveDictCode, lenCode;
|
|
int i, j, k;
|
|
i = j = k = 0;
|
|
|
|
while ((i < _userDict.dictLineCount) && (j < _predictiveDict.dictLineCount)) {
|
|
lenUserDictCode = strchr(_userDict.dictLine[i], ' ') - _userDict.dictLine[i];
|
|
lenPredictiveDictCode = strchr(_predictiveDict.dictLine[j], ' ') - _predictiveDict.dictLine[j];
|
|
lenCode = (lenUserDictCode >= lenPredictiveDictCode) ? lenUserDictCode : lenPredictiveDictCode;
|
|
if (strncmp(_userDict.dictLine[i], _predictiveDict.dictLine[j], lenCode) >= 0) {
|
|
_unitedDict.dictLine[k++] = _predictiveDict.dictLine[j++];
|
|
} else {
|
|
_unitedDict.dictLine[k++] = _userDict.dictLine[i++];
|
|
}
|
|
}
|
|
|
|
while (i < _userDict.dictLineCount) {
|
|
_unitedDict.dictLine[k++] = _userDict.dictLine[i++];
|
|
}
|
|
|
|
while (j < _predictiveDict.dictLineCount) {
|
|
_unitedDict.dictLine[k++] = _predictiveDict.dictLine[j++];
|
|
}
|
|
}
|
|
|
|
uint8 PredictiveDialog::countWordsInString(const char *const str) {
|
|
// Count the number of (space separated) words in the given string.
|
|
const char *ptr;
|
|
|
|
if (!str)
|
|
return 0;
|
|
|
|
ptr = strchr(str, ' ');
|
|
if (!ptr) {
|
|
debug(5, "Predictive Dialog: Invalid dictionary line");
|
|
return 0;
|
|
}
|
|
|
|
uint8 num = 1;
|
|
ptr++;
|
|
while ((ptr = strchr(ptr, ' '))) {
|
|
ptr++;
|
|
num++;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
void PredictiveDialog::bringWordtoTop(char *str, int wordnum) {
|
|
// This function reorders the words on the given pred.dic line
|
|
// by moving the word at position 'wordnum' to the front (that is, right behind
|
|
// right behind the numerical code word at the start of the line).
|
|
Common::Array<Common::String> words;
|
|
char buf[kMaxLineLen];
|
|
|
|
if (!str)
|
|
return;
|
|
strncpy(buf, str, kMaxLineLen);
|
|
buf[kMaxLineLen - 1] = 0;
|
|
char *word = strtok(buf, " ");
|
|
if (!word) {
|
|
debug(5, "Predictive Dialog: Invalid dictionary line");
|
|
return;
|
|
}
|
|
|
|
words.push_back(word);
|
|
while ((word = strtok(nullptr, " ")) != nullptr)
|
|
words.push_back(word);
|
|
words.insert_at(1, words.remove_at(wordnum + 1));
|
|
|
|
Common::String tmp;
|
|
for (uint8 i = 0; i < words.size(); i++)
|
|
tmp += words[i] + " ";
|
|
tmp.deleteLastChar();
|
|
memcpy(str, tmp.c_str(), strlen(str));
|
|
}
|
|
|
|
int PredictiveDialog::binarySearch(const char *const *const dictLine, const Common::String &code, const int dictLineCount) {
|
|
int hi = dictLineCount - 1;
|
|
int lo = 0;
|
|
int line = 0;
|
|
while (lo <= hi) {
|
|
line = (lo + hi) / 2;
|
|
int cmpVal = strncmp(dictLine[line], code.c_str(), code.size());
|
|
if (cmpVal > 0)
|
|
hi = line - 1;
|
|
else if (cmpVal < 0)
|
|
lo = line + 1;
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hi < lo) {
|
|
return -(lo + 1);
|
|
} else {
|
|
return line;
|
|
}
|
|
}
|
|
|
|
bool PredictiveDialog::matchWord() {
|
|
// If there is no dictionary, then there is no match.
|
|
if (_unitedDict.dictLineCount <= 0)
|
|
return false;
|
|
|
|
// If no text has been entered, then there is no match.
|
|
if (_currentCode.empty())
|
|
return false;
|
|
|
|
// If the currently entered text is too long, it cannot match anything.
|
|
if (_currentCode.size() > kMaxWordLen)
|
|
return false;
|
|
|
|
// The entries in the dictionary consist of a code, a space, and then
|
|
// a space-separated list of words matching this code.
|
|
// To exactly match a code, we therefore match the code plus the trailing
|
|
// space in the dictionary.
|
|
Common::String code = _currentCode + " ";
|
|
|
|
int line = binarySearch(_unitedDict.dictLine, code, _unitedDict.dictLineCount);
|
|
if (line < 0) {
|
|
line = -(line + 1);
|
|
_unitedDict.dictActLine = nullptr;
|
|
} else {
|
|
_unitedDict.dictActLine = _unitedDict.dictLine[line];
|
|
}
|
|
|
|
_currentWord.clear();
|
|
_wordNumber = 0;
|
|
if (0 == strncmp(_unitedDict.dictLine[line], _currentCode.c_str(), _currentCode.size())) {
|
|
char tmp[kMaxLineLen];
|
|
strncpy(tmp, _unitedDict.dictLine[line], kMaxLineLen);
|
|
tmp[kMaxLineLen - 1] = 0;
|
|
char *tok;
|
|
strtok(tmp, " ");
|
|
tok = strtok(nullptr, " ");
|
|
_currentWord = Common::String(tok, _currentCode.size());
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool PredictiveDialog::searchWord(const char *const where, const Common::String &whatCode) {
|
|
const char *ptr = where;
|
|
ptr += whatCode.size();
|
|
|
|
const char *newPtr;
|
|
bool is = false;
|
|
while ((newPtr = strchr(ptr, ' '))) {
|
|
if (0 == strncmp(ptr, _currentWord.c_str(), newPtr - ptr)) {
|
|
is = true;
|
|
break;
|
|
}
|
|
ptr = newPtr + 1;
|
|
}
|
|
if (!is) {
|
|
if (0 == strcmp(ptr, _currentWord.c_str())) {
|
|
is = true;
|
|
}
|
|
}
|
|
return is;
|
|
}
|
|
|
|
void PredictiveDialog::addWord(Dict &dict, const Common::String &word, const Common::String &code) {
|
|
char *newLine = nullptr;
|
|
Common::String tmpCode = code + ' ';
|
|
int line = binarySearch(dict.dictLine, tmpCode, dict.dictLineCount);
|
|
if (line >= 0) {
|
|
if (searchWord(dict.dictLine[line], tmpCode)) {
|
|
// if we found code and word, we should not insert/expands any word
|
|
return;
|
|
} else {
|
|
// if we found the code, but did not find a word, we must
|
|
// EXPANDS the currnent line with new word
|
|
int oldLineSize = strlen(dict.dictLine[line]);
|
|
int newLineSize = oldLineSize + word.size() + 1;
|
|
|
|
newLine = (char *)malloc(newLineSize + 1);
|
|
|
|
char *ptr = newLine;
|
|
strncpy(ptr, dict.dictLine[line], oldLineSize);
|
|
ptr += oldLineSize;
|
|
Common::String tmp = ' ' + word + '\0';
|
|
strncpy(ptr, tmp.c_str(), tmp.size());
|
|
|
|
dict.dictLine[line] = newLine;
|
|
_memoryList[_numMemory++] = newLine;
|
|
|
|
if (dict.nameDict == "user_dictionary")
|
|
_userDictHasChanged = true;
|
|
|
|
return;
|
|
}
|
|
} else { // if we didn't find the code, we need to INSERT new line with code and word
|
|
if (dict.nameDict == "user_dictionary") {
|
|
// if we must INSERT new line(code and word) to user_dictionary, we need to
|
|
// check if there is a line that we want to INSERT in predictive dictionay
|
|
int predictLine = binarySearch(_predictiveDict.dictLine, tmpCode, _predictiveDict.dictLineCount);
|
|
if (predictLine >= 0) {
|
|
if (searchWord(_predictiveDict.dictLine[predictLine], tmpCode)) {
|
|
// if code and word is in predictive dictionary, we need to copy
|
|
// this line to user dictionary
|
|
int len = (predictLine == _predictiveDict.dictLineCount - 1) ? &_predictiveDict.dictText[_predictiveDict.dictTextSize] - _predictiveDict.dictLine[predictLine] :
|
|
_predictiveDict.dictLine[predictLine + 1] - _predictiveDict.dictLine[predictLine];
|
|
newLine = (char *)malloc(len);
|
|
strncpy(newLine, _predictiveDict.dictLine[predictLine], len);
|
|
} else {
|
|
// if there is no word in predictive dictionary, we need to copy to
|
|
// user dictionary mathed line + new word.
|
|
int len = (predictLine == _predictiveDict.dictLineCount - 1) ? &_predictiveDict.dictText[_predictiveDict.dictTextSize] - _predictiveDict.dictLine[predictLine] :
|
|
_predictiveDict.dictLine[predictLine + 1] - _predictiveDict.dictLine[predictLine];
|
|
newLine = (char *)malloc(len + word.size() + 1);
|
|
char *ptr = newLine;
|
|
strncpy(ptr, _predictiveDict.dictLine[predictLine], len);
|
|
ptr[len - 1] = ' ';
|
|
ptr += len;
|
|
strncpy(ptr, word.c_str(), word.size());
|
|
ptr[len + word.size()] = '\0';
|
|
}
|
|
} else {
|
|
// if we didnt find line in predictive dialog, we should copy to user dictionary
|
|
// code + word
|
|
Common::String tmp;
|
|
tmp = tmpCode + word + '\0';
|
|
newLine = (char *)malloc(tmp.size());
|
|
strncpy(newLine, tmp.c_str(), tmp.size());
|
|
}
|
|
} else {
|
|
// if want to insert line to different from user dictionary, we should copy to this
|
|
// dictionary code + word
|
|
Common::String tmp;
|
|
tmp = tmpCode + word + '\0';
|
|
newLine = (char *)malloc(tmp.size());
|
|
strncpy(newLine, tmp.c_str(), tmp.size());
|
|
}
|
|
}
|
|
|
|
// start from here are INSERTING new line to dictionaty ( dict )
|
|
char **newDictLine = (char **)calloc(dict.dictLineCount + 1, sizeof(char *));
|
|
if (!newDictLine) {
|
|
warning("Predictive Dialog: cannot allocate memory for index buffer");
|
|
|
|
free(newLine);
|
|
|
|
return;
|
|
}
|
|
|
|
int k = 0;
|
|
bool inserted = false;
|
|
for (int i = 0; i < dict.dictLineCount; i++) {
|
|
uint lenPredictiveDictCode = strchr(dict.dictLine[i], ' ') - dict.dictLine[i];
|
|
uint lenCode = (lenPredictiveDictCode >= (code.size() - 1)) ? lenPredictiveDictCode : code.size() - 1;
|
|
if ((strncmp(dict.dictLine[i], code.c_str(), lenCode) > 0) && !inserted) {
|
|
newDictLine[k++] = newLine;
|
|
inserted = true;
|
|
}
|
|
if (k != (dict.dictLineCount + 1)) {
|
|
newDictLine[k++] = dict.dictLine[i];
|
|
}
|
|
}
|
|
if (!inserted)
|
|
newDictLine[k] = newLine;
|
|
|
|
_memoryList[_numMemory++] = newLine;
|
|
|
|
free(dict.dictLine);
|
|
dict.dictLineCount += 1;
|
|
dict.dictLine = (char **)calloc(dict.dictLineCount, sizeof(char *));
|
|
if (!dict.dictLine) {
|
|
warning("Predictive Dialog: cannot allocate memory for index buffer");
|
|
free(newDictLine);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < dict.dictLineCount; i++) {
|
|
dict.dictLine[i] = newDictLine[i];
|
|
}
|
|
|
|
if (dict.nameDict == "user_dictionary")
|
|
_userDictHasChanged = true;
|
|
|
|
free(newDictLine);
|
|
}
|
|
|
|
void PredictiveDialog::addWordToDict() {
|
|
if (_numMemory < kMaxWord) {
|
|
addWord(_unitedDict, _currentWord, _currentCode);
|
|
addWord(_userDict, _currentWord, _currentCode);
|
|
} else {
|
|
warning("Predictive Dialog: You cannot add word to user dictionary...");
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::loadDictionary(Common::SeekableReadStream *in, Dict &dict) {
|
|
int lines = 0;
|
|
|
|
uint32 time1 = g_system->getMillis();
|
|
|
|
dict.dictTextSize = in->size();
|
|
dict.dictText = (char *)malloc(dict.dictTextSize + 1);
|
|
|
|
if (!dict.dictText) {
|
|
warning("Predictive Dialog: Not enough memory to load the file user.dic");
|
|
return;
|
|
}
|
|
|
|
in->read(dict.dictText, dict.dictTextSize);
|
|
dict.dictText[dict.dictTextSize] = 0;
|
|
uint32 time2 = g_system->getMillis();
|
|
debug(5, "Predictive Dialog: Time to read %s: %d bytes, %d ms", ConfMan.get(dict.nameDict).c_str(), dict.dictTextSize, time2 - time1);
|
|
delete in;
|
|
|
|
char *ptr = dict.dictText;
|
|
lines = 1;
|
|
while ((ptr = strchr(ptr, '\n'))) {
|
|
lines++;
|
|
ptr++;
|
|
}
|
|
|
|
dict.dictLine = (char **)calloc(lines, sizeof(char *));
|
|
if (dict.dictLine == nullptr) {
|
|
warning("Predictive Dialog: Cannot allocate memory for line index buffer");
|
|
return;
|
|
}
|
|
dict.dictLine[0] = dict.dictText;
|
|
ptr = dict.dictText;
|
|
int i = 1;
|
|
while ((ptr = strchr(ptr, '\n'))) {
|
|
*ptr = 0;
|
|
ptr++;
|
|
dict.dictLine[i++] = ptr;
|
|
}
|
|
if (dict.dictLine[lines - 1][0] == 0)
|
|
lines--;
|
|
|
|
dict.dictLineCount = lines;
|
|
debug(5, "Predictive Dialog: Loaded %d lines", dict.dictLineCount);
|
|
|
|
// FIXME: We use binary search on _predictiveDict.dictLine, yet we make no at_tempt
|
|
// to ever sort this array. That seems risky, doesn't it?
|
|
|
|
uint32 time3 = g_system->getMillis();
|
|
debug(5, "Predictive Dialog: Time to parse %s: %d, total: %d", ConfMan.get(dict.nameDict).c_str(), time3 - time2, time3 - time1);
|
|
}
|
|
|
|
void PredictiveDialog::loadAllDictionary(Dict &dict) {
|
|
ConfMan.registerDefault(dict.nameDict, dict.defaultFilename);
|
|
|
|
if (dict.nameDict == "predictive_dictionary") {
|
|
Common::File *inFile = new Common::File();
|
|
if (!inFile->open(ConfMan.get(dict.nameDict))) {
|
|
warning("Predictive Dialog: cannot read file: %s", dict.defaultFilename.c_str());
|
|
delete inFile;
|
|
return;
|
|
}
|
|
loadDictionary(inFile, dict);
|
|
} else {
|
|
Common::InSaveFile *inFile = g_system->getSavefileManager()->openForLoading(ConfMan.get(dict.nameDict));
|
|
if (!inFile) {
|
|
warning("Predictive Dialog: cannot read file: %s", dict.defaultFilename.c_str());
|
|
return;
|
|
}
|
|
loadDictionary(inFile, dict);
|
|
}
|
|
}
|
|
|
|
void PredictiveDialog::pressEditText() {
|
|
Common::strlcpy(_predictiveResult, _prefix.c_str(), sizeof(_predictiveResult));
|
|
Common::strlcat(_predictiveResult, _currentWord.c_str(), sizeof(_predictiveResult));
|
|
_editText->setEditString(Common::convertToU32String(_predictiveResult));
|
|
//_editText->setCaretPos(_prefix.size() + _currentWord.size());
|
|
_editText->markAsDirty();
|
|
}
|
|
|
|
} // namespace GUI
|