/* 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 "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" #ifdef __DS__ #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"); new ButtonWidget(this, "Predictive.Cancel" , _("Cancel"), 0, kCancelCmd); new ButtonWidget(this, "Predictive.OK" , _("Ok") , 0, kOkCmd); new ButtonWidget(this, "Predictive.Button1", "1 `-.&" , 0, kBut1Cmd); new ButtonWidget(this, "Predictive.Button2", "2 abc" , 0, kBut2Cmd); new ButtonWidget(this, "Predictive.Button3", "3 def" , 0, kBut3Cmd); new ButtonWidget(this, "Predictive.Button4", "4 ghi" , 0, kBut4Cmd); new ButtonWidget(this, "Predictive.Button5", "5 jkl" , 0, kBut5Cmd); new ButtonWidget(this, "Predictive.Button6", "6 mno" , 0, kBut6Cmd); new ButtonWidget(this, "Predictive.Button7", "7 pqrs" , 0, kBut7Cmd); new ButtonWidget(this, "Predictive.Button8", "8 tuv" , 0, kBut8Cmd); new ButtonWidget(this, "Predictive.Button9", "9 wxyz" , 0, kBut9Cmd); new ButtonWidget(this, "Predictive.Button0", "0" , 0, kBut0Cmd); // I18N: You must leave "#" as is, only word 'next' is translatable new ButtonWidget(this, "Predictive.Next" , _("# next"), 0, kNextCmd); _addBtn = new ButtonWidget(this, "Predictive.Add", _("add"), 0, kAddCmd); _addBtn->setEnabled(false); #ifndef DISABLE_FANCY_THEMES _delbtn = new PicButtonWidget(this, "Predictive.Delete", _("Delete char"), kDelCmd); ((PicButtonWidget *)_delbtn)->useThemeTransparency(true); ((PicButtonWidget *)_delbtn)->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDelbtn)); #endif _delbtn = new ButtonWidget(this, "Predictive.Delete" , _("<"), 0, kDelCmd); // I18N: Pre means 'Predictive', leave '*' as is _modebutton = new ButtonWidget(this, "Predictive.Pre", _("* Pre"), 0, kModeCmd); _edittext = new EditTextWidget(this, "Predictive.Word", _search, 0, 0, 0); _userDictHasChanged = false; _predictiveDict.nameDict = "predictive_dictionary"; _predictiveDict.fnameDict = "pred.dic"; _predictiveDict.dictActLine = NULL; _userDict.nameDict = "user_dictionary"; _userDict.fnameDict = "user.dic"; _userDict.dictActLine = NULL; _unitedDict.nameDict = ""; _unitedDict.fnameDict = ""; _predictiveDict.dictLine = NULL; _predictiveDict.dictText = NULL; _predictiveDict.dictLineCount = 0; if (!_predictiveDict.dictText) { loadAllDictionary(_predictiveDict); if (!_predictiveDict.dictText) debug("Predictive Dialog: pred.dic not loaded"); } _userDict.dictLine = NULL; _userDict.dictText = NULL; _userDict.dictTextSize = 0; _userDict.dictLineCount = 0; if (!_userDict.dictText) { loadAllDictionary(_userDict); if (!_userDict.dictText) debug("Predictive Dialog: user.dic not loaded"); } mergeDicts(); _unitedDict.dictActLine = NULL; _unitedDict.dictText = NULL; memset(_repeatcount, 0, sizeof(_repeatcount)); _prefix.clear(); _currentCode.clear(); _currentWord.clear(); _wordNumber = 0; _numMatchingWords = 0; _lastbutton = kNoAct; _mode = kModePre; _lastTime = 0; _curTime = 0; _lastPressBtn = kNoAct; _memoryList[0] = _predictiveDict.dictText; _memoryList[1] = _userDict.dictText; _numMemory = 0; _navigationwithkeys = false; } PredictiveDialog::~PredictiveDialog() { for (int i = 0; i < _numMemory; i++) { free(_memoryList[i]); } free(_userDict.dictLine); free(_predictiveDict.dictLine); free(_unitedDict.dictLine); } 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::handleKeyDown(Common::KeyState state) { ButtonId act = kNoAct; if (getFocusWidget() == _edittext) { setFocusWidget(_addBtn); } switch (state.keycode) { case Common::KEYCODE_ESCAPE: saveUserDictToFile(); close(); return; case Common::KEYCODE_LEFT: _navigationwithkeys = true; if (_lastbutton == kBtn1Act || _lastbutton == kBtn4Act || _lastbutton == kBtn7Act) act = ButtonId(_lastbutton + 2); else if (_lastbutton == kNextAct) act = kBtn0Act; else if (_lastbutton == kDelAct) act = kDelAct; else if (_lastbutton == kCancelAct) act = kOkAct; else if (_lastbutton == kModeAct) act = kAddAct; else act = ButtonId(_lastbutton - 1); _lastbutton = act; //needRefresh = true; break; case Common::KEYCODE_RIGHT: _navigationwithkeys = true; if (_lastbutton == kBtn3Act || _lastbutton == kBtn6Act || _lastbutton == kBtn9Act) act = ButtonId(_lastbutton - 2); else if (_lastbutton == kAddAct) act = kModeAct; else if (_lastbutton == kDelAct) act = kDelAct; else if (_lastbutton == kOkAct) act = kCancelAct; else if (_lastbutton == kBtn0Act) act = kNextAct; else act = ButtonId(_lastbutton + 1); _lastbutton = act; //needRefresh = true; break; case Common::KEYCODE_UP: _navigationwithkeys = true; if (_lastbutton <= kBtn3Act) act = kDelAct; else if (_lastbutton == kNextAct || _lastbutton == kAddAct) act = ButtonId(_lastbutton - 2); else if (_lastbutton == kDelAct) act = kOkAct; else if (_lastbutton == kModeAct) act = kBtn9Act; else if (_lastbutton == kBtn0Act) act = kBtn7Act; else act = ButtonId(_lastbutton - 3); _lastbutton = act; //needRefresh = true; break; case Common::KEYCODE_DOWN: _navigationwithkeys = true; if (_lastbutton == kBtn7Act) act = kBtn0Act; else if (_lastbutton == kBtn8Act || _lastbutton == kBtn9Act) act = ButtonId(_lastbutton + 2); else if (_lastbutton == kDelAct) act = kBtn1Act; else if (_lastbutton == kCancelAct || _lastbutton == kOkAct) act = kDelAct; else if (_lastbutton == kModeAct || _lastbutton == kBtn0Act) act = ButtonId(_lastbutton - 2); else act = ButtonId(_lastbutton + 3); _lastbutton = act; //needRefresh = true; break; case Common::KEYCODE_KP_ENTER: if (_navigationwithkeys) { // when the user has utilized arrow key navigation, // interpret enter as 'click' on the act button act = _lastbutton; } else { // else it is a shortcut for 'Ok' act = kOkAct; } break; case Common::KEYCODE_KP_PLUS: act = kAddAct; break; case Common::KEYCODE_BACKSPACE: case Common::KEYCODE_KP_MINUS: act = kDelAct; break; case Common::KEYCODE_KP_DIVIDE: act = kNextAct; break; case Common::KEYCODE_KP_MULTIPLY: act = kModeAct; break; case Common::KEYCODE_KP0: act = kBtn0Act; 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: act = ButtonId(state.keycode - Common::KEYCODE_KP1); break; default: Dialog::handleKeyDown(state); } if (act != kNoAct) { processBtnActive(act); } } void PredictiveDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { ButtonId act = kNoAct; _navigationwithkeys = false; switch (cmd) { case kDelCmd: act = kDelAct; break; case kNextCmd: act = kNextAct; break; case kAddCmd: act = kAddAct; break; case kModeCmd: act = kModeAct; break; case kBut1Cmd: act = kBtn1Act; break; case kBut2Cmd: act = kBtn2Act; break; case kBut3Cmd: act = kBtn3Act; break; case kBut4Cmd: act = kBtn4Act; break; case kBut5Cmd: act = kBtn5Act; break; case kBut6Cmd: act = kBtn6Act; break; case kBut7Cmd: act = kBtn7Act; break; case kBut8Cmd: act = kBtn8Act; break; case kBut9Cmd: act = kBtn9Act; break; case kBut0Cmd: act = kBtn0Act; break; case kCancelCmd: saveUserDictToFile(); close(); return; case kOkCmd: act = kOkAct; break; default: Dialog::handleCommand(sender, cmd, data); } if (act != kNoAct) { processBtnActive(act); } } void PredictiveDialog::processBtnActive(ButtonId button) { uint8 x; 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) ", NULL }; if (_mode == kModeAbc) { if (button >= kBtn1Act && button <= kBtn9Act) { if (!_lastTime) _lastTime = g_system->getMillis(); if (_lastPressBtn == button) { _curTime = g_system->getMillis(); if ((_curTime - _lastTime) < kRepeatDelay) { button = kNextAct; _lastTime = _curTime; } else { _lastTime = 0; } } else { _lastPressBtn = button; _lastTime = g_system->getMillis(); } } } if (button >= kBtn1Act) { _lastbutton = button; if (button == kBtn0Act && _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; _lastPressBtn = kNoAct; _curTime = 0; } else if (button < kNextAct || button == kDelAct || button == kBtn0Act) { // 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 == kBtn0Act) { // 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 (x = 0; x < _currentCode.size(); x++) if (_currentCode[x] >= '1') _temp[x] = buttons[_currentCode[x] - '1'][_repeatcount[x]]; _temp[_currentCode.size()] = 0; _currentWord = _temp; } } 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(NULL, " "); _currentWord = Common::String(tok, _currentCode.size()); } } else if (_mode == kModeAbc) { 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("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++; _addBtn->setEnabled(false); if (_mode > kModeAbc) { _mode = kModePre; // I18N: Pre means 'Predictive', leave '*' as is _modebutton->setLabel("* Pre"); // I18N: 'Num' means Numbers, 'Abc' means Latin alphabet input } else if (_mode == kModeNum) { _modebutton->setLabel("* Num"); } else { _modebutton->setLabel("* Abc"); _addBtn->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; _lastPressBtn = kNoAct; _curTime = 0; } } pressEditText(); if (button == kOkAct) close(); } void PredictiveDialog::handleTickle() { // TODO/FIXME: This code does not seem to make any sense. It is only // triggered when _lastTime is zero and sets _lastTime to zero again // under some condition. This should really be a nop. Probably this // code intends to check "_lastTime" instead of "!_lastTime". if (!_lastTime) { if ((_curTime - _lastTime) > kRepeatDelay) { _lastTime = 0; } } } void PredictiveDialog::mergeDicts() { _unitedDict.dictLineCount = _predictiveDict.dictLineCount + _userDict.dictLineCount; _unitedDict.dictLine = (char **)calloc(1, sizeof(char *) * _unitedDict.dictLineCount); if (!_unitedDict.dictLine) { debug("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("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 words; char buf[kMaxLineLen]; if (!str) return; strncpy(buf, str, kMaxLineLen); buf[kMaxLineLen - 1] = 0; char *word = strtok(buf, " "); if (!word) { debug("Predictive Dialog: Invalid dictionary line"); return; } words.push_back(word); while ((word = strtok(NULL, " ")) != NULL) 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 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 = NULL; } 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(NULL, " "); _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; 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(1, sizeof(char *) * (dict.dictLineCount + 1)); if (!newDictLine) { warning("Predictive Dialog: cannot allocate memory for index buffer"); return; } newDictLine[dict.dictLineCount] = '\0'; 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(1, sizeof(char *) * dict.dictLineCount); 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("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(1, sizeof(char *) * lines); if (dict.dictLine == NULL) { 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++; #ifdef __DS__ // 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("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? #ifdef __DS__ // 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("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.fnameDict); 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.fnameDict.c_str()); 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.fnameDict.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->draw(); } } // namespace GUI