scummvm/gui/predictivedialog.cpp

1039 lines
32 KiB
C++
Raw Normal View History

/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#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"
#if defined(__DS__) && defined(ENABLE_AGI)
#include "backends/platform/ds/arm9/source/wordcompletion.h"
#endif
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") , nullptr, kCancelCmd);
_button[kOkAct] = new ButtonWidget(this, "Predictive.OK", _("Ok") , nullptr, kOkCmd);
if (g_gui.useRTL()) {
/** If using RTL, swap out the odd rows, as they will be flipped when drawing. (Swapping the commands and display data)
We flip them back to orignal, because the keyboard layout stays the same in LTR & RTL,
but the rest, like okButton, cancel, etc are all flipped.
*/
// Row 1
_button[kButton1Act] = new ButtonWidget(this, "Predictive.Button1", "3 def", nullptr, kBut3Cmd);
_button[kButton4Act] = new ButtonWidget(this, "Predictive.Button4", "6 mno", nullptr, kBut6Cmd);
_button[kButton7Act] = new ButtonWidget(this, "Predictive.Button7", "9 wxyz", nullptr, kBut9Cmd);
// Row 3
_button[kButton3Act] = new ButtonWidget(this, "Predictive.Button3", "1 `-.&", nullptr, kBut1Cmd);
_button[kButton6Act] = new ButtonWidget(this, "Predictive.Button6", "4 ghi", nullptr, kBut4Cmd);
_button[kButton9Act] = new ButtonWidget(this, "Predictive.Button9", "7 pqrs", nullptr, kBut7Cmd);
// Middle Row - Stays the same
_button[kButton2Act] = new ButtonWidget(this, "Predictive.Button2", "2 abc", nullptr, kBut2Cmd);
_button[kButton5Act] = new ButtonWidget(this, "Predictive.Button5", "5 jkl", nullptr, kBut5Cmd);
_button[kButton8Act] = new ButtonWidget(this, "Predictive.Button8", "8 tuv", nullptr, kBut8Cmd);
_button[kButton0Act] = new ButtonWidget(this, "Predictive.Button0", "0", nullptr, kBut0Cmd);
} else {
_button[kButton1Act] = new ButtonWidget(this, "Predictive.Button1", "1 `-.&" , nullptr, kBut1Cmd);
_button[kButton2Act] = new ButtonWidget(this, "Predictive.Button2", "2 abc" , nullptr, kBut2Cmd);
_button[kButton3Act] = new ButtonWidget(this, "Predictive.Button3", "3 def" , nullptr, kBut3Cmd);
_button[kButton4Act] = new ButtonWidget(this, "Predictive.Button4", "4 ghi" , nullptr, kBut4Cmd);
_button[kButton5Act] = new ButtonWidget(this, "Predictive.Button5", "5 jkl" , nullptr, kBut5Cmd);
_button[kButton6Act] = new ButtonWidget(this, "Predictive.Button6", "6 mno" , nullptr, kBut6Cmd);
_button[kButton7Act] = new ButtonWidget(this, "Predictive.Button7", "7 pqrs" , nullptr, kBut7Cmd);
_button[kButton8Act] = new ButtonWidget(this, "Predictive.Button8", "8 tuv" , nullptr, kBut8Cmd);
_button[kButton9Act] = new ButtonWidget(this, "Predictive.Button9", "9 wxyz" , nullptr, kBut9Cmd);
_button[kButton0Act] = new ButtonWidget(this, "Predictive.Button0", "0" , nullptr, kBut0Cmd);
}
// I18N: You must leave "#" as is, only word 'next' is translatable
_button[kNextAct] = new ButtonWidget(this, "Predictive.Next", _("# next") , nullptr, kNextCmd);
_button[kAddAct] = new ButtonWidget(this, "Predictive.Add", _("add") , nullptr, 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" , _("<") , nullptr, kDelCmd);
// I18N: Pre means 'Predictive', leave '*' as is
_button[kModeAct] = new ButtonWidget(this, "Predictive.Pre", _("* Pre"), nullptr, kModeCmd);
_editText = new EditTextWidget(this, "Predictive.Word", _search, nullptr, 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;
2016-06-01 11:18:17 +02:00
_curPressedButton = kNoAct;
_needRefresh = true;
2020-03-20 15:37:44 +05:30
_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" , _("<") , nullptr, 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);
}
2020-03-20 15:37:44 +05:30
_isPressed = false;
}
void PredictiveDialog::handleKeyDown(Common::KeyState state) {
2020-03-20 15:37:44 +05:30
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");
2013-04-24 01:16:31 +03:00
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++;
#if defined(__DS__) && defined(ENABLE_AGI)
// Pass the line on to the DS word list
DS::addAutoCompleteLine(dict.dictLine[i - 1]);
#endif
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 (except for the DS port). That seems risky, doesn't it?
#if defined(__DS__) && defined(ENABLE_AGI)
// Sort the DS word completion list, to allow for a binary chop later (in the ds backend)
DS::sortAutoCompleteWordList();
#endif
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(_predictiveResult);
//_editText->setCaretPos(_prefix.size() + _currentWord.size());
_editText->markAsDirty();
}
} // namespace GUI