diff --git a/base/commandLine.cpp b/base/commandLine.cpp index 2da8966014d..0ede21b6ff8 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -510,7 +510,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha END_OPTION #endif -#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) +#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) // Optional console window on Windows (default: enabled) DO_LONG_OPTION_BOOL("console") END_OPTION diff --git a/base/main.cpp b/base/main.cpp index 4af86758570..3939d55def8 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -195,7 +195,7 @@ static Common::Error runGame(const EnginePlugin *plugin, OSystem &system, const } // On creation the engine should have set up all debug levels so we can use - // the command line arugments here + // the command line arguments here Common::StringTokenizer tokenizer(edebuglevels, " ,"); while (!tokenizer.empty()) { Common::String token = tokenizer.nextToken(); @@ -206,6 +206,12 @@ static Common::Error runGame(const EnginePlugin *plugin, OSystem &system, const // Initialize any game-specific keymaps engine->initKeymap(); + // Set default values to the custom engine options + const ExtraGuiOptions engineOptions = (*plugin)->getExtraGuiOptions(ConfMan.getActiveDomainName()); + for (uint i = 0; i < engineOptions.size(); i++) { + ConfMan.registerDefault(engineOptions[i].configOption, engineOptions[i].defaultState); + } + // Inform backend that the engine is about to be run system.engineInit(); diff --git a/common/gui_options.cpp b/common/gui_options.cpp index 32a7cc9c414..59fbfb38d12 100644 --- a/common/gui_options.cpp +++ b/common/gui_options.cpp @@ -64,6 +64,14 @@ const struct GameOpt { { GUIO_RENDERPC9821, "pc9821" }, { GUIO_RENDERPC9801, "pc9801" }, + { GUIO_GAMEOPTIONS1, "gameOption1" }, + { GUIO_GAMEOPTIONS2, "gameOption2" }, + { GUIO_GAMEOPTIONS3, "gameOption3" }, + { GUIO_GAMEOPTIONS4, "gameOption4" }, + { GUIO_GAMEOPTIONS5, "gameOption5" }, + { GUIO_GAMEOPTIONS6, "gameOption6" }, + { GUIO_GAMEOPTIONS7, "gameOption7" }, + { GUIO_NONE, 0 } }; diff --git a/common/gui_options.h b/common/gui_options.h index 33ecccad634..774e57e1338 100644 --- a/common/gui_options.h +++ b/common/gui_options.h @@ -56,6 +56,16 @@ #define GUIO_RENDERPC9821 "\037" #define GUIO_RENDERPC9801 "\040" +// Special GUIO flags for the AdvancedDetector's caching of game specific +// options. +#define GUIO_GAMEOPTIONS1 "\041" +#define GUIO_GAMEOPTIONS2 "\042" +#define GUIO_GAMEOPTIONS3 "\043" +#define GUIO_GAMEOPTIONS4 "\044" +#define GUIO_GAMEOPTIONS5 "\045" +#define GUIO_GAMEOPTIONS6 "\046" +#define GUIO_GAMEOPTIONS7 "\047" + #define GUIO0() (GUIO_NONE) #define GUIO1(a) (a) #define GUIO2(a,b) (a b) @@ -63,6 +73,8 @@ #define GUIO4(a,b,c,d) (a b c d) #define GUIO5(a,b,c,d,e) (a b c d e) #define GUIO6(a,b,c,d,e,f) (a b c d e f) +#define GUIO7(a,b,c,d,e,f,g) (a b c d e f g) +#define GUIO8(a,b,c,d,e,f,g,h) (a b c d e f g h) namespace Common { diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp index 6635bcfd5e8..27b93f73959 100644 --- a/engines/advancedDetector.cpp +++ b/engines/advancedDetector.cpp @@ -167,6 +167,25 @@ GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { return detectedGames; } +const ExtraGuiOptions AdvancedMetaEngine::getExtraGuiOptions(const Common::String &target) const { + if (!_extraGuiOptions) + return ExtraGuiOptions(); + + // Query the GUI options + const Common::String guiOptionsString = ConfMan.get("guioptions", target); + const Common::String guiOptions = parseGameGUIOptions(guiOptionsString); + + ExtraGuiOptions options; + + // Add all the applying extra GUI options. + for (const ADExtraGuiOptionsMap *entry = _extraGuiOptions; entry->guioFlag; ++entry) { + if (guiOptions.contains(entry->guioFlag)) + options.push_back(entry->option); + } + + return options; +} + Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) const { assert(engine); @@ -562,8 +581,9 @@ GameDescriptor AdvancedMetaEngine::findGame(const char *gameid) const { return GameDescriptor(); } -AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids) - : _gameDescriptors((const byte *)descs), _descItemSize(descItemSize), _gameids(gameids) { +AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids, const ADExtraGuiOptionsMap *extraGuiOptions) + : _gameDescriptors((const byte *)descs), _descItemSize(descItemSize), _gameids(gameids), + _extraGuiOptions(extraGuiOptions) { _md5Bytes = 5000; _singleid = NULL; diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h index 0cec039b5ed..45a9f183e82 100644 --- a/engines/advancedDetector.h +++ b/engines/advancedDetector.h @@ -133,6 +133,24 @@ enum ADFlags { }; +/** + * Map entry for mapping GUIO_GAMEOPTIONS* to their ExtraGuiOption + * description. + */ +struct ADExtraGuiOptionsMap { + /** + * GUIO_GAMEOPTION* string. + */ + const char *guioFlag; + + /** + * The associated option. + */ + ExtraGuiOption option; +}; + +#define AD_EXTRA_GUI_OPTIONS_TERMINATOR { 0, { 0, 0, 0, 0 } } + /** * A MetaEngine implementation based around the advanced detector code. */ @@ -158,6 +176,11 @@ protected: */ const PlainGameDescriptor *_gameids; + /** + * A map containing all the extra game GUI options the engine supports. + */ + const ADExtraGuiOptionsMap * const _extraGuiOptions; + /** * The number of bytes to compute MD5 sum for. The AdvancedDetector * is primarily based on computing and matching MD5 checksums of files. @@ -211,7 +234,7 @@ protected: const char * const *_directoryGlobs; public: - AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids); + AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids, const ADExtraGuiOptionsMap *extraGuiOptions = 0); /** * Returns list of targets supported by the engine. @@ -225,6 +248,8 @@ public: virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; + virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; + protected: // To be implemented by subclasses virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const = 0; diff --git a/engines/metaengine.h b/engines/metaengine.h index d9c1360042c..632c204978b 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -24,6 +24,7 @@ #include "common/scummsys.h" #include "common/error.h" +#include "common/array.h" #include "engines/game.h" #include "engines/savestate.h" @@ -38,6 +39,19 @@ class FSList; class String; } +/** + * Per-game extra GUI options structure. + * Currently, this can only be used for options with checkboxes. + */ +struct ExtraGuiOption { + const char *label; // option label, e.g. "Fullscreen mode" + const char *tooltip; // option tooltip (when the mouse hovers above it) + const char *configOption; // confMan key, e.g. "fullscreen" + bool defaultState; // the detault state of the checkbox (checked or not) +}; + +typedef Common::Array ExtraGuiOptions; + /** * A meta engine is essentially a factory for Engine instances with the * added ability of listing and detecting supported games. @@ -97,6 +111,16 @@ public: return SaveStateList(); } + /** + * Return a list of extra GUI options. + * Currently, this only supports options with checkboxes. + * + * The default implementation returns an empty list. + */ + virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const { + return ExtraGuiOptions(); + } + /** * Return the maximum save slot that the engine supports. * diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index 4261e230e5a..d9d91c1e9ea 100644 --- a/gui/ThemeEngine.cpp +++ b/gui/ThemeEngine.cpp @@ -47,6 +47,7 @@ const char * const ThemeEngine::kImageLogo = "logo.bmp"; const char * const ThemeEngine::kImageLogoSmall = "logo_small.bmp"; const char * const ThemeEngine::kImageSearch = "search.bmp"; const char * const ThemeEngine::kImageEraser = "eraser.bmp"; +const char * const ThemeEngine::kImageDelbtn = "delbtn.bmp"; struct TextDrawData { const Graphics::Font *_fontPtr; diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index f041f85ab99..e26a584354c 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -35,7 +35,7 @@ #include "graphics/pixelformat.h" -#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.8" +#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.10" class OSystem; @@ -229,6 +229,7 @@ public: static const char *const kImageLogoSmall; ///< ScummVM logo used in the GMM static const char *const kImageSearch; ///< Search tool image used in the launcher static const char *const kImageEraser; ///< Clear input image used in the launcher + static const char *const kImageDelbtn; ///< Delete characters in the predictive dialog /** * Graphics mode enumeration. diff --git a/gui/launcher.cpp b/gui/launcher.cpp index f2184b072a4..c53511da4fd 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -145,11 +145,28 @@ protected: CheckboxWidget *_globalMIDIOverride; CheckboxWidget *_globalMT32Override; CheckboxWidget *_globalVolumeOverride; + + ExtraGuiOptions _engineOptions; }; EditGameDialog::EditGameDialog(const String &domain, const String &desc) : OptionsDialog(domain, "GameOptions") { - + // Retrieve all game specific options. + const EnginePlugin *plugin = 0; + // To allow for game domains without a gameid. + // TODO: Is it intentional that this is still supported? + String gameId(ConfMan.get("gameid", domain)); + if (gameId.empty()) + gameId = domain; + // Retrieve the plugin, since we need to access the engine's MetaEngine + // implementation. + EngineMan.findGame(gameId, &plugin); + if (plugin) { + _engineOptions = (*plugin)->getExtraGuiOptions(domain); + } else { + warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str()); + } + // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w) String gamePath(ConfMan.get("path", _domain)); String extraPath(ConfMan.get("extrapath", _domain)); @@ -208,7 +225,16 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) } // - // 2) The graphics tab + // 2) The engine tab (shown only if there are custom engine options) + // + if (_engineOptions.size() > 0) { + tab->addTab(_("Engine")); + + addEngineControls(tab, "GameOptions_Engine.", _engineOptions); + } + + // + // 3) The graphics tab // _graphicsTabId = tab->addTab(g_system->getOverlayWidth() > 320 ? _("Graphics") : _("GFX")); @@ -220,7 +246,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) addGraphicControls(tab, "GameOptions_Graphics."); // - // 3) The audio tab + // 4) The audio tab // tab->addTab(_("Audio")); @@ -233,7 +259,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) addSubtitleControls(tab, "GameOptions_Audio."); // - // 4) The volume tab + // 5) The volume tab // if (g_system->getOverlayWidth() > 320) tab->addTab(_("Volume")); @@ -248,7 +274,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) addVolumeControls(tab, "GameOptions_Volume."); /* ResidualVM: do not enable midi // - // 5) The MIDI tab + // 6) The MIDI tab // if (!_guioptions.contains(GUIO_NOMIDI)) { tab->addTab(_("MIDI")); @@ -262,7 +288,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) } // - // 6) The MT-32 tab + // 7) The MT-32 tab // if (!_guioptions.contains(GUIO_NOMIDI)) { tab->addTab(_("MT-32")); @@ -276,7 +302,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) }*/ // - // 7) The Paths tab + // 8) The Paths tab // if (g_system->getOverlayWidth() > 320) tab->addTab(_("Paths")); @@ -311,7 +337,6 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear); - // Activate the first tab tab->setActiveTab(0); _tabWidget = tab; @@ -386,6 +411,19 @@ void EditGameDialog::open() { _langPopUp->setEnabled(false); } + // Set the state of engine-specific checkboxes + for (uint j = 0; j < _engineOptions.size(); ++j) { + // The default values for engine-specific checkboxes are not set when + // ScummVM starts, as this would require us to load and poll all of the + // engine plugins on startup. Thus, we set the state of each custom + // option checkbox to what is specified by the engine plugin, and + // update it only if a value has been set in the configuration of the + // currently selected game. + bool isChecked = _engineOptions[j].defaultState; + if (ConfMan.hasKey(_engineOptions[j].configOption, _domain)) + isChecked = ConfMan.getBool(_engineOptions[j].configOption, _domain); + _engineCheckboxes[j]->setState(isChecked); + } const Common::PlatformDescription *p = Common::g_platforms; const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain)); @@ -429,6 +467,11 @@ void EditGameDialog::close() { ConfMan.removeKey("platform", _domain); else ConfMan.set("platform", Common::getPlatformCode(platform), _domain); + + // Set the state of engine-specific checkboxes + for (uint i = 0; i < _engineOptions.size(); i++) { + ConfMan.setBool(_engineOptions[i].configOption, _engineCheckboxes[i]->getState(), _domain); + } } OptionsDialog::close(); } diff --git a/gui/module.mk b/gui/module.mk index df6b76172a7..d272bb03131 100644 --- a/gui/module.mk +++ b/gui/module.mk @@ -13,6 +13,7 @@ MODULE_OBJS := \ message.o \ object.o \ options.o \ + predictivedialog.o \ saveload.o \ themebrowser.o \ ThemeEngine.o \ diff --git a/gui/options.cpp b/gui/options.cpp index 34deb1e92dd..75434942b77 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -890,6 +890,22 @@ void OptionsDialog::addVolumeControls(GuiObject *boss, const Common::String &pre _enableVolumeSettings = true; } +void OptionsDialog::addEngineControls(GuiObject *boss, const Common::String &prefix, const ExtraGuiOptions &engineOptions) { + // Note: up to 7 engine options can currently fit on screen (the most that + // can fit in a 320x200 screen with the classic theme). + // TODO: Increase this number by including the checkboxes inside a scroll + // widget. The appropriate number of checkboxes will need to be added to + // the theme files. + + uint i = 1; + ExtraGuiOptions::const_iterator iter; + for (iter = engineOptions.begin(); iter != engineOptions.end(); ++iter, ++i) { + Common::String id = Common::String::format("%d", i); + _engineCheckboxes.push_back(new CheckboxWidget(boss, + prefix + "customOption" + id + "Checkbox", _(iter->label), _(iter->tooltip))); + } +} + bool OptionsDialog::loadMusicDeviceSetting(PopUpWidget *popup, Common::String setting, MusicType preferredType) { if (!popup || !popup->isEnabled()) return true; @@ -1369,11 +1385,11 @@ void GlobalOptionsDialog::reflowLayout() { if (_midiTabId != -1) { _tabWidget->setActiveTab(_midiTabId); - +/* Residual do not use it _tabWidget->removeWidget(_soundFontClearButton); _soundFontClearButton->setNext(0); delete _soundFontClearButton; - _soundFontClearButton = addClearButton(_tabWidget, "GlobalOptions_MIDI.mcFontClearButton", kClearSoundFontCmd); + _soundFontClearButton = addClearButton(_tabWidget, "GlobalOptions_MIDI.mcFontClearButton", kClearSoundFontCmd);*/ } if (_pathsTabId != -1) { diff --git a/gui/options.h b/gui/options.h index 894c5a8e9f1..0bc17f6b3c2 100644 --- a/gui/options.h +++ b/gui/options.h @@ -22,6 +22,8 @@ #ifndef OPTIONS_DIALOG_H #define OPTIONS_DIALOG_H +#include "engines/metaengine.h" + #include "gui/dialog.h" #include "common/str.h" #include "audio/mididrv.h" @@ -44,6 +46,8 @@ class RadiobuttonGroup; class RadiobuttonWidget; class OptionsDialog : public Dialog { + typedef Common::Array CheckboxWidgetList; + public: OptionsDialog(const Common::String &domain, int x, int y, int w, int h); OptionsDialog(const Common::String &domain, const Common::String &name); @@ -74,6 +78,7 @@ protected: // The default value is the launcher's non-scaled talkspeed value. When SCUMM uses the widget, // it uses its own scale void addSubtitleControls(GuiObject *boss, const Common::String &prefix, int maxSliderVal = 255); + void addEngineControls(GuiObject *boss, const Common::String &prefix, const ExtraGuiOptions &engineOptions); void setGraphicSettingsState(bool enabled); void setAudioSettingsState(bool enabled); @@ -181,6 +186,11 @@ protected: //Theme Options // Common::String _oldTheme; + + // + // Engine-specific controls + // + CheckboxWidgetList _engineCheckboxes; }; diff --git a/gui/predictivedialog.cpp b/gui/predictivedialog.cpp new file mode 100644 index 00000000000..ac329867d29 --- /dev/null +++ b/gui/predictivedialog.cpp @@ -0,0 +1,917 @@ +/* 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" + +using namespace Common; + +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; + const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; + const char *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 == Common::String("")) + _currentWord.clear(); + } else { + if (_prefix.size()) + _prefix.deleteLastChar(); + } + } else if (_prefix.size() + _currentCode.size() < MAXWORDLEN - 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[MAXLINELEN]; + strncpy(tmp, _unitedDict.dictActLine, MAXLINELEN); + tmp[MAXLINELEN - 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); + + goto press; + } 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 (_mode == kModeNum) ? _modebutton->setLabel("* Num") : (_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; + } else { + goto press; + } + } + +press: + pressEditText(); + + if (button == kOkAct) close(); +} + +void PredictiveDialog::handleTickle() { + 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(char *str) { + // Count the number of (space separated) words in the given string. + 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[MAXLINELEN]; + + if (!str) + return; + strncpy(buf, str, MAXLINELEN); + buf[MAXLINELEN - 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(char **dictLine, const String &code, 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() > MAXWORDLEN) + 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[MAXLINELEN]; + strncpy(tmp, _unitedDict.dictLine[line], MAXLINELEN); + tmp[MAXLINELEN - 1] = 0; + char *tok = strtok(tmp, " "); + tok = strtok(NULL, " "); + _currentWord = Common::String(tok, _currentCode.size()); + return true; + } else { + return false; + } +} + +bool PredictiveDialog::searchWord(char *where, const String &whatCode) { + char *ptr = where; + ptr += whatCode.size(); + + 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 String &word, const 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 < MAXWORD) { + 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 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 \ No newline at end of file diff --git a/gui/predictivedialog.h b/gui/predictivedialog.h new file mode 100644 index 00000000000..6d7cd320b71 --- /dev/null +++ b/gui/predictivedialog.h @@ -0,0 +1,142 @@ +/* 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. + */ + +#ifndef GLOBAL_DIALOGS_H +#define GLOBAL_DIALOGS_H + +#include "gui/dialog.h" +#include "common/str.h" +#include "common/stream.h" + +namespace GUI { + +class EditTextWidget; +class ButtonWidget; +class PicButtonWidget; + +enum ButtonId { + kBtn1Act = 0, + kBtn2Act = 1, + kBtn3Act = 2, + kBtn4Act = 3, + kBtn5Act = 4, + kBtn6Act = 5, + kBtn7Act = 6, + kBtn8Act = 7, + kBtn9Act = 8, + kNextAct = 9, + kAddAct = 10, + kDelAct = 11, + kCancelAct = 12, + kOkAct = 13, + kModeAct = 14, + kBtn0Act = 15, + kNoAct = -1 +}; + +enum { + kRepeatDelay = 500 +}; + +enum { + MAXLINELEN = 80, + MAXWORDLEN = 24, + MAXWORD = 50 +}; + +class PredictiveDialog : public GUI::Dialog { + typedef Common::String String; + +public: + PredictiveDialog(); + ~PredictiveDialog(); + + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + virtual void handleKeyDown(Common::KeyState state); + virtual void handleTickle(); + + char * getResult() { return _predictiveResult; } +private: + struct Dict { + char **dictLine; + char *dictText; + char *dictActLine; // using only for united dict... + int32 dictLineCount; + int32 dictTextSize; + String nameDict; + String fnameDict; + }; + + uint8 countWordsInString(char *str); + void bringWordtoTop(char *str, int wordnum); + void loadDictionary(Common::SeekableReadStream *in, Dict &dict); + void loadAllDictionary(Dict &dict); + void addWordToDict(); + void addWord(Dict &dict, const String &word, const String &code); + bool searchWord(char *where, const String &whatCode); + int binarySearch(char **dictLine, const String &code, int dictLineCount); + bool matchWord(); + void processBtnActive(ButtonId active); + void pressEditText(); + + void saveUserDictToFile(); + + void mergeDicts(); +private: + Dict _unitedDict; + Dict _predictiveDict; + Dict _userDict; + + int _mode; + ButtonId _lastbutton; + + bool _userDictHasChanged; + + int _wordNumber; + uint8 _numMatchingWords; + char _predictiveResult[40]; + + String _currentCode; + String _currentWord; + String _prefix; + + uint32 _curTime, _lastTime; + ButtonId _lastPressBtn; + + char _temp[MAXWORDLEN + 1]; + int _repeatcount[MAXWORDLEN]; + + char *_memoryList[MAXWORD]; + int _numMemory; + + String _search; + + bool _navigationwithkeys; +private: + EditTextWidget *_edittext; + ButtonWidget *_modebutton; + ButtonWidget *_delbtn; + ButtonWidget *_addBtn; +}; + +} // namespace GUI + +#endif diff --git a/gui/themes/default.inc b/gui/themes/default.inc index bd28c2e85dc..c1047c0994b 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -599,6 +599,954 @@ "/> " " " " " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " " " " " " " @@ -615,6 +1563,8 @@ " " " " " " +" " +" " " " @@ -1187,6 +2137,31 @@ " " " " " " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " " " " " " " " " " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " +" " +" " " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " +" " +" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " " " -" " -" " -" " " " +" " +" " +" " +" " " " -" " -" " -" " -" " +" " -" " -" " -" " +" " +" " +" " +" " +" " +" " +" " +" " +" " " " -" " -" " " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " +" " +" " " " +" " " " " " " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " " " diff --git a/gui/themes/modern.zip b/gui/themes/modern.zip index 5fab1527feb..761380eddee 100644 Binary files a/gui/themes/modern.zip and b/gui/themes/modern.zip differ diff --git a/gui/themes/modern/THEMERC b/gui/themes/modern/THEMERC index a438205176d..811f001ab84 100644 --- a/gui/themes/modern/THEMERC +++ b/gui/themes/modern/THEMERC @@ -1 +1 @@ -[SCUMMVM_STX0.8.8:ResidualVM Modern Theme:No Author] +[SCUMMVM_STX0.8.10:ResidualVM Modern Theme:No Author] diff --git a/gui/themes/modern/delbtn.bmp b/gui/themes/modern/delbtn.bmp new file mode 100644 index 00000000000..7eb2f409f5c Binary files /dev/null and b/gui/themes/modern/delbtn.bmp differ diff --git a/gui/themes/modern/modern_gfx.stx b/gui/themes/modern/modern_gfx.stx index 23aa10c1e00..8bb1ac0ac79 100644 --- a/gui/themes/modern/modern_gfx.stx +++ b/gui/themes/modern/modern_gfx.stx @@ -100,6 +100,7 @@ + diff --git a/gui/themes/modern/modern_layout.stx b/gui/themes/modern/modern_layout.stx index 69ad9c79fa5..676de964e26 100644 --- a/gui/themes/modern/modern_layout.stx +++ b/gui/themes/modern/modern_layout.stx @@ -49,6 +49,8 @@ + + - + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/themes/modern/modern_layout_lowres.stx b/gui/themes/modern/modern_layout_lowres.stx index 0bfd16c1d9f..00ff440d441 100644 --- a/gui/themes/modern/modern_layout_lowres.stx +++ b/gui/themes/modern/modern_layout_lowres.stx @@ -33,6 +33,9 @@ + + + @@ -637,6 +640,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/widgets/editable.h b/gui/widgets/editable.h index 4b51ac91453..7e453c1204e 100644 --- a/gui/widgets/editable.h +++ b/gui/widgets/editable.h @@ -70,18 +70,17 @@ public: virtual void handleTickle(); virtual bool handleKeyDown(Common::KeyState state); - virtual void reflowLayout(); + bool setCaretPos(int newPos); + protected: virtual void startEditMode() = 0; virtual void endEditMode() = 0; - virtual void abortEditMode() = 0; - + virtual void abortEditMode() = 0; virtual Common::Rect getEditRect() const = 0; virtual int getCaretOffset() const; - void drawCaret(bool erase); - bool setCaretPos(int newPos); + void drawCaret(bool erase); bool adjustOffset(); void makeCaretVisible();