aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/ThemeEngine.cpp34
-rw-r--r--gui/ThemeEngine.h18
-rw-r--r--gui/ThemeParser.cpp11
-rw-r--r--gui/ThemeParser.h3
-rw-r--r--gui/Tooltip.cpp2
-rw-r--r--gui/credits.h36
-rw-r--r--gui/debugger.cpp5
-rw-r--r--gui/dialog.cpp20
-rw-r--r--gui/dialog.h16
-rw-r--r--gui/gui-manager.cpp16
-rw-r--r--gui/launcher.cpp63
-rw-r--r--gui/module.mk2
-rw-r--r--gui/options.cpp63
-rw-r--r--gui/options.h13
-rw-r--r--gui/predictivedialog.cpp998
-rw-r--r--gui/predictivedialog.h143
-rw-r--r--gui/saveload-dialog.cpp946
-rw-r--r--gui/saveload-dialog.h209
-rw-r--r--gui/saveload.cpp391
-rw-r--r--gui/saveload.h70
-rw-r--r--gui/themes/default.inc318
-rw-r--r--gui/themes/scummclassic.zipbin86033 -> 95180 bytes
-rw-r--r--gui/themes/scummclassic/THEMERC2
-rw-r--r--gui/themes/scummclassic/classic_gfx.stx13
-rw-r--r--gui/themes/scummclassic/classic_layout.stx170
-rw-r--r--gui/themes/scummclassic/classic_layout_lowres.stx156
-rw-r--r--gui/themes/scummmodern.zipbin1441312 -> 1453485 bytes
-rw-r--r--gui/themes/scummmodern/THEMERC2
-rw-r--r--gui/themes/scummmodern/delbtn.bmpbin0 -> 822 bytes
-rw-r--r--gui/themes/scummmodern/grid.bmpbin0 -> 822 bytes
-rw-r--r--gui/themes/scummmodern/list.bmpbin0 -> 822 bytes
-rw-r--r--gui/themes/scummmodern/scummmodern_gfx.stx40
-rw-r--r--gui/themes/scummmodern/scummmodern_layout.stx173
-rw-r--r--gui/themes/scummmodern/scummmodern_layout_lowres.stx155
-rw-r--r--gui/themes/translations.datbin289269 -> 370908 bytes
-rw-r--r--gui/widget.cpp133
-rw-r--r--gui/widget.h28
-rw-r--r--gui/widgets/editable.h5
-rw-r--r--gui/widgets/list.h1
39 files changed, 3732 insertions, 523 deletions
diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index 2cb1635e20..e2fa2580f5 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -30,11 +30,11 @@
#include "graphics/cursorman.h"
#include "graphics/fontman.h"
-#include "graphics/imagedec.h"
#include "graphics/surface.h"
#include "graphics/VectorRenderer.h"
#include "graphics/fonts/bdf.h"
#include "graphics/fonts/ttf.h"
+#include "graphics/decoders/bmp.h"
#include "gui/widget.h"
#include "gui/ThemeEngine.h"
@@ -47,6 +47,9 @@ 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";
+const char * const ThemeEngine::kImageList = "list.bmp";
+const char * const ThemeEngine::kImageGrid = "grid.bmp";
struct TextDrawData {
const Graphics::Font *_fontPtr;
@@ -174,6 +177,7 @@ static const DrawDataInfo kDrawDataDefaults[] = {
{kDDButtonIdle, "button_idle", true, kDDWidgetBackgroundSlider},
{kDDButtonHover, "button_hover", false, kDDButtonIdle},
{kDDButtonDisabled, "button_disabled", true, kDDNone},
+ {kDDButtonPressed, "button_pressed", false, kDDButtonIdle},
{kDDSliderFull, "slider_full", false, kDDNone},
{kDDSliderHover, "slider_hover", false, kDDNone},
@@ -427,7 +431,7 @@ bool ThemeEngine::init() {
void ThemeEngine::clearAll() {
if (_initOk) {
_system->clearOverlay();
- _system->grabOverlay((OverlayColor *)_screen.pixels, _screen.w);
+ _system->grabOverlay(_screen.pixels, _screen.pitch);
}
}
@@ -452,7 +456,7 @@ void ThemeEngine::refresh() {
if (_useCursor) {
CursorMan.replaceCursorPalette(_cursorPal, 0, _cursorPalSize);
- CursorMan.replaceCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, _cursorTargetScale);
+ CursorMan.replaceCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
}
}
}
@@ -463,7 +467,7 @@ void ThemeEngine::enable() {
if (_useCursor) {
CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
- CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, _cursorTargetScale);
+ CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
CursorMan.showMouse(true);
}
@@ -620,20 +624,25 @@ bool ThemeEngine::addBitmap(const Common::String &filename) {
if (surf)
return true;
- // If not, try to load the bitmap via the ImageDecoder class.
+ // If not, try to load the bitmap via the BitmapDecoder class.
+ Graphics::BitmapDecoder bitmapDecoder;
+ const Graphics::Surface *srcSurface = 0;
Common::ArchiveMemberList members;
_themeFiles.listMatchingMembers(members, filename);
for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
Common::SeekableReadStream *stream = (*i)->createReadStream();
if (stream) {
- surf = Graphics::ImageDecoder::loadFile(*stream, _overlayFormat);
+ bitmapDecoder.loadStream(*stream);
+ srcSurface = bitmapDecoder.getSurface();
delete stream;
-
- if (surf)
+ if (srcSurface)
break;
}
}
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+
// Store the surface into our hashmap (attention, may store NULL entries!)
_bitmaps[filename] = surf;
@@ -871,6 +880,8 @@ void ThemeEngine::drawButton(const Common::Rect &r, const Common::String &str, W
dd = kDDButtonHover;
else if (state == kStateDisabled)
dd = kDDButtonDisabled;
+ else if (state == kStatePressed)
+ dd = kDDButtonPressed;
queueDD(dd, r, 0, hints & WIDGET_CLEARBG);
queueDDText(getTextData(dd), getTextColor(dd), r, str, false, true, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV);
@@ -1119,6 +1130,7 @@ void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, Wid
break;
case kStateEnabled:
+ case kStatePressed:
colorId = kTextColorNormal;
break;
}
@@ -1139,6 +1151,7 @@ void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, Wid
break;
case kStateEnabled:
+ case kStatePressed:
colorId = kTextColorAlternative;
break;
}
@@ -1276,7 +1289,7 @@ void ThemeEngine::openDialog(bool doBuffer, ShadingStyle style) {
_vectorRenderer->setSurface(&_screen);
}
-bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int hotspotY, int scale) {
+bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int hotspotY) {
if (!_system->hasFeature(OSystem::kFeatureCursorPalette))
return true;
@@ -1294,7 +1307,6 @@ bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int
// Set up the cursor parameters
_cursorHotspotX = hotspotX;
_cursorHotspotY = hotspotY;
- _cursorTargetScale = scale;
_cursorWidth = cursor->w;
_cursorHeight = cursor->h;
@@ -1412,7 +1424,7 @@ const Graphics::Font *ThemeEngine::loadScalableFont(const Common::String &filena
for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
Common::SeekableReadStream *stream = (*i)->createReadStream();
if (stream) {
- font = Graphics::loadTTFFont(*stream, pointsize, false,
+ font = Graphics::loadTTFFont(*stream, pointsize, 0, false,
#ifdef USE_TRANSLATION
TransMan.getCharsetMapping()
#else
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index f041f85ab9..6fb93d3b46 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.16"
class OSystem;
@@ -81,6 +81,7 @@ enum DrawData {
kDDButtonIdle,
kDDButtonHover,
kDDButtonDisabled,
+ kDDButtonPressed,
kDDSliderFull,
kDDSliderHover,
@@ -178,7 +179,8 @@ public:
enum State {
kStateDisabled, ///< Indicates that the widget is disabled, that does NOT include that it is invisible
kStateEnabled, ///< Indicates that the widget is enabled
- kStateHighlight ///< Indicates that the widget is highlighted by the user
+ kStateHighlight, ///< Indicates that the widget is highlighted by the user
+ kStatePressed ///< Indicates that the widget is pressed, currently works for buttons
};
typedef State WidgetStateInfo;
@@ -229,6 +231,9 @@ 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
+ static const char *const kImageList; ///< List image used in save/load chooser selection
+ static const char *const kImageGrid; ///< Grid image used in save/load chooser selection
/**
* Graphics mode enumeration.
@@ -273,6 +278,11 @@ public:
void disable();
/**
+ * Query the set up pixel format.
+ */
+ const Graphics::PixelFormat getPixelFormat() const { return _overlayFormat; }
+
+ /**
* Implementation of the GUI::Theme API. Called when a
* new dialog is opened. Note that the boolean parameter
* meaning has been changed.
@@ -492,9 +502,8 @@ public:
* @param filename File name of the bitmap to load.
* @param hotspotX X Coordinate of the bitmap which does the cursor click.
* @param hotspotY Y Coordinate of the bitmap which does the cursor click.
- * @param scale Scale at which the bitmap is supposed to be used.
*/
- bool createCursor(const Common::String &filename, int hotspotX, int hotspotY, int scale);
+ bool createCursor(const Common::String &filename, int hotspotX, int hotspotY);
/**
* Wrapper for restoring data from the Back Buffer to the screen.
@@ -666,7 +675,6 @@ protected:
bool _useCursor;
int _cursorHotspotX, _cursorHotspotY;
- int _cursorTargetScale;
enum {
MAX_CURS_COLORS = 255
};
diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp
index 9ccdedd564..8285aff7ca 100644
--- a/gui/ThemeParser.cpp
+++ b/gui/ThemeParser.cpp
@@ -218,15 +218,12 @@ bool ThemeParser::parserCallback_cursor(ParserNode *node) {
return true;
}
- int spotx, spoty, scale;
+ int spotx, spoty;
if (!parseIntegerKey(node->values["hotspot"], 2, &spotx, &spoty))
return parserError("Error parsing cursor Hot Spot coordinates.");
- if (!parseIntegerKey(node->values["scale"], 1, &scale))
- return parserError("Error parsing cursor scale.");
-
- if (!_theme->createCursor(node->values["file"], spotx, spoty, scale))
+ if (!_theme->createCursor(node->values["file"], spotx, spoty))
return parserError("Error creating Bitmap Cursor.");
return true;
@@ -551,11 +548,11 @@ bool ThemeParser::parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawst
else
return parserError("'" + stepNode->values["fill"] + "' is not a valid fill mode for a shape.");
}
-
+
if (stepNode->values.contains("padding")) {
val = stepNode->values["padding"];
int pr, pt, pl, pb;
- if (parseIntegerKey(val, 4, &pl, &pt, &pr, &pb))
+ if (parseIntegerKey(val, 4, &pl, &pt, &pr, &pb))
drawstep->padding.left = pl,
drawstep->padding.top = pt,
drawstep->padding.right = pr,
diff --git a/gui/ThemeParser.h b/gui/ThemeParser.h
index 4b7e88cbf3..360e3da009 100644
--- a/gui/ThemeParser.h
+++ b/gui/ThemeParser.h
@@ -85,7 +85,6 @@ protected:
XML_KEY(cursor)
XML_PROP(file, true)
XML_PROP(hotspot, true)
- XML_PROP(scale, true)
XML_PROP(resolution, false)
KEY_END()
@@ -140,7 +139,7 @@ protected:
XML_PROP(height, false)
XML_PROP(xpos, false)
XML_PROP(ypos, false)
- XML_PROP(padding, false)
+ XML_PROP(padding, false)
XML_PROP(orientation, false)
XML_PROP(file, false)
KEY_END()
diff --git a/gui/Tooltip.cpp b/gui/Tooltip.cpp
index 85e5856cff..88124e782b 100644
--- a/gui/Tooltip.cpp
+++ b/gui/Tooltip.cpp
@@ -37,7 +37,7 @@ Tooltip::Tooltip() :
}
void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
- assert(widget->getTooltip());
+ assert(widget->hasTooltip());
_maxWidth = g_gui.xmlEval()->getVar("Globals.Tooltip.MaxWidth", 100);
_xdelta = g_gui.xmlEval()->getVar("Globals.Tooltip.XDelta", 0);
diff --git a/gui/credits.h b/gui/credits.h
index 154f577fba..37c5a7bd95 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -106,8 +106,10 @@ static const char *credits[] = {
"C1""DreamWeb",
"C0""Torbj\366rn Andersson",
"C0""Bertrand Augereau",
+"C0""Filippos Karapetis",
"C0""Vladimir Menshakov",
"C2""(retired)",
+"C0""Willem Jan Palenstijn",
"",
"C1""Gob",
"C0""Torbj\366rn Andersson",
@@ -157,6 +159,9 @@ static const char *credits[] = {
"C1""Parallaction",
"C0""peres",
"",
+"C1""Pegasus",
+"C0""Matthew Hoops",
+"",
"C1""Queen",
"C0""David Eriksson",
"C2""(retired)",
@@ -232,6 +237,11 @@ static const char *credits[] = {
"C0""Filippos Karapetis",
"C0""Joost Peters",
"",
+"C1""Tony",
+"C0""Arnaud Boutonn\351",
+"C0""Paul Gilbert",
+"C0""Alyssa Milburn",
+"",
"C1""Toon",
"C0""Sylvain Dupont",
"",
@@ -247,6 +257,9 @@ static const char *credits[] = {
"C0""Gregory Montoir",
"C2""(retired)",
"",
+"C1""Wintermute",
+"C0""Einar Johan T. S\370m\345en",
+"",
"",
"C1""Backend Teams",
"C1""Android",
@@ -459,6 +472,9 @@ static const char *credits[] = {
"C1""Translations",
"C0""Thierry Crozat",
"C2""Translation Lead",
+"C1""Basque",
+"C0""Mikel Iturbe Urretxa",
+"",
"C1""Catalan",
"C0""Jordi Vilalta Prat",
"",
@@ -471,9 +487,13 @@ static const char *credits[] = {
"C1""French",
"C0""Thierry Crozat",
"",
+"C1""Galician",
+"C0""Santiago G. Sanz",
+"",
"C1""German",
"C0""Simon Sawatzki",
"C0""Lothar Serra Mari",
+"C2""(retired)",
"",
"C1""Hungarian",
"C0""Alex Bevilacqua",
@@ -483,10 +503,10 @@ static const char *credits[] = {
"C0""Matteo Angelino",
"",
"C1""Norwegian (Bokm\345l)",
-"C0""Einar Johan T. S\370m\345en",
+"C0""Einar Johan S\370m\345en",
"",
"C1""Norwegian (Nynorsk)",
-"C0""Einar Johan T. S\370m\345en",
+"C0""Einar Johan S\370m\345en",
"",
"C1""Polish",
"C0""GrajPoPolsku.pl Team",
@@ -671,6 +691,8 @@ static const char *credits[] = {
"C2""For generously providing hosting for our buildbot, SVN repository, planet and doxygen sites as well as tons of HD space",
"C0""DOSBox Team",
"C2""For their awesome OPL2 and OPL3 emulator",
+"C0""Yusuke Kamiyamane",
+"C2""For contributing some GUI icons ",
"C0""Till Kresslein",
"C2""For design of modern ScummVM GUI",
"C0""Jezar",
@@ -687,8 +709,6 @@ static const char *credits[] = {
"C2""For additional work on the original MT-32 emulator",
"C0""James Woodcock",
"C2""Soundtrack enhancements",
-"C0""Some icons by Yusuke Kamiyamane",
-"C0""",
"C0""Tony Warriner and everyone at Revolution Software Ltd. for sharing with us the source of some of their brilliant games, allowing us to release Beneath a Steel Sky as freeware... and generally being supportive above and beyond the call of duty.",
"C0""",
"C0""John Passfield and Steve Stamatiadis for sharing the source of their classic title, Flight of the Amazon Queen and also being incredibly supportive.",
@@ -707,5 +727,13 @@ static const char *credits[] = {
"C0""",
"C0""Broken Sword 2.5 team for providing sources of their engine and their great support.",
"C0""",
+"C0""Neil Dodwell and David Dew from Creative Reality for providing the source of Dreamweb and for their tremendous support.",
+"C0""",
+"C0""Janusz Wisniewski and Miroslaw Liminowicz from Laboratorium Komputerowe Avalon for providing full source code for Soltys and letting us redistribute the game.",
+"C0""",
+"C0""Jan Nedoma for providing the sources to the Wintermute-engine, and for his support while porting the engine to ScummVM.",
+"C0""",
+"C0""Bob Bell, Michel Kripalani, Tommy Yune, from Presto Studios for providing the source code of The Journeyman Project: Pegasus Prime.",
+"C0""",
"",
};
diff --git a/gui/debugger.cpp b/gui/debugger.cpp
index 26e62dc1d9..972163df6f 100644
--- a/gui/debugger.cpp
+++ b/gui/debugger.cpp
@@ -200,9 +200,8 @@ void Debugger::enter() {
bool Debugger::handleCommand(int argc, const char **argv, bool &result) {
if (_cmds.contains(argv[0])) {
- Debuglet *debuglet = _cmds[argv[0]].get();
- assert(debuglet);
- result = (*debuglet)(argc, argv);
+ assert(_cmds[argv[0]]);
+ result = (*_cmds[argv[0]])(argc, argv);
return true;
}
diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index fd15ba5e09..ffca15bbc8 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -21,6 +21,10 @@
#include "common/rect.h"
+#ifdef ENABLE_KEYMAPPER
+#include "common/events.h"
+#endif
+
#include "gui/gui-manager.h"
#include "gui/dialog.h"
#include "gui/widget.h"
@@ -38,7 +42,7 @@ namespace GUI {
Dialog::Dialog(int x, int y, int w, int h)
: GuiObject(x, y, w, h),
- _mouseWidget(0), _focusedWidget(0), _dragWidget(0), _visible(false),
+ _mouseWidget(0), _focusedWidget(0), _dragWidget(0), _tickleWidget(0), _visible(false),
_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
// Some dialogs like LauncherDialog use internally a fixed size, even though
// their widgets rely on the layout to be initialized correctly by the theme.
@@ -50,7 +54,7 @@ Dialog::Dialog(int x, int y, int w, int h)
Dialog::Dialog(const Common::String &name)
: GuiObject(name),
- _mouseWidget(0), _focusedWidget(0), _dragWidget(0), _visible(false),
+ _mouseWidget(0), _focusedWidget(0), _dragWidget(0), _tickleWidget(0), _visible(false),
_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
// It may happen that we have 3x scaler in launcher (960xY) and then 640x480
@@ -113,6 +117,12 @@ void Dialog::reflowLayout() {
GuiObject::reflowLayout();
}
+void Dialog::lostFocus() {
+ if (_tickleWidget) {
+ _tickleWidget->lostFocus();
+ }
+}
+
void Dialog::setFocusWidget(Widget *widget) {
// The focus will change. Tell the old focused widget (if any)
// that it lost the focus.
@@ -304,6 +314,9 @@ void Dialog::handleTickle() {
// Focused widget receives tickle notifications
if (_focusedWidget && _focusedWidget->getFlags() & WIDGET_WANT_TICKLE)
_focusedWidget->handleTickle();
+
+ if (_tickleWidget && _tickleWidget->getFlags() & WIDGET_WANT_TICKLE)
+ _tickleWidget->handleTickle();
}
void Dialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
@@ -314,6 +327,9 @@ void Dialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
}
}
+#ifdef ENABLE_KEYMAPPER
+void Dialog::handleOtherEvent(Common::Event evt) { }
+#endif
/*
* Determine the widget at location (x,y) if any. Assumes the coordinates are
* in the local coordinate system, i.e. relative to the top left of the dialog.
diff --git a/gui/dialog.h b/gui/dialog.h
index a324450996..1773c6633e 100644
--- a/gui/dialog.h
+++ b/gui/dialog.h
@@ -29,6 +29,12 @@
#include "gui/object.h"
#include "gui/ThemeEngine.h"
+#ifdef ENABLE_KEYMAPPER
+namespace Common {
+struct Event;
+}
+#endif
+
namespace GUI {
class Widget;
@@ -46,6 +52,7 @@ protected:
Widget *_mouseWidget;
Widget *_focusedWidget;
Widget *_dragWidget;
+ Widget *_tickleWidget;
bool _visible;
ThemeEngine::DialogBackground _backgroundType;
@@ -65,7 +72,13 @@ public:
void setFocusWidget(Widget *widget);
Widget *getFocusWidget() { return _focusedWidget; }
+ void setTickleWidget(Widget *widget) { _tickleWidget = widget; }
+ void unSetTickleWidget() { _tickleWidget = NULL; }
+ Widget *getTickleWidget() { return _tickleWidget; }
+
virtual void reflowLayout();
+ virtual void lostFocus();
+ virtual void receivedFocus() {}
protected:
virtual void open();
@@ -82,6 +95,9 @@ protected:
virtual void handleKeyUp(Common::KeyState state);
virtual void handleMouseMoved(int x, int y, int button);
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+#ifdef ENABLE_KEYMAPPER
+ virtual void handleOtherEvent(Common::Event evt);
+#endif
Widget *findWidget(int x, int y); // Find the widget at pos x,y if any
Widget *findWidget(const char *name);
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 4fa60bfe07..a0ef4216aa 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -366,6 +366,9 @@ void GuiManager::runLoop() {
screenChange();
break;
default:
+#ifdef ENABLE_KEYMAPPER
+ activeDialog->handleOtherEvent(event);
+#endif
break;
}
@@ -378,7 +381,7 @@ void GuiManager::runLoop() {
if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis()) {
Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
- if (wdg && wdg->getTooltip()) {
+ if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)) {
Tooltip *tooltip = new Tooltip();
tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
tooltip->runModal();
@@ -438,6 +441,11 @@ void GuiManager::restoreState() {
}
void GuiManager::openDialog(Dialog *dialog) {
+ dialog->receivedFocus();
+
+ if (!_dialogStack.empty())
+ getTopDialog()->lostFocus();
+
_dialogStack.push(dialog);
if (_redrawStatus != kRedrawFull)
_redrawStatus = kRedrawOpenDialog;
@@ -455,7 +463,11 @@ void GuiManager::closeTopDialog() {
return;
// Remove the dialog from the stack
- _dialogStack.pop();
+ _dialogStack.pop()->lostFocus();
+
+ if (!_dialogStack.empty())
+ getTopDialog()->receivedFocus();
+
if (_redrawStatus != kRedrawFull)
_redrawStatus = kRedrawCloseDialog;
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index a3e4925848..0f4867ced5 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -145,10 +145,27 @@ 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));
@@ -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.");
//
- // 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;
@@ -342,8 +367,7 @@ void EditGameDialog::open() {
e = ConfMan.hasKey("gfx_mode", _domain) ||
ConfMan.hasKey("render_mode", _domain) ||
ConfMan.hasKey("fullscreen", _domain) ||
- ConfMan.hasKey("aspect_ratio", _domain) ||
- ConfMan.hasKey("disable_dithering", _domain);
+ ConfMan.hasKey("aspect_ratio", _domain);
_globalGraphicsOverride->setState(e);
e = ConfMan.hasKey("music_driver", _domain) ||
@@ -386,6 +410,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 +466,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();
}
@@ -621,7 +663,6 @@ LauncherDialog::LauncherDialog()
_list->setEditable(false);
_list->setNumberingMode(kListNumberingOff);
-
// Populate the list
updateListing();
@@ -636,7 +677,7 @@ LauncherDialog::LauncherDialog()
_browser = new BrowserDialog(_("Select directory with game data"), true);
// Create Load dialog
- _loadDialog = new SaveLoadChooser(_("Load game:"), _("Load"));
+ _loadDialog = new SaveLoadChooser(_("Load game:"), _("Load"), false);
}
void LauncherDialog::selectTarget(const String &target) {
diff --git a/gui/module.mk b/gui/module.mk
index df6b76172a..a435d8cca7 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -13,7 +13,9 @@ MODULE_OBJS := \
message.o \
object.o \
options.o \
+ predictivedialog.o \
saveload.o \
+ saveload-dialog.o \
themebrowser.o \
ThemeEngine.o \
ThemeEval.o \
diff --git a/gui/options.cpp b/gui/options.cpp
index 5085f9cdd9..4868f1876d 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -102,7 +102,6 @@ void OptionsDialog::init() {
_renderModePopUpDesc = 0;
_fullscreenCheckbox = 0;
_aspectCheckbox = 0;
- _disableDitheringCheckbox = 0;
_enableAudioSettings = false;
_midiPopUp = 0;
_midiPopUpDesc = 0;
@@ -217,14 +216,6 @@ void OptionsDialog::open() {
}
#endif // SMALL_SCREEN_DEVICE
- // EGA undithering setting
- if (_guioptions.contains(GUIO_EGAUNDITHER) || _domain == Common::ConfigManager::kApplicationDomain) {
- _disableDitheringCheckbox->setEnabled(true);
- _disableDitheringCheckbox->setState(ConfMan.getBool("disable_dithering", _domain));
- } else {
- _disableDitheringCheckbox->setState(false);
- _disableDitheringCheckbox->setEnabled(false);
- }
}
// Audio options
@@ -333,7 +324,6 @@ void OptionsDialog::close() {
ConfMan.setBool("fullscreen", _fullscreenCheckbox->getState(), _domain);
ConfMan.setBool("aspect_ratio", _aspectCheckbox->getState(), _domain);
- ConfMan.setBool("disable_dithering", _disableDitheringCheckbox->getState(), _domain);
bool isSet = false;
@@ -359,7 +349,6 @@ void OptionsDialog::close() {
} else {
ConfMan.removeKey("fullscreen", _domain);
ConfMan.removeKey("aspect_ratio", _domain);
- ConfMan.removeKey("disable_dithering", _domain);
ConfMan.removeKey("gfx_mode", _domain);
ConfMan.removeKey("render_mode", _domain);
}
@@ -617,10 +606,6 @@ void OptionsDialog::setGraphicSettingsState(bool enabled) {
else
_aspectCheckbox->setEnabled(enabled);
#endif
- if (_guioptions.contains(GUIO_EGAUNDITHER) && enabled)
- _disableDitheringCheckbox->setEnabled(true);
- else
- _disableDitheringCheckbox->setEnabled(false);
}
void OptionsDialog::setAudioSettingsState(bool enabled) {
@@ -750,7 +735,7 @@ void OptionsDialog::addGraphicControls(GuiObject *boss, const Common::String &pr
}
// RenderMode popup
- const Common::String allFlags = renderType2GUIO((uint32)-1);
+ const Common::String allFlags = Common::allRenderModesGUIOs();
bool renderingTypeDefined = (strpbrk(_guioptions.c_str(), allFlags.c_str()) != NULL);
_renderModePopUpDesc = new StaticTextWidget(boss, prefix + "grRenderPopupDesc", _("Render mode:"), _("Special dithering modes supported by some games"));
@@ -759,7 +744,7 @@ void OptionsDialog::addGraphicControls(GuiObject *boss, const Common::String &pr
_renderModePopUp->appendEntry("");
const Common::RenderModeDescription *rm = Common::g_renderModes;
for (; rm->code; ++rm) {
- Common::String renderGuiOption = renderType2GUIO(rm->id);
+ Common::String renderGuiOption = Common::renderMode2GUIO(rm->id);
if ((_domain == Common::ConfigManager::kApplicationDomain) || (_domain != Common::ConfigManager::kApplicationDomain && !renderingTypeDefined) || (_guioptions.contains(renderGuiOption)))
_renderModePopUp->appendEntry(_c(rm->description, context), rm->id);
}
@@ -769,7 +754,6 @@ void OptionsDialog::addGraphicControls(GuiObject *boss, const Common::String &pr
// Aspect ratio checkbox
_aspectCheckbox = new CheckboxWidget(boss, prefix + "grAspectCheckbox", _("Aspect ratio correction"), _("Correct aspect ratio for 320x200 games"));
- _disableDitheringCheckbox = new CheckboxWidget(boss, prefix + "grDisableDitheringCheckbox", _("EGA undithering"), _("Enable undithering in EGA games that support it"));
_enableGraphicSettings = true;
}
@@ -997,6 +981,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;
@@ -1010,7 +1010,7 @@ bool OptionsDialog::loadMusicDeviceSetting(PopUpWidget *popup, Common::String se
for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) {
if (setting.empty() ? (preferredType == d->getMusicType()) : (drv == d->getCompleteId())) {
popup->setSelectedTag(d->getHandle());
- return popup->getSelected() == -1 ? false : true;
+ return popup->getSelected() != -1;
}
}
}
@@ -1040,31 +1040,6 @@ void OptionsDialog::saveMusicDeviceSetting(PopUpWidget *popup, Common::String se
ConfMan.removeKey(setting, _domain);
}
-Common::String OptionsDialog::renderType2GUIO(uint32 renderType) {
- static const struct {
- Common::RenderMode type;
- const char *guio;
- } renderGUIOMapping[] = {
- { Common::kRenderHercG, GUIO_RENDERHERCGREEN },
- { Common::kRenderHercA, GUIO_RENDERHERCAMBER },
- { Common::kRenderCGA, GUIO_RENDERCGA },
- { Common::kRenderEGA, GUIO_RENDEREGA },
- { Common::kRenderVGA, GUIO_RENDERVGA },
- { Common::kRenderAmiga, GUIO_RENDERAMIGA },
- { Common::kRenderFMTowns, GUIO_RENDERFMTOWNS },
- { Common::kRenderPC9821, GUIO_RENDERPC9821 },
- { Common::kRenderPC9801, GUIO_RENDERPC9801 }
- };
- Common::String res;
-
- for (int i = 0; i < ARRAYSIZE(renderGUIOMapping); i++) {
- if (renderType == renderGUIOMapping[i].type || renderType == (uint32)-1)
- res += renderGUIOMapping[i].guio;
- }
-
- return res;
-}
-
int OptionsDialog::getSubtitleMode(bool subtitles, bool speech_mute) {
if (_guioptions.contains(GUIO_NOSUBTITLES))
return kSubtitlesSpeech; // Speech only
diff --git a/gui/options.h b/gui/options.h
index 83c9d60d59..def56cfa35 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<CheckboxWidget *> 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);
@@ -85,8 +90,6 @@ protected:
bool loadMusicDeviceSetting(PopUpWidget *popup, Common::String setting, MusicType preferredType = MT_AUTO);
void saveMusicDeviceSetting(PopUpWidget *popup, Common::String setting);
- Common::String renderType2GUIO(uint32 renderType);
-
TabWidget *_tabWidget;
int _graphicsTabId;
int _midiTabId;
@@ -101,7 +104,6 @@ private:
PopUpWidget *_gfxPopUp;
CheckboxWidget *_fullscreenCheckbox;
CheckboxWidget *_aspectCheckbox;
- CheckboxWidget *_disableDitheringCheckbox;
StaticTextWidget *_renderModePopUpDesc;
PopUpWidget *_renderModePopUp;
@@ -183,6 +185,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 0000000000..ed18847a40
--- /dev/null
+++ b/gui/predictivedialog.cpp
@@ -0,0 +1,998 @@
+/* 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");
+
+ _btns = (ButtonWidget **)calloc(1, sizeof(ButtonWidget *) * 16);
+
+ _btns[kCancelAct] = new ButtonWidget(this, "Predictive.Cancel", _("Cancel") , 0, kCancelCmd);
+ _btns[kOkAct] = new ButtonWidget(this, "Predictive.OK", _("Ok") , 0, kOkCmd);
+ _btns[kBtn1Act] = new ButtonWidget(this, "Predictive.Button1", "1 `-.&" , 0, kBut1Cmd);
+ _btns[kBtn2Act] = new ButtonWidget(this, "Predictive.Button2", "2 abc" , 0, kBut2Cmd);
+ _btns[kBtn3Act] = new ButtonWidget(this, "Predictive.Button3", "3 def" , 0, kBut3Cmd);
+ _btns[kBtn4Act] = new ButtonWidget(this, "Predictive.Button4", "4 ghi" , 0, kBut4Cmd);
+ _btns[kBtn5Act] = new ButtonWidget(this, "Predictive.Button5", "5 jkl" , 0, kBut5Cmd);
+ _btns[kBtn6Act] = new ButtonWidget(this, "Predictive.Button6", "6 mno" , 0, kBut6Cmd);
+ _btns[kBtn7Act] = new ButtonWidget(this, "Predictive.Button7", "7 pqrs" , 0, kBut7Cmd);
+ _btns[kBtn8Act] = new ButtonWidget(this, "Predictive.Button8", "8 tuv" , 0, kBut8Cmd);
+ _btns[kBtn9Act] = new ButtonWidget(this, "Predictive.Button9", "9 wxyz" , 0, kBut9Cmd);
+ _btns[kBtn0Act] = new ButtonWidget(this, "Predictive.Button0", "0" , 0, kBut0Cmd);
+ // I18N: You must leave "#" as is, only word 'next' is translatable
+ _btns[kNextAct] = new ButtonWidget(this, "Predictive.Next", _("# next") , 0, kNextCmd);
+ _btns[kAddAct] = new ButtonWidget(this, "Predictive.Add", _("add") , 0, kAddCmd);
+ _btns[kAddAct]->setEnabled(false);
+
+ #ifndef DISABLE_FANCY_THEMES
+ _btns[kDelAct] = new PicButtonWidget(this, "Predictive.Delete", _("Delete char"), kDelCmd);
+ ((PicButtonWidget *)_btns[kDelAct])->useThemeTransparency(true);
+ ((PicButtonWidget *)_btns[kDelAct])->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDelbtn));
+ #endif
+ _btns[kDelAct] = new ButtonWidget(this, "Predictive.Delete" , _("<") , 0, kDelCmd);
+ // I18N: Pre means 'Predictive', leave '*' as is
+ _btns[kModeAct] = 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);
+
+ free(_btns);
+}
+
+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 (_currBtn != kNoAct && !_needRefresh) {
+ _btns[_currBtn]->startAnimatePressedState();
+ processBtnActive(_currBtn);
+ }
+}
+
+void PredictiveDialog::handleKeyDown(Common::KeyState state) {
+ _currBtn = kNoAct;
+ _needRefresh = false;
+
+ if (getFocusWidget() == _edittext) {
+ setFocusWidget(_btns[kAddAct]);
+ }
+
+ if (_lastbutton == kNoAct) {
+ _lastbutton = kBtn5Act;
+ }
+
+ switch (state.keycode) {
+ case Common::KEYCODE_ESCAPE:
+ saveUserDictToFile();
+ close();
+ return;
+ case Common::KEYCODE_LEFT:
+ _navigationwithkeys = true;
+ if (_lastbutton == kBtn1Act || _lastbutton == kBtn4Act || _lastbutton == kBtn7Act)
+ _currBtn = ButtonId(_lastbutton + 2);
+ else if (_lastbutton == kDelAct)
+ _currBtn = kBtn1Act;
+ else if (_lastbutton == kModeAct)
+ _currBtn = kNextAct;
+ else if (_lastbutton == kNextAct)
+ _currBtn = kBtn0Act;
+ else if (_lastbutton == kAddAct)
+ _currBtn = kOkAct;
+ else if (_lastbutton == kCancelAct)
+ _currBtn = kAddAct;
+ else
+ _currBtn = ButtonId(_lastbutton - 1);
+
+
+ if (_mode != kModeAbc && _lastbutton == kCancelAct)
+ _currBtn = kOkAct;
+
+ _needRefresh = true;
+ break;
+ case Common::KEYCODE_RIGHT:
+ _navigationwithkeys = true;
+ if (_lastbutton == kBtn3Act || _lastbutton == kBtn6Act || _lastbutton == kBtn9Act || _lastbutton == kOkAct)
+ _currBtn = ButtonId(_lastbutton - 2);
+ else if (_lastbutton == kDelAct)
+ _currBtn = kBtn3Act;
+ else if (_lastbutton == kBtn0Act)
+ _currBtn = kNextAct;
+ else if (_lastbutton == kNextAct)
+ _currBtn = kModeAct;
+ else if (_lastbutton == kAddAct)
+ _currBtn = kCancelAct;
+ else if (_lastbutton == kOkAct)
+ _currBtn = kAddAct;
+ else
+ _currBtn = ButtonId(_lastbutton + 1);
+
+ if (_mode != kModeAbc && _lastbutton == kOkAct)
+ _currBtn = kCancelAct;
+ _needRefresh = true;
+ break;
+ case Common::KEYCODE_UP:
+ _navigationwithkeys = true;
+ if (_lastbutton <= kBtn3Act)
+ _currBtn = kDelAct;
+ else if (_lastbutton == kDelAct)
+ _currBtn = kOkAct;
+ else if (_lastbutton == kModeAct)
+ _currBtn = kBtn7Act;
+ else if (_lastbutton == kBtn0Act)
+ _currBtn = kBtn8Act;
+ else if (_lastbutton == kNextAct)
+ _currBtn = kBtn9Act;
+ else if (_lastbutton == kAddAct)
+ _currBtn = kModeAct;
+ else if (_lastbutton == kCancelAct)
+ _currBtn = kBtn0Act;
+ else if (_lastbutton == kOkAct)
+ _currBtn = kNextAct;
+ else
+ _currBtn = ButtonId(_lastbutton - 3);
+ _needRefresh = true;
+ break;
+ case Common::KEYCODE_DOWN:
+ _navigationwithkeys = true;
+ if (_lastbutton == kDelAct)
+ _currBtn = kBtn3Act;
+ else if (_lastbutton == kBtn7Act)
+ _currBtn = kModeAct;
+ else if (_lastbutton == kBtn8Act)
+ _currBtn = kBtn0Act;
+ else if (_lastbutton == kBtn9Act)
+ _currBtn = kNextAct;
+ else if (_lastbutton == kModeAct)
+ _currBtn = kAddAct;
+ else if (_lastbutton == kBtn0Act)
+ _currBtn = kCancelAct;
+ else if (_lastbutton == kNextAct)
+ _currBtn = kOkAct;
+ else if (_lastbutton == kAddAct || _lastbutton == kCancelAct || _lastbutton == kOkAct)
+ _currBtn = kDelAct;
+ else
+ _currBtn = ButtonId(_lastbutton + 3);
+
+ if (_mode != kModeAbc && _lastbutton == kModeAct)
+ _currBtn = kCancelAct;
+
+ _needRefresh = true;
+ break;
+ case Common::KEYCODE_KP_ENTER:
+ case Common::KEYCODE_RETURN:
+ if (state.flags & Common::KBD_CTRL) {
+ _currBtn = kOkAct;
+ break;
+ }
+ if (_navigationwithkeys) {
+ // when the user has utilized arrow key navigation,
+ // interpret enter as 'click' on the _currBtn button
+ _currBtn = _lastbutton;
+ _needRefresh = false;
+ } else {
+ // else it is a shortcut for 'Ok'
+ _currBtn = kOkAct;
+ }
+ break;
+ case Common::KEYCODE_KP_PLUS:
+ _currBtn = kAddAct;
+ break;
+ case Common::KEYCODE_BACKSPACE:
+ case Common::KEYCODE_KP_MINUS:
+ _currBtn = kDelAct;
+ break;
+ case Common::KEYCODE_KP_DIVIDE:
+ _currBtn = kNextAct;
+ break;
+ case Common::KEYCODE_KP_MULTIPLY:
+ _currBtn = kModeAct;
+ break;
+ case Common::KEYCODE_KP0:
+ _currBtn = 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:
+ _currBtn = ButtonId(state.keycode - Common::KEYCODE_KP1);
+ break;
+ default:
+ Dialog::handleKeyDown(state);
+ }
+
+ if (_lastbutton != _currBtn)
+ _btns[_lastbutton]->stopAnimatePressedState();
+
+ if (_currBtn != kNoAct && !_needRefresh)
+ _btns[_currBtn]->setPressedState();
+ else
+ updateHighLightedButton(_currBtn);
+}
+
+void PredictiveDialog::updateHighLightedButton(ButtonId act) {
+ if (_currBtn != kNoAct) {
+ _btns[_lastbutton]->setHighLighted(false);
+ _lastbutton = act;
+ _btns[_lastbutton]->setHighLighted(true);
+ }
+}
+
+void PredictiveDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ _currBtn = kNoAct;
+
+ _navigationwithkeys = false;
+
+ if (_lastbutton != kNoAct)
+ _btns[_lastbutton]->setHighLighted(false);
+
+ switch (cmd) {
+ case kDelCmd:
+ _currBtn = kDelAct;
+ break;
+ case kNextCmd:
+ _currBtn = kNextAct;
+ break;
+ case kAddCmd:
+ _currBtn = kAddAct;
+ break;
+ case kModeCmd:
+ _currBtn = kModeAct;
+ break;
+ case kBut1Cmd:
+ _currBtn = kBtn1Act;
+ break;
+ case kBut2Cmd:
+ _currBtn = kBtn2Act;
+ break;
+ case kBut3Cmd:
+ _currBtn = kBtn3Act;
+ break;
+ case kBut4Cmd:
+ _currBtn = kBtn4Act;
+ break;
+ case kBut5Cmd:
+ _currBtn = kBtn5Act;
+ break;
+ case kBut6Cmd:
+ _currBtn = kBtn6Act;
+ break;
+ case kBut7Cmd:
+ _currBtn = kBtn7Act;
+ break;
+ case kBut8Cmd:
+ _currBtn = kBtn8Act;
+ break;
+ case kBut9Cmd:
+ _currBtn = kBtn9Act;
+ break;
+ case kBut0Cmd:
+ _currBtn = kBtn0Act;
+ break;
+ case kCancelCmd:
+ saveUserDictToFile();
+ close();
+ return;
+ case kOkCmd:
+ _currBtn = kOkAct;
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+
+ if (_currBtn != kNoAct) {
+ processBtnActive(_currBtn);
+ }
+}
+
+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++;
+ _btns[kAddAct]->setEnabled(false);
+ if (_mode > kModeAbc) {
+ _mode = kModePre;
+ // I18N: Pre means 'Predictive', leave '*' as is
+ _btns[kModeAct]->setLabel("* Pre");
+ } else if (_mode == kModeNum) {
+ // I18N: 'Num' means Numbers
+ _btns[kModeAct]->setLabel("* Num");
+ } else {
+ // I18N: 'Abc' means Latin alphabet input
+ _btns[kModeAct]->setLabel("* Abc");
+ _btns[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;
+ _lastPressBtn = kNoAct;
+ _curTime = 0;
+ }
+ }
+
+ pressEditText();
+
+ if (button == kOkAct)
+ close();
+
+ if (button == kCancelAct) {
+ saveUserDictToFile();
+ close();
+ }
+}
+
+void PredictiveDialog::handleTickle() {
+ if (_lastTime) {
+ if ((_curTime - _lastTime) > kRepeatDelay) {
+ _lastTime = 0;
+ }
+ }
+
+ if (getTickleWidget()) {
+ getTickleWidget()->handleTickle();
+ }
+}
+
+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<Common::String> 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 ex_currBtnly 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
diff --git a/gui/predictivedialog.h b/gui/predictivedialog.h
new file mode 100644
index 0000000000..0e3d2967c0
--- /dev/null
+++ b/gui/predictivedialog.h
@@ -0,0 +1,143 @@
+/* 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 {
+ kMaxLineLen = 80,
+ kMaxWordLen = 24,
+ kMaxWord = 50
+};
+
+class PredictiveDialog : public GUI::Dialog {
+public:
+ PredictiveDialog();
+ ~PredictiveDialog();
+
+ virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleKeyUp(Common::KeyState state);
+ virtual void handleKeyDown(Common::KeyState state);
+ virtual void handleTickle();
+
+ const char *getResult() const { return _predictiveResult; }
+private:
+ struct Dict {
+ char **dictLine;
+ char *dictText;
+ char *dictActLine; // using only for united dict...
+ int32 dictLineCount;
+ int32 dictTextSize;
+ Common::String nameDict;
+ Common::String fnameDict;
+ };
+
+ uint8 countWordsInString(const char *const 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 Common::String &word, const Common::String &code);
+ bool searchWord(const char *const where, const Common::String &whatCode);
+ int binarySearch(const char *const *const dictLine, const Common::String &code, const int dictLineCount);
+ bool matchWord();
+ void processBtnActive(ButtonId active);
+ void pressEditText();
+
+ void saveUserDictToFile();
+
+ void mergeDicts();
+
+ void updateHighLightedButton(ButtonId active);
+private:
+ Dict _unitedDict;
+ Dict _predictiveDict;
+ Dict _userDict;
+
+ int _mode;
+ ButtonId _lastbutton;
+
+ bool _userDictHasChanged;
+
+ int _wordNumber;
+ uint8 _numMatchingWords;
+ char _predictiveResult[40];
+
+ Common::String _currentCode;
+ Common::String _currentWord;
+ Common::String _prefix;
+
+ uint32 _curTime, _lastTime;
+ ButtonId _lastPressBtn;
+ ButtonId _currBtn;
+
+ char _temp[kMaxWordLen + 1];
+ int _repeatcount[kMaxWordLen];
+
+ char *_memoryList[kMaxWord];
+ int _numMemory;
+
+ Common::String _search;
+
+ bool _navigationwithkeys;
+ bool _needRefresh;
+private:
+ EditTextWidget *_edittext;
+ ButtonWidget **_btns;
+};
+
+} // namespace GUI
+
+#endif
diff --git a/gui/saveload-dialog.cpp b/gui/saveload-dialog.cpp
new file mode 100644
index 0000000000..df8dda7470
--- /dev/null
+++ b/gui/saveload-dialog.cpp
@@ -0,0 +1,946 @@
+/* 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/saveload-dialog.h"
+#include "common/translation.h"
+#include "common/config-manager.h"
+
+#include "gui/message.h"
+#include "gui/gui-manager.h"
+#include "gui/ThemeEval.h"
+#include "gui/widgets/edittext.h"
+
+#include "graphics/scaler.h"
+
+namespace GUI {
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) {
+ const Common::String &userConfig = ConfMan.get("gui_saveload_chooser", Common::ConfigManager::kApplicationDomain);
+ if (g_gui.getWidth() >= 640 && g_gui.getHeight() >= 400
+ && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo)
+ && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail)
+ && userConfig.equalsIgnoreCase("grid")) {
+ // In case we are 640x400 or higher, this dialog is not in save mode,
+ // the user requested the grid dialog and the engines supports it we
+ // try to set it up.
+ return kSaveLoadDialogGrid;
+ } else {
+ // In all other cases we want to use the list dialog.
+ return kSaveLoadDialogList;
+ }
+}
+
+enum {
+ kListSwitchCmd = 'LIST',
+ kGridSwitchCmd = 'GRID'
+};
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode)
+ : Dialog(dialogName), _metaEngine(0), _delSupport(false), _metaInfoSupport(false),
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode)
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ , _listButton(0), _gridButton(0)
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+ {
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ addChooserButtons();
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+}
+
+SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode)
+ : Dialog(x, y, w, h), _metaEngine(0), _delSupport(false), _metaInfoSupport(false),
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode)
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ , _listButton(0), _gridButton(0)
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+ {
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ addChooserButtons();
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+}
+
+void SaveLoadChooserDialog::open() {
+ Dialog::open();
+
+ // So that quitting ScummVM will not cause the dialog result to say a
+ // savegame was selected.
+ setResult(-1);
+}
+
+int SaveLoadChooserDialog::run(const Common::String &target, const MetaEngine *metaEngine) {
+ _metaEngine = metaEngine;
+ _target = target;
+ _delSupport = _metaEngine->hasFeature(MetaEngine::kSupportsDeleteSave);
+ _metaInfoSupport = _metaEngine->hasFeature(MetaEngine::kSavesSupportMetaInfo);
+ _thumbnailSupport = _metaInfoSupport && _metaEngine->hasFeature(MetaEngine::kSavesSupportThumbnail);
+ _saveDateSupport = _metaInfoSupport && _metaEngine->hasFeature(MetaEngine::kSavesSupportCreationDate);
+ _playTimeSupport = _metaInfoSupport && _metaEngine->hasFeature(MetaEngine::kSavesSupportPlayTime);
+
+ return runIntern();
+}
+
+void SaveLoadChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ switch (cmd) {
+ case kListSwitchCmd:
+ setResult(kSwitchSaveLoadDialog);
+ // We save the requested dialog type here to avoid the setting to be
+ // overwritten when our reflowLayout logic selects a different dialog
+ // type.
+ ConfMan.set("gui_saveload_chooser", "list", Common::ConfigManager::kApplicationDomain);
+ close();
+ break;
+
+ case kGridSwitchCmd:
+ setResult(kSwitchSaveLoadDialog);
+ // See above.
+ ConfMan.set("gui_saveload_chooser", "grid", Common::ConfigManager::kApplicationDomain);
+ close();
+ break;
+
+ default:
+ break;
+ }
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+ return Dialog::handleCommand(sender, cmd, data);
+}
+
+void SaveLoadChooserDialog::reflowLayout() {
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ addChooserButtons();
+
+ const SaveLoadChooserType currentType = getType();
+ const SaveLoadChooserType requestedType = getRequestedSaveLoadDialog(*_metaEngine);
+
+ // Change the dialog type if there is any need for it.
+ if (requestedType != currentType) {
+ setResult(kSwitchSaveLoadDialog);
+ close();
+ }
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+ Dialog::reflowLayout();
+}
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+void SaveLoadChooserDialog::addChooserButtons() {
+ if (_listButton) {
+ removeWidget(_listButton);
+ delete _listButton;
+ }
+
+ if (_gridButton) {
+ removeWidget(_gridButton);
+ delete _gridButton;
+ }
+
+ _listButton = createSwitchButton("SaveLoadChooser.ListSwitch", "L", _("List view"), ThemeEngine::kImageList, kListSwitchCmd);
+ _gridButton = createSwitchButton("SaveLoadChooser.GridSwitch", "G", _("Grid view"), ThemeEngine::kImageGrid, kGridSwitchCmd);
+ if (!_metaInfoSupport || !_thumbnailSupport || !(g_gui.getWidth() >= 640 && g_gui.getHeight() >= 400)) {
+ _gridButton->setEnabled(false);
+ _listButton->setEnabled(false);
+ }
+}
+
+ButtonWidget *SaveLoadChooserDialog::createSwitchButton(const Common::String &name, const char *desc, const char *tooltip, const char *image, uint32 cmd) {
+ ButtonWidget *button;
+
+#ifndef DISABLE_FANCY_THEMES
+ if (g_gui.xmlEval()->getVar("Globals.ShowChooserPics") == 1 && g_gui.theme()->supportsImages()) {
+ button = new PicButtonWidget(this, name, tooltip, cmd);
+ ((PicButtonWidget *)button)->useThemeTransparency(true);
+ ((PicButtonWidget *)button)->setGfx(g_gui.theme()->getImageSurface(image));
+ } else
+#endif
+ button = new ButtonWidget(this, name, desc, tooltip, cmd);
+
+ return button;
+}
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+// SaveLoadChooserSimple implementation
+
+enum {
+ kChooseCmd = 'CHOS',
+ kDelCmd = 'DEL '
+};
+
+SaveLoadChooserSimple::SaveLoadChooserSimple(const String &title, const String &buttonLabel, bool saveMode)
+ : SaveLoadChooserDialog("SaveLoadChooser", saveMode), _list(0), _chooseButton(0), _deleteButton(0), _gfxWidget(0) {
+ _backgroundType = ThemeEngine::kDialogBackgroundSpecial;
+
+ new StaticTextWidget(this, "SaveLoadChooser.Title", title);
+
+ // Add choice list
+ _list = new ListWidget(this, "SaveLoadChooser.List");
+ _list->setNumberingMode(kListNumberingZero);
+ _list->setEditable(saveMode);
+
+ _gfxWidget = new GraphicsWidget(this, 0, 0, 10, 10);
+
+ _date = new StaticTextWidget(this, 0, 0, 10, 10, _("No date saved"), Graphics::kTextAlignCenter);
+ _time = new StaticTextWidget(this, 0, 0, 10, 10, _("No time saved"), Graphics::kTextAlignCenter);
+ _playtime = new StaticTextWidget(this, 0, 0, 10, 10, _("No playtime saved"), Graphics::kTextAlignCenter);
+
+ // Buttons
+ new ButtonWidget(this, "SaveLoadChooser.Cancel", _("Cancel"), 0, kCloseCmd);
+ _chooseButton = new ButtonWidget(this, "SaveLoadChooser.Choose", buttonLabel, 0, kChooseCmd);
+ _chooseButton->setEnabled(false);
+
+ _deleteButton = new ButtonWidget(this, "SaveLoadChooser.Delete", _("Delete"), 0, kDelCmd);
+ _deleteButton->setEnabled(false);
+
+ _delSupport = _metaInfoSupport = _thumbnailSupport = false;
+
+ _container = new ContainerWidget(this, 0, 0, 10, 10);
+// _container->setHints(THEME_HINT_USE_SHADOW);
+}
+
+int SaveLoadChooserSimple::runIntern() {
+ if (_gfxWidget)
+ _gfxWidget->setGfx(0);
+
+ _resultString.clear();
+ reflowLayout();
+ updateSaveList();
+
+ return Dialog::runModal();
+}
+
+const Common::String &SaveLoadChooserSimple::getResultString() const {
+ int selItem = _list->getSelected();
+ return (selItem >= 0) ? _list->getSelectedString() : _resultString;
+}
+
+void SaveLoadChooserSimple::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ int selItem = _list->getSelected();
+
+ switch (cmd) {
+ case kListItemActivatedCmd:
+ case kListItemDoubleClickedCmd:
+ if (selItem >= 0 && _chooseButton->isEnabled()) {
+ if (_list->isEditable() || !_list->getSelectedString().empty()) {
+ _list->endEditMode();
+ if (!_saveList.empty()) {
+ setResult(_saveList[selItem].getSaveSlot());
+ _resultString = _list->getSelectedString();
+ }
+ close();
+ }
+ }
+ break;
+ case kChooseCmd:
+ _list->endEditMode();
+ if (!_saveList.empty()) {
+ setResult(_saveList[selItem].getSaveSlot());
+ _resultString = _list->getSelectedString();
+ }
+ close();
+ break;
+ case kListSelectionChangedCmd:
+ updateSelection(true);
+ break;
+ case kDelCmd:
+ if (selItem >= 0 && _delSupport) {
+ MessageDialog alert(_("Do you really want to delete this savegame?"),
+ _("Delete"), _("Cancel"));
+ if (alert.runModal() == kMessageOK) {
+ _metaEngine->removeSaveState(_target.c_str(), _saveList[selItem].getSaveSlot());
+
+ setResult(-1);
+ _list->setSelected(-1);
+
+ updateSaveList();
+ updateSelection(true);
+ }
+ }
+ break;
+ case kCloseCmd:
+ setResult(-1);
+ default:
+ SaveLoadChooserDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void SaveLoadChooserSimple::reflowLayout() {
+ if (g_gui.xmlEval()->getVar("Globals.SaveLoadChooser.ExtInfo.Visible") == 1 && _thumbnailSupport) {
+ int16 x, y;
+ uint16 w, h;
+
+ if (!g_gui.xmlEval()->getWidgetData("SaveLoadChooser.Thumbnail", x, y, w, h))
+ error("Error when loading position data for Save/Load Thumbnails");
+
+ int thumbW = kThumbnailWidth;
+ int thumbH = kThumbnailHeight2;
+ int thumbX = x + (w >> 1) - (thumbW >> 1);
+ int thumbY = y + kLineHeight;
+
+ int textLines = 0;
+ if (!_saveDateSupport)
+ textLines++;
+ if (!_playTimeSupport)
+ textLines++;
+
+ _container->resize(x, y, w, h - (kLineHeight * textLines));
+ _gfxWidget->resize(thumbX, thumbY, thumbW, thumbH);
+
+ int height = thumbY + thumbH + kLineHeight;
+
+ if (_saveDateSupport) {
+ _date->resize(thumbX, height, kThumbnailWidth, kLineHeight);
+ height += kLineHeight;
+ _time->resize(thumbX, height, kThumbnailWidth, kLineHeight);
+ height += kLineHeight;
+ }
+
+ if (_playTimeSupport)
+ _playtime->resize(thumbX, height, kThumbnailWidth, kLineHeight);
+
+ _container->setVisible(true);
+ _gfxWidget->setVisible(true);
+
+ _date->setVisible(_saveDateSupport);
+ _time->setVisible(_saveDateSupport);
+
+ _playtime->setVisible(_playTimeSupport);
+
+ updateSelection(false);
+ } else {
+ _container->setVisible(false);
+ _gfxWidget->setVisible(false);
+ _date->setVisible(false);
+ _time->setVisible(false);
+ _playtime->setVisible(false);
+ }
+
+ SaveLoadChooserDialog::reflowLayout();
+}
+
+void SaveLoadChooserSimple::updateSelection(bool redraw) {
+ int selItem = _list->getSelected();
+
+ bool isDeletable = _delSupport;
+ bool isWriteProtected = false;
+ bool startEditMode = _list->isEditable();
+
+ // We used to support letting the themes specify the fill color with our
+ // initial theme based GUI. But this support was dropped.
+ _gfxWidget->setGfx(-1, -1, 0, 0, 0);
+ _date->setLabel(_("No date saved"));
+ _time->setLabel(_("No time saved"));
+ _playtime->setLabel(_("No playtime saved"));
+
+ if (selItem >= 0 && _metaInfoSupport) {
+ SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot());
+
+ isDeletable = desc.getDeletableFlag() && _delSupport;
+ isWriteProtected = desc.getWriteProtectedFlag();
+
+ // Don't allow the user to change the description of write protected games
+ if (isWriteProtected)
+ startEditMode = false;
+
+ if (_thumbnailSupport) {
+ const Graphics::Surface *thumb = desc.getThumbnail();
+ if (thumb) {
+ _gfxWidget->setGfx(thumb);
+ _gfxWidget->useAlpha(256);
+ }
+ }
+
+ if (_saveDateSupport) {
+ const Common::String &saveDate = desc.getSaveDate();
+ if (!saveDate.empty())
+ _date->setLabel(_("Date: ") + saveDate);
+
+ const Common::String &saveTime = desc.getSaveTime();
+ if (!saveTime.empty())
+ _time->setLabel(_("Time: ") + saveTime);
+ }
+
+ if (_playTimeSupport) {
+ const Common::String &playTime = desc.getPlayTime();
+ if (!playTime.empty())
+ _playtime->setLabel(_("Playtime: ") + playTime);
+ }
+ }
+
+
+ if (_list->isEditable()) {
+ // Disable the save button if nothing is selected, or if the selected
+ // game is write protected
+ _chooseButton->setEnabled(selItem >= 0 && !isWriteProtected);
+
+ if (startEditMode) {
+ _list->startEditMode();
+
+ if (_chooseButton->isEnabled() && _list->getSelectedString() == _("Untitled savestate") &&
+ _list->getSelectionColor() == ThemeEngine::kFontColorAlternate) {
+ _list->setEditString("");
+ _list->setEditColor(ThemeEngine::kFontColorNormal);
+ }
+ }
+ } else {
+ // Disable the load button if nothing is selected, or if an empty
+ // list item is selected.
+ _chooseButton->setEnabled(selItem >= 0 && !_list->getSelectedString().empty());
+ }
+
+ // Delete will always be disabled if the engine doesn't support it.
+ _deleteButton->setEnabled(isDeletable && (selItem >= 0) && (!_list->getSelectedString().empty()));
+
+ if (redraw) {
+ _gfxWidget->draw();
+ _date->draw();
+ _time->draw();
+ _playtime->draw();
+ _chooseButton->draw();
+ _deleteButton->draw();
+
+ draw();
+ }
+}
+
+void SaveLoadChooserSimple::open() {
+ SaveLoadChooserDialog::open();
+
+ // Scroll the list to the last used entry.
+ _list->scrollTo(ConfMan.getInt("gui_saveload_last_pos"));
+}
+
+void SaveLoadChooserSimple::close() {
+ // Save the current scroll position/used entry.
+ const int result = getResult();
+ if (result >= 0) {
+ ConfMan.setInt("gui_saveload_last_pos", result);
+ } else {
+ // Use the current scroll position here.
+ // TODO: This means we canceled the dialog (or switch to the grid). Do
+ // we want to save this position here? Does the user want that?
+ // TODO: Do we want to save the current scroll position or the
+ // currently selected item here? The scroll position is what the user
+ // currently sees and seems to make more sense.
+ ConfMan.setInt("gui_saveload_last_pos", _list->getCurrentScrollPos());
+ }
+
+ _metaEngine = 0;
+ _target.clear();
+ _saveList.clear();
+ _list->setList(StringArray());
+
+ SaveLoadChooserDialog::close();
+}
+
+void SaveLoadChooserSimple::updateSaveList() {
+ _saveList = _metaEngine->listSaves(_target.c_str());
+
+ int curSlot = 0;
+ int saveSlot = 0;
+ StringArray saveNames;
+ ListWidget::ColorList colors;
+ for (SaveStateList::const_iterator x = _saveList.begin(); x != _saveList.end(); ++x) {
+ // Handle gaps in the list of save games
+ saveSlot = x->getSaveSlot();
+ if (curSlot < saveSlot) {
+ while (curSlot < saveSlot) {
+ SaveStateDescriptor dummySave(curSlot, "");
+ _saveList.insert_at(curSlot, dummySave);
+ saveNames.push_back(dummySave.getDescription());
+ colors.push_back(ThemeEngine::kFontColorNormal);
+ curSlot++;
+ }
+
+ // Sync the save list iterator
+ for (x = _saveList.begin(); x != _saveList.end(); ++x) {
+ if (x->getSaveSlot() == saveSlot)
+ break;
+ }
+ }
+
+ // Show "Untitled savestate" for empty/whitespace savegame descriptions
+ Common::String description = x->getDescription();
+ Common::String trimmedDescription = description;
+ trimmedDescription.trim();
+ if (trimmedDescription.empty()) {
+ description = _("Untitled savestate");
+ colors.push_back(ThemeEngine::kFontColorAlternate);
+ } else {
+ colors.push_back(ThemeEngine::kFontColorNormal);
+ }
+
+ saveNames.push_back(description);
+ curSlot++;
+ }
+
+ // Fill the rest of the save slots with empty saves
+
+ int maximumSaveSlots = _metaEngine->getMaximumSaveSlot();
+
+#ifdef __DS__
+ // Low memory on the DS means too many save slots are impractical, so limit
+ // the maximum here.
+ if (maximumSaveSlots > 99) {
+ maximumSaveSlots = 99;
+ }
+#endif
+
+ Common::String emptyDesc;
+ for (int i = curSlot; i <= maximumSaveSlots; i++) {
+ saveNames.push_back(emptyDesc);
+ SaveStateDescriptor dummySave(i, "");
+ _saveList.push_back(dummySave);
+ colors.push_back(ThemeEngine::kFontColorNormal);
+ }
+
+ _list->setList(saveNames, &colors);
+}
+
+// SaveLoadChooserGrid implementation
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+
+enum {
+ kNextCmd = 'NEXT',
+ kPrevCmd = 'PREV',
+ kNewSaveCmd = 'SAVE'
+};
+
+SaveLoadChooserGrid::SaveLoadChooserGrid(const Common::String &title, bool saveMode)
+ : SaveLoadChooserDialog("SaveLoadChooser", saveMode), _lines(0), _columns(0), _entriesPerPage(0),
+ _curPage(0), _newSaveContainer(0), _nextFreeSaveSlot(0), _buttons() {
+ _backgroundType = ThemeEngine::kDialogBackgroundSpecial;
+
+ new StaticTextWidget(this, "SaveLoadChooser.Title", title);
+
+ // Buttons
+ new ButtonWidget(this, "SaveLoadChooser.Delete", _("Cancel"), 0, kCloseCmd);
+ _nextButton = new ButtonWidget(this, "SaveLoadChooser.Choose", _("Next"), 0, kNextCmd);
+ _nextButton->setEnabled(false);
+
+ _prevButton = new ButtonWidget(this, "SaveLoadChooser.Cancel", _("Prev"), 0, kPrevCmd);
+ _prevButton->setEnabled(false);
+
+ // Page display
+ _pageDisplay = new StaticTextWidget(this, "SaveLoadChooser.PageDisplay", Common::String());
+ _pageDisplay->setAlign(Graphics::kTextAlignRight);
+}
+
+SaveLoadChooserGrid::~SaveLoadChooserGrid() {
+ removeWidget(_pageDisplay);
+ delete _pageDisplay;
+}
+
+const Common::String &SaveLoadChooserGrid::getResultString() const {
+ return _resultString;
+}
+
+void SaveLoadChooserGrid::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ if (cmd <= _entriesPerPage && cmd + _curPage * _entriesPerPage <= _saveList.size()) {
+ const SaveStateDescriptor &desc = _saveList[cmd - 1 + _curPage * _entriesPerPage];
+
+ if (_saveMode) {
+ _resultString = desc.getDescription();
+ }
+
+ setResult(desc.getSaveSlot());
+ close();
+ }
+
+ switch (cmd) {
+ case kNextCmd:
+ ++_curPage;
+ updateSaves();
+ draw();
+ break;
+
+ case kPrevCmd:
+ --_curPage;
+ updateSaves();
+ draw();
+ break;
+
+ case kNewSaveCmd:
+ setResult(_nextFreeSaveSlot);
+ close();
+ break;
+
+ case kCloseCmd:
+ setResult(-1);
+ default:
+ SaveLoadChooserDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void SaveLoadChooserGrid::handleMouseWheel(int x, int y, int direction) {
+ if (direction > 0) {
+ if (_nextButton->isEnabled()) {
+ ++_curPage;
+ updateSaves();
+ draw();
+ }
+ } else {
+ if (_prevButton->isEnabled()) {
+ --_curPage;
+ updateSaves();
+ draw();
+ }
+ }
+}
+
+void SaveLoadChooserGrid::open() {
+ SaveLoadChooserDialog::open();
+
+ _saveList = _metaEngine->listSaves(_target.c_str());
+ _resultString.clear();
+
+ // Load information to restore the last page the user had open.
+ assert(_entriesPerPage != 0);
+ const uint lastPos = ConfMan.getInt("gui_saveload_last_pos");
+ const uint listSize = _saveList.size();
+ uint bestMatch = 0;
+ uint diff = 0xFFFFFFFF;
+
+ // We look for the nearest available slot, since a slot might be missing
+ // due to the user deleting it via the list based chooser, by deleting
+ // it by hand, etc.
+ for (uint i = 0; i < listSize; ++i) {
+ uint curDiff = ABS(_saveList[i].getSaveSlot() - (int)lastPos);
+ if (curDiff < diff) {
+ diff = curDiff;
+ bestMatch = i;
+ }
+ }
+
+ _curPage = bestMatch / _entriesPerPage;
+
+ // Determine the next free save slot for save mode
+ if (_saveMode) {
+ int lastSlot = -1;
+ _nextFreeSaveSlot = -1;
+ for (SaveStateList::const_iterator x = _saveList.begin(); x != _saveList.end(); ++x) {
+ const int curSlot = x->getSaveSlot();
+
+ // In case there was a gap found use the slot.
+ if (lastSlot + 1 < curSlot) {
+ _nextFreeSaveSlot = lastSlot + 1;
+ break;
+ }
+
+ lastSlot = curSlot;
+ }
+
+ // Use the next available slot otherwise.
+ if (_nextFreeSaveSlot == -1 && lastSlot + 1 < _metaEngine->getMaximumSaveSlot()) {
+ _nextFreeSaveSlot = lastSlot + 1;
+ }
+ }
+
+ updateSaves();
+}
+
+void SaveLoadChooserGrid::reflowLayout() {
+ // HACK: The page display is not available in low resolution layout. We
+ // remove and readd the widget here to avoid our GUI from erroring out.
+ removeWidget(_pageDisplay);
+ if (g_gui.xmlEval()->getVar("Globals.ShowChooserPageDisplay") == 1) {
+ _pageDisplay->init();
+ }
+
+ SaveLoadChooserDialog::reflowLayout();
+ destroyButtons();
+
+ // HACK: The whole code below really works around the fact, that we have
+ // no easy way to dynamically layout widgets.
+ const uint16 availableWidth = getWidth() - 20;
+ uint16 availableHeight;
+
+ int16 x, y;
+ uint16 w;
+ g_gui.xmlEval()->getWidgetData("SaveLoadChooser.List", x, y, w, availableHeight);
+
+ const int16 buttonWidth = kThumbnailWidth + 6;
+ const int16 buttonHeight = kThumbnailHeight2 + 6;
+
+ const int16 containerFrameWidthAdd = 10;
+ const int16 containerFrameHeightAdd = 0;
+ const int16 containerWidth = buttonWidth + containerFrameWidthAdd;
+ const int16 containerHeight = buttonHeight + kLineHeight + containerFrameHeightAdd;
+
+ const int16 defaultSpacingHorizontal = 4;
+ const int16 defaultSpacingVertical = 8;
+ const int16 slotAreaWidth = containerWidth + defaultSpacingHorizontal;
+ const int16 slotAreaHeight = containerHeight + defaultSpacingVertical;
+
+ const uint oldEntriesPerPage = _entriesPerPage;
+ _columns = MAX<uint>(1, availableWidth / slotAreaWidth);
+ _lines = MAX<uint>(1, availableHeight / slotAreaHeight);
+ _entriesPerPage = _columns * _lines;
+
+ // In save mode the first button is always "New Save", thus we need to
+ // adjust the entries per page here.
+ if (_saveMode) {
+ --_entriesPerPage;
+ }
+
+ // Recalculate the page number
+ if (!_saveList.empty() && oldEntriesPerPage != 0) {
+ if (_entriesPerPage != 0) {
+ _curPage = (_curPage * oldEntriesPerPage) / _entriesPerPage;
+ } else {
+ _curPage = 0;
+ }
+ }
+
+ const uint addX = _columns > 1 ? (availableWidth % slotAreaWidth) / (_columns - 1) : 0;
+ //const uint addY = _lines > 1 ? (availableHeight % slotAreaHeight) / (_lines - 1) : 0;
+
+ _buttons.reserve(_lines * _columns);
+ y += defaultSpacingVertical / 2;
+ for (uint curLine = 0; curLine < _lines; ++curLine, y += slotAreaHeight/* + addY*/) {
+ for (uint curColumn = 0, curX = x + defaultSpacingHorizontal / 2; curColumn < _columns; ++curColumn, curX += slotAreaWidth + addX) {
+ int dstY = containerFrameHeightAdd / 2;
+ int dstX = containerFrameWidthAdd / 2;
+
+ // In the save mode we will always create a new save button as the first button.
+ if (_saveMode && curLine == 0 && curColumn == 0) {
+ _newSaveContainer = new ContainerWidget(this, curX, y, containerWidth, containerHeight);
+ ButtonWidget *newSave = new ButtonWidget(_newSaveContainer, dstX, dstY, buttonWidth, buttonHeight, _("New Save"), _("Create a new save game"), kNewSaveCmd);
+ // In case no more slots are free, we will disable the new save button
+ if (_nextFreeSaveSlot == -1) {
+ newSave->setEnabled(false);
+ }
+ continue;
+ }
+
+ ContainerWidget *container = new ContainerWidget(this, curX, y, containerWidth, containerHeight);
+ container->setVisible(false);
+
+ // Command 0 cannot be used, since it won't be send. Thus we will adjust
+ // command number here, if required. This is only the case for load mode
+ // since for save mode, the first button used is index 1 anyway.
+ uint buttonCmd = curLine * _columns + curColumn;
+ if (!_saveMode) {
+ buttonCmd += 1;
+ }
+
+ PicButtonWidget *button = new PicButtonWidget(container, dstX, dstY, buttonWidth, buttonHeight, 0, buttonCmd);
+ dstY += buttonHeight;
+
+ StaticTextWidget *description = new StaticTextWidget(container, dstX, dstY, buttonWidth, kLineHeight, Common::String(), Graphics::kTextAlignLeft);
+
+ _buttons.push_back(SlotButton(container, button, description));
+ }
+ }
+
+ if (!_target.empty())
+ updateSaves();
+}
+
+void SaveLoadChooserGrid::close() {
+ // Save the current page.
+ const int result = getResult();
+ if (result >= 0 && result != _nextFreeSaveSlot) {
+ // If the user selected a slot we use that one. We ignore new slots
+ // here, since otherwise the dialog would reset to page 0 when the
+ // user cancels the savename dialog.
+ ConfMan.setInt("gui_saveload_last_pos", result);
+ } else {
+ // Otherwise save the first entry on the current page.
+ // This is less precise than the solution above, since the number of
+ // entries shown differs between save and load version of the dialog,
+ // thus it might wrap to a different page than expected.
+ // Similar things happen on resolution changes.
+ // TODO: Should we ignore this here? Is the user likely to be
+ // interested in having this page restored when he canceled?
+ ConfMan.setInt("gui_saveload_last_pos", !_saveList.empty() ? _saveList[_curPage * _entriesPerPage].getSaveSlot() : 0);
+ }
+
+ SaveLoadChooserDialog::close();
+ hideButtons();
+}
+
+int SaveLoadChooserGrid::runIntern() {
+ int slot;
+ do {
+ const SaveLoadChooserType currentType = getType();
+ const SaveLoadChooserType requestedType = getRequestedSaveLoadDialog(*_metaEngine);
+
+ // Catch resolution changes when the save name dialog was open.
+ if (currentType != requestedType) {
+ setResult(kSwitchSaveLoadDialog);
+ return kSwitchSaveLoadDialog;
+ }
+
+ slot = runModal();
+ } while (_saveMode && slot >= 0 && !selectDescription());
+
+ // Special case for new save games. We need to handle this here, since
+ // we cannot handle it in close() without problems.
+ if (slot == _nextFreeSaveSlot) {
+ ConfMan.setInt("gui_saveload_last_pos", slot);
+ }
+
+ return slot;
+}
+
+bool SaveLoadChooserGrid::selectDescription() {
+ _savenameDialog.setDescription(_resultString);
+ _savenameDialog.setTargetSlot(getResult());
+ if (_savenameDialog.runModal() == 0) {
+ _resultString = _savenameDialog.getDescription();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void SaveLoadChooserGrid::destroyButtons() {
+ if (_newSaveContainer) {
+ removeWidget(_newSaveContainer);
+ delete _newSaveContainer;
+ _newSaveContainer = 0;
+ }
+
+ for (ButtonArray::iterator i = _buttons.begin(), end = _buttons.end(); i != end; ++i) {
+ removeWidget(i->container);
+ delete i->container;
+ }
+
+ _buttons.clear();
+}
+
+void SaveLoadChooserGrid::hideButtons() {
+ for (ButtonArray::iterator i = _buttons.begin(), end = _buttons.end(); i != end; ++i) {
+ i->button->setGfx(0);
+ i->setVisible(false);
+ }
+}
+
+void SaveLoadChooserGrid::updateSaves() {
+ hideButtons();
+
+ for (uint i = _curPage * _entriesPerPage, curNum = 0; i < _saveList.size() && curNum < _entriesPerPage; ++i, ++curNum) {
+ const uint saveSlot = _saveList[i].getSaveSlot();
+
+ SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot);
+ SlotButton &curButton = _buttons[curNum];
+ curButton.setVisible(true);
+ const Graphics::Surface *thumbnail = desc.getThumbnail();
+ if (thumbnail) {
+ curButton.button->setGfx(desc.getThumbnail());
+ } else {
+ curButton.button->setGfx(kThumbnailWidth, kThumbnailHeight2, 0, 0, 0);
+ }
+ curButton.description->setLabel(Common::String::format("%d. %s", saveSlot, desc.getDescription().c_str()));
+
+ Common::String tooltip(_("Name: "));
+ tooltip += desc.getDescription();
+
+ if (_saveDateSupport) {
+ const Common::String &saveDate = desc.getSaveDate();
+ if (!saveDate.empty()) {
+ tooltip += "\n";
+ tooltip += _("Date: ") + saveDate;
+ }
+
+ const Common::String &saveTime = desc.getSaveTime();
+ if (!saveTime.empty()) {
+ tooltip += "\n";
+ tooltip += _("Time: ") + saveTime;
+ }
+ }
+
+ if (_playTimeSupport) {
+ const Common::String &playTime = desc.getPlayTime();
+ if (!playTime.empty()) {
+ tooltip += "\n";
+ tooltip += _("Playtime: ") + playTime;
+ }
+ }
+
+ curButton.button->setTooltip(tooltip);
+
+ // In save mode we disable the button, when it's write protected.
+ // TODO: Maybe we should not display it at all then?
+ if (_saveMode && desc.getWriteProtectedFlag()) {
+ curButton.button->setEnabled(false);
+ } else {
+ curButton.button->setEnabled(true);
+ }
+ }
+
+ const uint numPages = (_entriesPerPage != 0 && !_saveList.empty()) ? ((_saveList.size() + _entriesPerPage - 1) / _entriesPerPage) : 1;
+ _pageDisplay->setLabel(Common::String::format("%u/%u", _curPage + 1, numPages));
+
+ if (_curPage > 0)
+ _prevButton->setEnabled(true);
+ else
+ _prevButton->setEnabled(false);
+
+ if ((_curPage + 1) * _entriesPerPage < _saveList.size())
+ _nextButton->setEnabled(true);
+ else
+ _nextButton->setEnabled(false);
+}
+
+SavenameDialog::SavenameDialog()
+ : Dialog("SavenameDialog") {
+ _title = new StaticTextWidget(this, "SavenameDialog.DescriptionText", Common::String());
+
+ new ButtonWidget(this, "SavenameDialog.Cancel", _("Cancel"), 0, kCloseCmd);
+ new ButtonWidget(this, "SavenameDialog.Ok", _("OK"), 0, kOKCmd);
+
+ _description = new EditTextWidget(this, "SavenameDialog.Description", Common::String(), 0, 0, kOKCmd);
+}
+
+void SavenameDialog::setDescription(const Common::String &desc) {
+ _description->setEditString(desc);
+}
+
+const Common::String &SavenameDialog::getDescription() {
+ return _description->getEditString();
+}
+
+void SavenameDialog::open() {
+ Dialog::open();
+ setResult(-1);
+
+ _title->setLabel(Common::String::format(_("Enter a description for slot %d:"), _targetSlot));
+}
+
+void SavenameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kOKCmd:
+ setResult(0);
+ close();
+ break;
+
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+} // End of namespace GUI
diff --git a/gui/saveload-dialog.h b/gui/saveload-dialog.h
new file mode 100644
index 0000000000..6f7d95f73f
--- /dev/null
+++ b/gui/saveload-dialog.h
@@ -0,0 +1,209 @@
+/* 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 GUI_SAVELOAD_DIALOG_H
+#define GUI_SAVELOAD_DIALOG_H
+
+#include "gui/dialog.h"
+#include "gui/widgets/list.h"
+
+#include "engines/metaengine.h"
+
+namespace GUI {
+
+#define kSwitchSaveLoadDialog -2
+
+// TODO: We might want to disable the grid based save/load chooser for more
+// platforms, than those which define DISABLE_FANCY_THEMES. But those are
+// probably not able to handle the grid chooser anyway, so disabling it
+// for them is a good start.
+#ifdef DISABLE_FANCY_THEMES
+#define DISABLE_SAVELOADCHOOSER_GRID
+#endif // DISABLE_FANCY_THEMES
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+enum SaveLoadChooserType {
+ kSaveLoadDialogList = 0,
+ kSaveLoadDialogGrid = 1
+};
+
+SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine);
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+class SaveLoadChooserDialog : protected Dialog {
+public:
+ SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode);
+ SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode);
+
+ virtual void open();
+
+ virtual void reflowLayout();
+
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ virtual SaveLoadChooserType getType() const = 0;
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+ int run(const Common::String &target, const MetaEngine *metaEngine);
+ virtual const Common::String &getResultString() const = 0;
+
+protected:
+ virtual int runIntern() = 0;
+
+ const bool _saveMode;
+ const MetaEngine *_metaEngine;
+ bool _delSupport;
+ bool _metaInfoSupport;
+ bool _thumbnailSupport;
+ bool _saveDateSupport;
+ bool _playTimeSupport;
+ Common::String _target;
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ ButtonWidget *_listButton;
+ ButtonWidget *_gridButton;
+
+ void addChooserButtons();
+ ButtonWidget *createSwitchButton(const Common::String &name, const char *desc, const char *tooltip, const char *image, uint32 cmd = 0);
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+};
+
+class SaveLoadChooserSimple : public SaveLoadChooserDialog {
+ typedef Common::String String;
+ typedef Common::Array<Common::String> StringArray;
+public:
+ SaveLoadChooserSimple(const String &title, const String &buttonLabel, bool saveMode);
+
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+ virtual const Common::String &getResultString() const;
+
+ virtual void reflowLayout();
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ virtual SaveLoadChooserType getType() const { return kSaveLoadDialogList; }
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+ virtual void open();
+ virtual void close();
+private:
+ virtual int runIntern();
+
+ ListWidget *_list;
+ ButtonWidget *_chooseButton;
+ ButtonWidget *_deleteButton;
+ GraphicsWidget *_gfxWidget;
+ ContainerWidget *_container;
+ StaticTextWidget *_date;
+ StaticTextWidget *_time;
+ StaticTextWidget *_playtime;
+
+ SaveStateList _saveList;
+ String _resultString;
+
+ void updateSaveList();
+ void updateSelection(bool redraw);
+};
+
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+
+class EditTextWidget;
+
+class SavenameDialog : public Dialog {
+public:
+ SavenameDialog();
+
+ void setDescription(const Common::String &desc);
+ const Common::String &getDescription();
+
+ void setTargetSlot(int slot) { _targetSlot = slot; }
+
+ virtual void open();
+protected:
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+private:
+ int _targetSlot;
+ StaticTextWidget *_title;
+ EditTextWidget *_description;
+};
+
+class SaveLoadChooserGrid : public SaveLoadChooserDialog {
+public:
+ SaveLoadChooserGrid(const Common::String &title, bool saveMode);
+ ~SaveLoadChooserGrid();
+
+ virtual const Common::String &getResultString() const;
+
+ virtual void open();
+
+ virtual void reflowLayout();
+
+ virtual SaveLoadChooserType getType() const { return kSaveLoadDialogGrid; }
+
+ virtual void close();
+protected:
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleMouseWheel(int x, int y, int direction);
+private:
+ virtual int runIntern();
+
+ uint _columns, _lines;
+ uint _entriesPerPage;
+ uint _curPage;
+ SaveStateList _saveList;
+
+ ButtonWidget *_nextButton;
+ ButtonWidget *_prevButton;
+
+ StaticTextWidget *_pageDisplay;
+
+ ContainerWidget *_newSaveContainer;
+ int _nextFreeSaveSlot;
+ Common::String _resultString;
+
+ SavenameDialog _savenameDialog;
+ bool selectDescription();
+
+ struct SlotButton {
+ SlotButton() : container(0), button(0), description(0) {}
+ SlotButton(ContainerWidget *c, PicButtonWidget *b, StaticTextWidget *d) : container(c), button(b), description(d) {}
+
+ ContainerWidget *container;
+ PicButtonWidget *button;
+ StaticTextWidget *description;
+
+ void setVisible(bool state) {
+ container->setVisible(state);
+ }
+ };
+ typedef Common::Array<SlotButton> ButtonArray;
+ ButtonArray _buttons;
+ void destroyButtons();
+ void hideButtons();
+ void updateSaves();
+};
+
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/saveload.cpp b/gui/saveload.cpp
index 3dc9961906..c2bbcd9bec 100644
--- a/gui/saveload.cpp
+++ b/gui/saveload.cpp
@@ -20,83 +20,87 @@
*/
#include "common/config-manager.h"
-#include "common/translation.h"
+#include "common/system.h"
-#include "gui/widgets/list.h"
-#include "gui/message.h"
#include "gui/saveload.h"
-#include "gui/ThemeEval.h"
+#include "gui/saveload-dialog.h"
#include "gui/gui-manager.h"
-#include "graphics/scaler.h"
-
#include "engines/metaengine.h"
namespace GUI {
-enum {
- kChooseCmd = 'CHOS',
- kDelCmd = 'DEL '
-
-};
-
-SaveLoadChooser::SaveLoadChooser(const String &title, const String &buttonLabel)
- : Dialog("SaveLoadChooser"), _delSupport(0), _list(0), _chooseButton(0), _deleteButton(0), _gfxWidget(0) {
- _delSupport = _metaInfoSupport = _thumbnailSupport = _saveDateSupport = _playTimeSupport = false;
-
- _backgroundType = ThemeEngine::kDialogBackgroundSpecial;
-
- new StaticTextWidget(this, "SaveLoadChooser.Title", title);
-
- // Add choice list
- _list = new GUI::ListWidget(this, "SaveLoadChooser.List");
- _list->setNumberingMode(GUI::kListNumberingZero);
- setSaveMode(false);
-
- _gfxWidget = new GUI::GraphicsWidget(this, 0, 0, 10, 10);
+SaveLoadChooser::SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode)
+ : _impl(0), _title(title), _buttonLabel(buttonLabel), _saveMode(saveMode) {
+}
- _date = new StaticTextWidget(this, 0, 0, 10, 10, _("No date saved"), Graphics::kTextAlignCenter);
- _time = new StaticTextWidget(this, 0, 0, 10, 10, _("No time saved"), Graphics::kTextAlignCenter);
- _playtime = new StaticTextWidget(this, 0, 0, 10, 10, _("No playtime saved"), Graphics::kTextAlignCenter);
+SaveLoadChooser::~SaveLoadChooser() {
+ delete _impl;
+ _impl = 0;
+}
- // Buttons
- new GUI::ButtonWidget(this, "SaveLoadChooser.Cancel", _("Cancel"), 0, kCloseCmd);
- _chooseButton = new GUI::ButtonWidget(this, "SaveLoadChooser.Choose", buttonLabel, 0, kChooseCmd);
- _chooseButton->setEnabled(false);
+void SaveLoadChooser::selectChooser(const MetaEngine &engine) {
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ const SaveLoadChooserType requestedType = getRequestedSaveLoadDialog(engine);
+ if (!_impl || _impl->getType() != requestedType) {
+ delete _impl;
+ _impl = 0;
+
+ switch (requestedType) {
+ case kSaveLoadDialogGrid:
+ _impl = new SaveLoadChooserGrid(_title, _saveMode);
+ break;
+
+ case kSaveLoadDialogList:
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+ _impl = new SaveLoadChooserSimple(_title, _buttonLabel, _saveMode);
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ break;
+ }
+ }
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+}
- _deleteButton = new GUI::ButtonWidget(this, "SaveLoadChooser.Delete", _("Delete"), 0, kDelCmd);
- _deleteButton->setEnabled(false);
+Common::String SaveLoadChooser::createDefaultSaveDescription(const int slot) const {
+#if defined(USE_SAVEGAME_TIMESTAMP)
+ TimeDate curTime;
+ g_system->getTimeAndDate(curTime);
+ curTime.tm_year += 1900; // fixup year
+ curTime.tm_mon++; // fixup month
+ return Common::String::format("%04d.%02d.%02d / %02d:%02d:%02d", curTime.tm_year, curTime.tm_mon, curTime.tm_mday, curTime.tm_hour, curTime.tm_min, curTime.tm_sec);
+#else
+ return Common::String::format("Save %d", slot + 1);
+#endif
+}
- _delSupport = _metaInfoSupport = _thumbnailSupport = false;
+int SaveLoadChooser::runModalWithCurrentTarget() {
+ const Common::String gameId = ConfMan.get("gameid");
- _container = new GUI::ContainerWidget(this, 0, 0, 10, 10);
-// _container->setHints(GUI::THEME_HINT_USE_SHADOW);
-}
+ const EnginePlugin *plugin = 0;
+ EngineMan.findGame(gameId, &plugin);
-SaveLoadChooser::~SaveLoadChooser() {
+ return runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName());
}
int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, const String &target) {
- if (_gfxWidget)
- _gfxWidget->setGfx(0);
+ selectChooser(**plugin);
+ if (!_impl)
+ return -1;
// Set up the game domain as newly active domain, so
// target specific savepath will be checked
String oldDomain = ConfMan.getActiveDomainName();
ConfMan.setActiveDomain(target);
- _plugin = plugin;
- _target = target;
- _delSupport = (*_plugin)->hasFeature(MetaEngine::kSupportsDeleteSave);
- _metaInfoSupport = (*_plugin)->hasFeature(MetaEngine::kSavesSupportMetaInfo);
- _thumbnailSupport = _metaInfoSupport && (*_plugin)->hasFeature(MetaEngine::kSavesSupportThumbnail);
- _saveDateSupport = _metaInfoSupport && (*_plugin)->hasFeature(MetaEngine::kSavesSupportCreationDate);
- _playTimeSupport = _metaInfoSupport && (*_plugin)->hasFeature(MetaEngine::kSavesSupportPlayTime);
- _resultString.clear();
- reflowLayout();
- updateSaveList();
-
- int ret = Dialog::runModal();
+ int ret;
+ do {
+ ret = _impl->run(target, &(**plugin));
+#ifndef DISABLE_SAVELOADCHOOSER_GRID
+ if (ret == kSwitchSaveLoadDialog) {
+ selectChooser(**plugin);
+ }
+#endif // !DISABLE_SAVELOADCHOOSER_GRID
+ } while (ret < -1);
// Revert to the old active domain
ConfMan.setActiveDomain(oldDomain);
@@ -104,284 +108,9 @@ int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, con
return ret;
}
-void SaveLoadChooser::open() {
- Dialog::open();
-
- // So that quitting ScummVM will not cause the dialog result to say a
- // savegame was selected.
- setResult(-1);
-}
-
const Common::String &SaveLoadChooser::getResultString() const {
- int selItem = _list->getSelected();
- return (selItem >= 0) ? _list->getSelectedString() : _resultString;
-}
-
-void SaveLoadChooser::setSaveMode(bool saveMode) {
- _list->setEditable(saveMode);
-}
-
-void SaveLoadChooser::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
- int selItem = _list->getSelected();
-
- switch (cmd) {
- case GUI::kListItemActivatedCmd:
- case GUI::kListItemDoubleClickedCmd:
- if (selItem >= 0 && _chooseButton->isEnabled()) {
- if (_list->isEditable() || !_list->getSelectedString().empty()) {
- _list->endEditMode();
- if (!_saveList.empty()) {
- setResult(_saveList[selItem].getSaveSlot());
- _resultString = _list->getSelectedString();
- }
- close();
- }
- }
- break;
- case kChooseCmd:
- _list->endEditMode();
- if (!_saveList.empty()) {
- setResult(_saveList[selItem].getSaveSlot());
- _resultString = _list->getSelectedString();
- }
- close();
- break;
- case GUI::kListSelectionChangedCmd:
- updateSelection(true);
- break;
- case kDelCmd:
- if (selItem >= 0 && _delSupport) {
- MessageDialog alert(_("Do you really want to delete this savegame?"),
- _("Delete"), _("Cancel"));
- if (alert.runModal() == GUI::kMessageOK) {
- (*_plugin)->removeSaveState(_target.c_str(), _saveList[selItem].getSaveSlot());
-
- setResult(-1);
- _list->setSelected(-1);
-
- updateSaveList();
- updateSelection(true);
- }
- }
- break;
- case kCloseCmd:
- setResult(-1);
- default:
- Dialog::handleCommand(sender, cmd, data);
- }
-}
-
-void SaveLoadChooser::reflowLayout() {
- if (g_gui.xmlEval()->getVar("Globals.SaveLoadChooser.ExtInfo.Visible") == 1 && _thumbnailSupport) {
- int16 x, y;
- uint16 w, h;
-
- if (!g_gui.xmlEval()->getWidgetData("SaveLoadChooser.Thumbnail", x, y, w, h))
- error("Error when loading position data for Save/Load Thumbnails");
-
- int thumbW = kThumbnailWidth;
- int thumbH = kThumbnailHeight2;
- int thumbX = x + (w >> 1) - (thumbW >> 1);
- int thumbY = y + kLineHeight;
-
- int textLines = 0;
- if (!_saveDateSupport)
- textLines++;
- if (!_playTimeSupport)
- textLines++;
-
- _container->resize(x, y, w, h - (kLineHeight * textLines));
- _gfxWidget->resize(thumbX, thumbY, thumbW, thumbH);
-
- int height = thumbY + thumbH + kLineHeight;
-
- if (_saveDateSupport) {
- _date->resize(thumbX, height, kThumbnailWidth, kLineHeight);
- height += kLineHeight;
- _time->resize(thumbX, height, kThumbnailWidth, kLineHeight);
- height += kLineHeight;
- }
-
- if (_playTimeSupport)
- _playtime->resize(thumbX, height, kThumbnailWidth, kLineHeight);
-
- _container->setVisible(true);
- _gfxWidget->setVisible(true);
-
- _date->setVisible(_saveDateSupport);
- _time->setVisible(_saveDateSupport);
-
- _playtime->setVisible(_playTimeSupport);
-
- _fillR = 0;
- _fillG = 0;
- _fillB = 0;
- updateSelection(false);
- } else {
- _container->setVisible(false);
- _gfxWidget->setVisible(false);
- _date->setVisible(false);
- _time->setVisible(false);
- _playtime->setVisible(false);
- }
-
- Dialog::reflowLayout();
-}
-
-void SaveLoadChooser::updateSelection(bool redraw) {
- int selItem = _list->getSelected();
-
- bool isDeletable = _delSupport;
- bool isWriteProtected = false;
- bool startEditMode = _list->isEditable();
-
- _gfxWidget->setGfx(-1, -1, _fillR, _fillG, _fillB);
- _date->setLabel(_("No date saved"));
- _time->setLabel(_("No time saved"));
- _playtime->setLabel(_("No playtime saved"));
-
- if (selItem >= 0 && _metaInfoSupport) {
- SaveStateDescriptor desc = (*_plugin)->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot());
-
- isDeletable = desc.getDeletableFlag() && _delSupport;
- isWriteProtected = desc.getWriteProtectedFlag();
-
- // Don't allow the user to change the description of write protected games
- if (isWriteProtected)
- startEditMode = false;
-
- if (_thumbnailSupport) {
- const Graphics::Surface *thumb = desc.getThumbnail();
- if (thumb) {
- _gfxWidget->setGfx(thumb);
- _gfxWidget->useAlpha(256);
- }
- }
-
- if (_saveDateSupport) {
- const Common::String &saveDate = desc.getSaveDate();
- if (!saveDate.empty())
- _date->setLabel(_("Date: ") + saveDate);
-
- const Common::String &saveTime = desc.getSaveTime();
- if (!saveTime.empty())
- _time->setLabel(_("Time: ") + saveTime);
- }
-
- if (_playTimeSupport) {
- const Common::String &playTime = desc.getPlayTime();
- if (!playTime.empty())
- _playtime->setLabel(_("Playtime: ") + playTime);
- }
- }
-
-
- if (_list->isEditable()) {
- // Disable the save button if nothing is selected, or if the selected
- // game is write protected
- _chooseButton->setEnabled(selItem >= 0 && !isWriteProtected);
-
- if (startEditMode) {
- _list->startEditMode();
-
- if (_chooseButton->isEnabled() && _list->getSelectedString() == _("Untitled savestate") &&
- _list->getSelectionColor() == ThemeEngine::kFontColorAlternate) {
- _list->setEditString("");
- _list->setEditColor(ThemeEngine::kFontColorNormal);
- }
- }
- } else {
- // Disable the load button if nothing is selected, or if an empty
- // list item is selected.
- _chooseButton->setEnabled(selItem >= 0 && !_list->getSelectedString().empty());
- }
-
- // Delete will always be disabled if the engine doesn't support it.
- _deleteButton->setEnabled(isDeletable && (selItem >= 0) && (!_list->getSelectedString().empty()));
-
- if (redraw) {
- _gfxWidget->draw();
- _date->draw();
- _time->draw();
- _playtime->draw();
- _chooseButton->draw();
- _deleteButton->draw();
-
- draw();
- }
-}
-
-void SaveLoadChooser::close() {
- _plugin = 0;
- _target.clear();
- _saveList.clear();
- _list->setList(StringArray());
-
- Dialog::close();
-}
-
-void SaveLoadChooser::updateSaveList() {
- _saveList = (*_plugin)->listSaves(_target.c_str());
-
- int curSlot = 0;
- int saveSlot = 0;
- StringArray saveNames;
- ListWidget::ColorList colors;
- for (SaveStateList::const_iterator x = _saveList.begin(); x != _saveList.end(); ++x) {
- // Handle gaps in the list of save games
- saveSlot = x->getSaveSlot();
- if (curSlot < saveSlot) {
- while (curSlot < saveSlot) {
- SaveStateDescriptor dummySave(curSlot, "");
- _saveList.insert_at(curSlot, dummySave);
- saveNames.push_back(dummySave.getDescription());
- colors.push_back(ThemeEngine::kFontColorNormal);
- curSlot++;
- }
-
- // Sync the save list iterator
- for (x = _saveList.begin(); x != _saveList.end(); ++x) {
- if (x->getSaveSlot() == saveSlot)
- break;
- }
- }
-
- // Show "Untitled savestate" for empty/whitespace savegame descriptions
- Common::String description = x->getDescription();
- Common::String trimmedDescription = description;
- trimmedDescription.trim();
- if (trimmedDescription.empty()) {
- description = _("Untitled savestate");
- colors.push_back(ThemeEngine::kFontColorAlternate);
- } else {
- colors.push_back(ThemeEngine::kFontColorNormal);
- }
-
- saveNames.push_back(description);
- curSlot++;
- }
-
- // Fill the rest of the save slots with empty saves
-
- int maximumSaveSlots = (*_plugin)->getMaximumSaveSlot();
-
-#ifdef __DS__
- // Low memory on the DS means too many save slots are impractical, so limit
- // the maximum here.
- if (maximumSaveSlots > 99) {
- maximumSaveSlots = 99;
- }
-#endif
-
- Common::String emptyDesc;
- for (int i = curSlot; i <= maximumSaveSlots; i++) {
- saveNames.push_back(emptyDesc);
- SaveStateDescriptor dummySave(i, "");
- _saveList.push_back(dummySave);
- colors.push_back(ThemeEngine::kFontColorNormal);
- }
-
- _list->setList(saveNames, &colors);
+ assert(_impl);
+ return _impl->getResultString();
}
} // End of namespace GUI
diff --git a/gui/saveload.h b/gui/saveload.h
index adaf311fd2..17fd99a31d 100644
--- a/gui/saveload.h
+++ b/gui/saveload.h
@@ -19,63 +19,55 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef GUI_SAVELOAD_DIALOG_H
-#define GUI_SAVELOAD_DIALOG_H
+#ifndef GUI_SAVELOAD_H
+#define GUI_SAVELOAD_H
#include "gui/dialog.h"
#include "engines/metaengine.h"
namespace GUI {
-class ListWidget;
-class GraphicsWidget;
-class ButtonWidget;
-class CommandSender;
-class ContainerWidget;
-class StaticTextWidget;
+class SaveLoadChooserDialog;
-class SaveLoadChooser : GUI::Dialog {
+class SaveLoadChooser {
typedef Common::String String;
- typedef Common::Array<Common::String> StringArray;
protected:
- GUI::ListWidget *_list;
- GUI::ButtonWidget *_chooseButton;
- GUI::ButtonWidget *_deleteButton;
- GUI::GraphicsWidget *_gfxWidget;
- GUI::ContainerWidget *_container;
- GUI::StaticTextWidget *_date;
- GUI::StaticTextWidget *_time;
- GUI::StaticTextWidget *_playtime;
+ SaveLoadChooserDialog *_impl;
- const EnginePlugin *_plugin;
- bool _delSupport;
- bool _metaInfoSupport;
- bool _thumbnailSupport;
- bool _saveDateSupport;
- bool _playTimeSupport;
- String _target;
- SaveStateList _saveList;
- String _resultString;
+ const String _title;
+ const String _buttonLabel;
+ const bool _saveMode;
- uint8 _fillR, _fillG, _fillB;
-
- void updateSaveList();
- void updateSelection(bool redraw);
+ void selectChooser(const MetaEngine &engine);
public:
- SaveLoadChooser(const String &title, const String &buttonLabel);
+ SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode);
~SaveLoadChooser();
- virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
- void setList(const StringArray& list);
+ /**
+ * Runs the save/load chooser with the currently active config manager
+ * domain as target.
+ *
+ * @return The selcted save slot. -1 in case none is selected.
+ */
+ int runModalWithCurrentTarget();
int runModalWithPluginAndTarget(const EnginePlugin *plugin, const String &target);
- void open();
const Common::String &getResultString() const;
- void setSaveMode(bool saveMode);
-
- virtual void reflowLayout();
- virtual void close();
+ /**
+ * Creates a default save description for the specified slot. Depending
+ * on the ScummVM configuration this might be a simple "Slot #" description
+ * or the current date and time.
+ *
+ * TODO: This might not be the best place to put this, since engines not
+ * using this class might want to mimic the same behavior. Check whether
+ * moving this to a better place makes sense and find what this place would
+ * be.
+ *
+ * @param slot The slot number (must be >= 0).
+ * @return The slot description.
+ */
+ Common::String createDefaultSaveDescription(const int slot) const;
};
} // End of namespace GUI
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index bd28c2e85d..7f565eb05d 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -460,6 +460,17 @@
"bevel='2' "
"/> "
"</drawdata> "
+"<drawdata id='button_pressed' cache='false'> "
+"<text font='text_button' "
+"text_color='color_alternative_inverted' "
+"vertical_align='center' "
+"horizontal_align='center' "
+"/> "
+"<drawstep func='square' "
+"fill='foreground' "
+"fg_color='green' "
+"/> "
+"</drawdata> "
"<drawdata id='button_idle' cache='false'> "
"<text font='text_button' "
"text_color='color_button' "
@@ -608,6 +619,8 @@
"<def var='ShowLauncherLogo' value='0'/> "
"<def var='ShowGlobalMenuLogo' value='0'/> "
"<def var='ShowSearchPic' value='0'/> "
+"<def var='ShowChooserPics' value='0'/> "
+"<def var='ShowChooserPageDisplay' value='0'/> "
"<def var='SaveLoadChooser.ExtInfo.Visible' value='0'/> "
"<def var='KeyMapper.Spacing' value='5'/> "
"<def var='KeyMapper.LabelWidth' value='80'/> "
@@ -615,6 +628,8 @@
"<def var='Tooltip.MaxWidth' value='70'/> "
"<def var='Tooltip.XDelta' value='8'/> "
"<def var='Tooltip.YDelta' value='8'/> "
+"<def var='Predictive.Button.Width' value='45' /> "
+"<def var='Predictive.Button.Height' value='15' /> "
"<widget name='Button' "
"size='72,16' "
"/> "
@@ -777,9 +792,6 @@
"<widget name='grFullscreenCheckbox' "
"type='Checkbox' "
"/> "
-"<widget name='grDisableDitheringCheckbox' "
-"type='Checkbox' "
-"/> "
"</layout> "
"</dialog> "
"<dialog name='GlobalOptions_Audio' overlays='Dialog.GlobalOptions.TabWidget'> "
@@ -1187,6 +1199,31 @@
"</layout> "
"</layout> "
"</dialog> "
+"<dialog name='GameOptions_Engine' overlays='Dialog.GameOptions.TabWidget' shading='dim'> "
+"<layout type='vertical' padding='8,8,8,8'> "
+"<widget name='customOption1Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption2Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption3Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption4Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption5Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption6Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption7Checkbox' "
+"type='Checkbox' "
+"/> "
+"</layout> "
+"</dialog> "
"<dialog name='GlobalMenu' overlays='screen_center'> "
"<layout type='vertical' padding='2,2,4,6' center='true' spacing='6'> "
"<widget name='Title' "
@@ -1327,6 +1364,14 @@
"<widget name='Title' height='Globals.Line.Height'/> "
"<widget name='List' /> "
"<layout type='horizontal' padding='0,0,16,0'> "
+"<widget name='ListSwitch' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/> "
+"<widget name='GridSwitch' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/> "
"<space/> "
"<widget name='Delete' "
"type='Button' "
@@ -1341,6 +1386,25 @@
"</layout> "
"</layout> "
"</dialog> "
+"<dialog name='SavenameDialog' overlays='screen_center'> "
+"<layout type='vertical' padding='8,8,8,8'> "
+"<widget name='DescriptionText' "
+"width='180' "
+"height='Globals.Line.Height' "
+"/> "
+"<widget name='Description' "
+"height='19' "
+"/> "
+"<layout type='horizontal' padding='0,0,16,0'> "
+"<widget name='Cancel' "
+"type='Button' "
+"/> "
+"<widget name='Ok' "
+"type='Button' "
+"/> "
+"</layout> "
+"</layout> "
+"</dialog> "
"<dialog name='ScummHelp' overlays='screen'> "
"<layout type='vertical' padding='8,8,8,8'> "
"<widget name='Title' "
@@ -1429,6 +1493,96 @@
"/> "
"</layout> "
"</dialog> "
+"<dialog name='Predictive' overlays='screen_center'> "
+"<layout type='vertical' padding='1,1,1,1' center='true'> "
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"width='150' "
+"textalign='center' "
+"/> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Word' "
+"width='120' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Delete' "
+"width='20' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button1' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button2' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button3' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button4' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button5' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button6' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button7' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button8' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button9' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,0'> "
+"<widget name='Pre' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Button0' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Next' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"<space size='3' /> "
+"<layout type='horizontal' padding='3,3,0,3'> "
+"<widget name='Add' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='Cancel' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"<widget name='OK' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Predictive.Button.Height' "
+"/> "
+"</layout> "
+"</layout> "
+"</dialog> "
"</layout_info> "
"<layout_info resolution='y>399'> "
"<globals> "
@@ -1439,6 +1593,8 @@
"<def var='ShowLauncherLogo' value='0'/> "
"<def var='ShowGlobalMenuLogo' value='0'/> "
"<def var='ShowSearchPic' value='0'/> "
+"<def var='ShowChooserPics' value='0'/> "
+"<def var='ShowChooserPageDisplay' value='1'/> "
"<def var='SaveLoadChooser.ExtInfo.Visible' value='1'/> "
"<def var='KeyMapper.Spacing' value='10'/> "
"<def var='KeyMapper.LabelWidth' value='100'/> "
@@ -1446,6 +1602,7 @@
"<def var='Tooltip.MaxWidth' value='200'/> "
"<def var='Tooltip.XDelta' value='16'/> "
"<def var='Tooltip.YDelta' value='16'/> "
+"<def var='Predictive.Button.Width' value='60' /> "
"<widget name='OptionsLabel' "
"size='110,Globals.Line.Height' "
"textalign='right' "
@@ -1612,9 +1769,6 @@
"<widget name='grFullscreenCheckbox' "
"type='Checkbox' "
"/> "
-"<widget name='grDisableDitheringCheckbox' "
-"type='Checkbox' "
-"/> "
"</layout> "
"</dialog> "
"<dialog name='GlobalOptions_Audio' overlays='Dialog.GlobalOptions.TabWidget'> "
@@ -2008,6 +2162,31 @@
"</layout> "
"</layout> "
"</dialog> "
+"<dialog name='GameOptions_Engine' overlays='Dialog.GameOptions.TabWidget' shading='dim'> "
+"<layout type='vertical' padding='16,16,16,16'> "
+"<widget name='customOption1Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption2Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption3Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption4Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption5Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption6Checkbox' "
+"type='Checkbox' "
+"/> "
+"<widget name='customOption7Checkbox' "
+"type='Checkbox' "
+"/> "
+"</layout> "
+"</dialog> "
"<dialog name='GlobalMenu' overlays='screen_center'> "
"<layout type='vertical' padding='16,16,16,16' center='true'> "
"<widget name='Title' "
@@ -2146,9 +2325,16 @@
"</dialog> "
"<dialog name='SaveLoadChooser' overlays='screen' inset='8' shading='dim'> "
"<layout type='vertical' padding='8,8,8,32' center='true'> "
+"<layout type='horizontal' padding='0,0,0,0'> "
"<widget name='Title' "
"height='Globals.Line.Height' "
"/> "
+"<space/> "
+"<widget name='PageDisplay' "
+"width='200' "
+"height='Globals.Line.Height' "
+"/> "
+"</layout> "
"<layout type='horizontal' padding='0,0,0,16' spacing='16'> "
"<widget name='List' /> "
"<widget name='Thumbnail' "
@@ -2157,6 +2343,14 @@
"/> "
"</layout> "
"<layout type='horizontal' padding='0,0,0,0'> "
+"<widget name='ListSwitch' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/> "
+"<widget name='GridSwitch' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/> "
"<space/> "
"<widget name='Delete' "
"type='Button' "
@@ -2171,6 +2365,26 @@
"</layout> "
"</layout> "
"</dialog> "
+"<dialog name='SavenameDialog' overlays='screen_center'> "
+"<layout type='vertical' padding='8,8,8,8'> "
+"<widget name='DescriptionText' "
+"width='320' "
+"height='Globals.Line.Height' "
+"/> "
+"<widget name='Description' "
+"height='19' "
+"/> "
+"<layout type='horizontal' padding='0,0,16,0'> "
+"<widget name='Cancel' "
+"type='Button' "
+"/> "
+"<space size='96'/> "
+"<widget name='Ok' "
+"type='Button' "
+"/> "
+"</layout> "
+"</layout> "
+"</dialog> "
"<dialog name='ScummHelp' overlays='screen_center'> "
"<layout type='vertical' padding='8,8,8,8' center='true'> "
"<widget name='Title' "
@@ -2259,4 +2473,96 @@
"/> "
"</layout> "
"</dialog> "
+"<dialog name='Predictive' overlays='screen_center'> "
+"<layout type='vertical' padding='5,5,5,5' center='true'> "
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"width='210' "
+"textalign='center' "
+"/> "
+"<layout type='horizontal' padding='5,5,5,5'> "
+"<widget name='Word' "
+"width='190' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Delete' "
+"width='20' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"<space size='5' /> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button1' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button2' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button3' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button4' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button5' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button6' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Button7' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button8' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button9' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Pre' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Button0' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='Next' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"<space size='5' /> "
+"<layout type='horizontal' padding='3,3,3,3'> "
+"<widget name='Add' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<space size='22'/> "
+"<widget name='Cancel' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"<widget name='OK' "
+"width='Globals.Predictive.Button.Width' "
+"height='Globals.Button.Height' "
+"/> "
+"</layout> "
+"</layout> "
+"</dialog> "
"</layout_info> "
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index d4cfd0cba3..62eae0cd43 100644
--- a/gui/themes/scummclassic.zip
+++ b/gui/themes/scummclassic.zip
Binary files differ
diff --git a/gui/themes/scummclassic/THEMERC b/gui/themes/scummclassic/THEMERC
index f3904cbb6d..b8937adcb2 100644
--- a/gui/themes/scummclassic/THEMERC
+++ b/gui/themes/scummclassic/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.8:ScummVM Classic Theme:No Author]
+[SCUMMVM_STX0.8.16:ScummVM Classic Theme:No Author]
diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx
index f07499ce79..49ecea2e11 100644
--- a/gui/themes/scummclassic/classic_gfx.stx
+++ b/gui/themes/scummclassic/classic_gfx.stx
@@ -541,6 +541,19 @@
/>
</drawdata>
+ <!-- Pressed button -->
+ <drawdata id = 'button_pressed' cache = 'false'>
+ <text font = 'text_button'
+ text_color = 'color_alternative_inverted'
+ vertical_align = 'center'
+ horizontal_align = 'center'
+ />
+ <drawstep func = 'square'
+ fill = 'foreground'
+ fg_color = 'green'
+ />
+ </drawdata>
+
<drawdata id = 'button_idle' cache = 'false'>
<text font = 'text_button'
text_color = 'color_button'
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 9da42635a7..4a6aae00bc 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -32,6 +32,9 @@
<def var = 'ShowGlobalMenuLogo' value = '0'/>
<def var = 'ShowSearchPic' value = '0'/>
+ <def var = 'ShowChooserPics' value = '0'/>
+ <def var = 'ShowChooserPageDisplay' value = '1'/>
+
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
<def var = 'KeyMapper.Spacing' value = '10'/>
@@ -42,6 +45,8 @@
<def var = 'Tooltip.XDelta' value = '16'/> <!-- basically cursor size -->
<def var = 'Tooltip.YDelta' value = '16'/>
+ <def var = 'Predictive.Button.Width' value = '60' />
+
<widget name = 'OptionsLabel'
size = '110, Globals.Line.Height'
textalign = 'right'
@@ -215,9 +220,6 @@
<widget name = 'grFullscreenCheckbox'
type = 'Checkbox'
/>
- <widget name = 'grDisableDitheringCheckbox'
- type = 'Checkbox'
- />
</layout>
</dialog>
@@ -627,6 +629,32 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Engine' overlays = 'Dialog.GameOptions.TabWidget' shading = 'dim'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16'>
+ <widget name = 'customOption1Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption2Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption3Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption4Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption5Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption6Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption7Checkbox'
+ type = 'Checkbox'
+ />
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '16, 16, 16, 16' center = 'true'>
<widget name = 'Title'
@@ -768,9 +796,16 @@
<dialog name = 'SaveLoadChooser' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
- <widget name = 'Title'
- height = 'Globals.Line.Height'
- />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0'>
+ <widget name = 'Title'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <widget name = 'PageDisplay'
+ width = '200'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
<widget name = 'List' />
<widget name = 'Thumbnail'
@@ -779,6 +814,14 @@
/>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
+ <widget name = 'ListSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ <widget name = 'GridSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
<space/>
<widget name = 'Delete'
type = 'Button'
@@ -794,6 +837,27 @@
</layout>
</dialog>
+ <dialog name = 'SavenameDialog' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'DescriptionText'
+ width = '320'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'Description'
+ height = '19'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'Cancel'
+ type = 'Button'
+ />
+ <space size = '96'/>
+ <widget name = 'Ok'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'ScummHelp' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
@@ -885,4 +949,98 @@
/>
</layout>
</dialog>
+
+ <dialog name = 'Predictive' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '5, 5, 5, 5' center = 'true'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ width = '210'
+ textalign = 'center'
+ />
+
+ <layout type = 'horizontal' padding = '5, 5, 5, 5'>
+ <widget name = 'Word'
+ width = '190'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Delete'
+ width = '20'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <space size = '5' />
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button1'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button2'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button3'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button4'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button5'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button6'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button7'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button8'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button9'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Pre'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button0'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Next'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <space size = '5' />
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Add'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '22'/>
+ <widget name = 'Cancel'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'OK'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
</layout_info>
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index dc528a4c00..57e149b570 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -33,6 +33,9 @@
<def var = 'ShowGlobalMenuLogo' value = '0'/>
<def var = 'ShowSearchPic' value = '0'/>
+ <def var = 'ShowChooserPics' value = '0'/>
+ <def var = 'ShowChooserPageDisplay' value = '0'/>
+
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
<def var = 'KeyMapper.Spacing' value = '5'/>
@@ -43,6 +46,9 @@
<def var = 'Tooltip.XDelta' value = '8'/> <!-- basically cursor size -->
<def var = 'Tooltip.YDelta' value = '8'/>
+ <def var = 'Predictive.Button.Width' value = '45' />
+ <def var = 'Predictive.Button.Height' value = '15' />
+
<widget name = 'Button'
size = '72, 16'
/>
@@ -213,9 +219,6 @@
<widget name = 'grFullscreenCheckbox'
type = 'Checkbox'
/>
- <widget name = 'grDisableDitheringCheckbox'
- type = 'Checkbox'
- />
</layout>
</dialog>
@@ -639,6 +642,32 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Engine' overlays = 'Dialog.GameOptions.TabWidget' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'customOption1Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption2Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption3Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption4Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption5Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption6Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption7Checkbox'
+ type = 'Checkbox'
+ />
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '2, 2, 4, 6' center = 'true' spacing='6'>
<widget name = 'Title'
@@ -781,6 +810,14 @@
<widget name = 'Title' height = 'Globals.Line.Height'/>
<widget name = 'List' />
<layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'ListSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ <widget name = 'GridSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
<space/>
<widget name = 'Delete'
type = 'Button'
@@ -796,6 +833,26 @@
</layout>
</dialog>
+ <dialog name = 'SavenameDialog' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'DescriptionText'
+ width = '180'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'Description'
+ height = '19'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'Cancel'
+ type = 'Button'
+ />
+ <widget name = 'Ok'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'ScummHelp' overlays = 'screen'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'Title'
@@ -887,4 +944,97 @@
/>
</layout>
</dialog>
+
+ <dialog name = 'Predictive' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '1, 1, 1, 1' center = 'true'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ width = '150'
+ textalign = 'center'
+ />
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Word'
+ width = '120'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Delete'
+ width = '20'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <!-- <space size = '3' /> -->
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button1'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button2'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button3'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button4'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button5'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button6'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button7'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button8'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button9'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 0'>
+ <widget name = 'Pre'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button0'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Next'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <space size = '3' />
+ <layout type = 'horizontal' padding = '3, 3, 0, 3'>
+ <widget name = 'Add'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <!-- <space size = '22'/> -->
+ <widget name = 'Cancel'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'OK'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
</layout_info>
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index fc4c89cbcc..38352bcc2f 100644
--- a/gui/themes/scummmodern.zip
+++ b/gui/themes/scummmodern.zip
Binary files differ
diff --git a/gui/themes/scummmodern/THEMERC b/gui/themes/scummmodern/THEMERC
index 32bd36241e..52eb683ebd 100644
--- a/gui/themes/scummmodern/THEMERC
+++ b/gui/themes/scummmodern/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.8:ScummVM Modern Theme:No Author]
+[SCUMMVM_STX0.8.16:ScummVM Modern Theme:No Author]
diff --git a/gui/themes/scummmodern/delbtn.bmp b/gui/themes/scummmodern/delbtn.bmp
new file mode 100644
index 0000000000..7eb2f409f5
--- /dev/null
+++ b/gui/themes/scummmodern/delbtn.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/grid.bmp b/gui/themes/scummmodern/grid.bmp
new file mode 100644
index 0000000000..adeb209380
--- /dev/null
+++ b/gui/themes/scummmodern/grid.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/list.bmp b/gui/themes/scummmodern/list.bmp
new file mode 100644
index 0000000000..2f54a40bcd
--- /dev/null
+++ b/gui/themes/scummmodern/list.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx
index 5f7cc69acd..4d449f50ec 100644
--- a/gui/themes/scummmodern/scummmodern_gfx.stx
+++ b/gui/themes/scummmodern/scummmodern_gfx.stx
@@ -100,6 +100,9 @@
<bitmap filename = 'logo_small.bmp'/>
<bitmap filename = 'search.bmp'/>
<bitmap filename = 'eraser.bmp'/>
+ <bitmap filename = 'delbtn.bmp'/>
+ <bitmap filename = 'list.bmp'/>
+ <bitmap filename = 'grid.bmp'/>
</bitmaps>
<fonts>
@@ -152,7 +155,7 @@
/>
<text_color id = 'color_normal_disabled'
- color = '192, 192, 192'
+ color = '96, 96, 96'
/>
<text_color id = 'color_alternative'
@@ -186,8 +189,8 @@
<!-- <defaults fill = 'gradient' fg_color = 'white'/> -->
- <cursor file = 'cursor.bmp' hotspot = '0, 0' scale = '3'/>
- <cursor resolution = 'y<400' file = 'cursor_small.bmp' hotspot = '0, 0' scale = '3'/>
+ <cursor file = 'cursor.bmp' hotspot = '0, 0'/>
+ <cursor resolution = 'y<400' file = 'cursor_small.bmp' hotspot = '0, 0'/>
<!-- Selection (text or list items) -->
<drawdata id = 'text_selection' cache = 'false'>
@@ -335,9 +338,13 @@
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '10'
- fill = 'none'
- fg_color = 'darkgray'
+ fill = 'gradient'
+ gradient_start = 'brightpink'
+ gradient_end = 'darkpink'
stroke = '1'
+ fg_color = 'darkred'
+ bevel = '1'
+ bevel_color = 'brightred'
/>
<drawstep func = 'triangle'
fg_color = 'shadowcolor'
@@ -346,7 +353,7 @@
height = '5'
xpos = 'right'
ypos = 'center'
- padding = '0,0,2,0'
+ padding = '0,0,1,0'
orientation = 'top'
/>
</drawdata>
@@ -735,6 +742,27 @@
/>
</drawdata>
+ <!-- Pressed button -->
+ <drawdata id = 'button_pressed' cache = 'false'>
+ <text font = 'text_button'
+ text_color = 'color_button'
+ vertical_align = 'center'
+ horizontal_align = 'center'
+ />
+ <drawstep func = 'roundedsq'
+ radius = '5'
+ stroke = '1'
+ fill = 'foreground'
+ shadow = '0'
+ factor = '0'
+ fg_color = '120, 40, 16'
+ gradient_start = '255, 0, 0'
+ gradient_end = '255, 0, 0'
+ bevel = '1'
+ bevel_color = 'black'
+ />
+ </drawdata>
+
<!-- Idle button -->
<drawdata id = 'button_idle' cache = 'false'>
<text font = 'text_button'
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index 69ad9c79fa..d99d7416c2 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -39,6 +39,9 @@
<def var = 'ShowGlobalMenuLogo' value = '1'/>
<def var = 'ShowSearchPic' value = '1'/>
+ <def var = 'ShowChooserPics' value = '1'/>
+ <def var = 'ShowChooserPageDisplay' value = '1'/>
+
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
<def var = 'KeyMapper.Spacing' value = '10'/>
@@ -49,6 +52,8 @@
<def var = 'Tooltip.XDelta' value = '16'/> <!-- basically cursor size -->
<def var = 'Tooltip.YDelta' value = '32'/>
+ <def var = 'Predictive.Button.Width' value = '60' />
+
<widget name = 'OptionsLabel'
size = '115, Globals.Line.Height'
textalign = 'right'
@@ -59,8 +64,7 @@
<widget name = 'Button'
size = '108, 24'
- />
-
+ />
<widget name = 'Slider'
size = '128, 18'
@@ -230,9 +234,6 @@
<widget name = 'grFullscreenCheckbox'
type = 'Checkbox'
/>
- <widget name = 'grDisableDitheringCheckbox'
- type = 'Checkbox'
- />
</layout>
</dialog>
@@ -642,6 +643,32 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Engine' overlays = 'Dialog.GameOptions.TabWidget' shading = 'dim'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16'>
+ <widget name = 'customOption1Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption2Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption3Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption4Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption5Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption6Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption7Checkbox'
+ type = 'Checkbox'
+ />
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '16, 16, 16, 16' center = 'true'>
<widget name = 'Logo'
@@ -783,9 +810,16 @@
<dialog name = 'SaveLoadChooser' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
- <widget name = 'Title'
- height = 'Globals.Line.Height'
- />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0'>
+ <widget name = 'Title'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <widget name = 'PageDisplay'
+ width = '200'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
<widget name = 'List' />
<widget name = 'Thumbnail'
@@ -794,6 +828,14 @@
/>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
+ <widget name = 'ListSwitch'
+ height = '20'
+ width = '20'
+ />
+ <widget name = 'GridSwitch'
+ height = '20'
+ width = '20'
+ />
<space/>
<widget name = 'Delete'
type = 'Button'
@@ -809,6 +851,27 @@
</layout>
</dialog>
+ <dialog name = 'SavenameDialog' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'DescriptionText'
+ width = '320'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'Description'
+ height = '19'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'Cancel'
+ type = 'Button'
+ />
+ <space size = '96'/>
+ <widget name = 'Ok'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'ScummHelp' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
@@ -899,5 +962,99 @@
type = 'Button'
/>
</layout>
+ </dialog>
+ <dialog name = 'Predictive' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '5, 5, 5, 5' center = 'true'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ width = '210'
+ textalign = 'center'
+ />
+
+ <layout type = 'horizontal' padding = '5, 5, 5, 5'>
+ <widget name = 'Word'
+ width = '190'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Delete'
+ width = '20'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <space size = '5' />
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button1'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button2'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button3'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button4'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button5'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button6'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Button7'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button8'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button9'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Pre'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Button0'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Next'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ <space size = '5' />
+ <layout type = 'horizontal' padding = '3, 3, 3, 3'>
+ <widget name = 'Add'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '22'/>
+ <widget name = 'Cancel'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'OK'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
</dialog>
+
</layout_info>
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index 0bfd16c1d9..4fd5bdcf40 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -31,8 +31,14 @@
<def var = 'ShowGlobalMenuLogo' value = '0'/>
<def var = 'ShowSearchPic' value = '0'/>
+ <def var = 'ShowChooserPics' value = '0'/>
+ <def var = 'ShowChooserPageDisplay' value = '0'/>
+
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
+ <def var = 'Predictive.Button.Width' value = '45' />
+ <def var = 'Predictive.Button.Height' value = '15' />
+
<widget name = 'Button'
size = '72, 16'
/>
@@ -211,9 +217,6 @@
<widget name = 'grFullscreenCheckbox'
type = 'Checkbox'
/>
- <widget name = 'grDisableDitheringCheckbox'
- type = 'Checkbox'
- />
</layout>
</dialog>
@@ -637,6 +640,32 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Engine' overlays = 'Dialog.GameOptions.TabWidget' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'customOption1Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption2Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption3Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption4Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption5Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption6Checkbox'
+ type = 'Checkbox'
+ />
+ <widget name = 'customOption7Checkbox'
+ type = 'Checkbox'
+ />
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '4, 4, 4, 4' center = 'true' spacing='2'>
<widget name = 'Title'
@@ -780,6 +809,14 @@
<widget name = 'Title' height = 'Globals.Line.Height'/>
<widget name = 'List' />
<layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'ListSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ <widget name = 'GridSwitch'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
<space/>
<widget name = 'Delete'
type = 'Button'
@@ -795,6 +832,26 @@
</layout>
</dialog>
+ <dialog name = 'SavenameDialog' overlays = 'screen_center'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8'>
+ <widget name = 'DescriptionText'
+ width = '180'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'Description'
+ height = '19'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 16, 0'>
+ <widget name = 'Cancel'
+ type = 'Button'
+ />
+ <widget name = 'Ok'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'ScummHelp' overlays = 'screen' inset = '8'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'Title'
@@ -885,4 +942,96 @@
/>
</layout>
</dialog>
+ <dialog name = 'Predictive' overlays = 'screen_center'>
+ <layout type = 'vertical' center = 'true'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ width = '150'
+ textalign = 'center'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Word'
+ width = '120'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Delete'
+ width = '20'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <!-- <space size = '3' /> -->
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Button1'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button2'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button3'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Button4'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button5'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button6'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Button7'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button8'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button9'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Pre'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Button0'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'Next'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ <space size = '2' />
+ <layout type = 'horizontal' padding = '0, 0, 2, 2'>
+ <widget name = 'Add'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <!-- <space size = '22'/> -->
+ <widget name = 'Cancel'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ <widget name = 'OK'
+ width = 'Globals.Predictive.Button.Width'
+ height = 'Globals.Predictive.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
</layout_info>
diff --git a/gui/themes/translations.dat b/gui/themes/translations.dat
index 65ca204393..1c3abf84a8 100644
--- a/gui/themes/translations.dat
+++ b/gui/themes/translations.dat
Binary files differ
diff --git a/gui/widget.cpp b/gui/widget.cpp
index 0e2fd248b1..4ffb63e945 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -30,6 +30,8 @@
#include "gui/ThemeEval.h"
+#include "gui/dialog.h"
+
namespace GUI {
Widget::Widget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip)
@@ -77,6 +79,8 @@ void Widget::updateState(int oldFlags, int newFlags) {
_state = ThemeEngine::kStateEnabled;
if (newFlags & WIDGET_HILITED)
_state = ThemeEngine::kStateHighlight;
+ if (newFlags & WIDGET_PRESSED)
+ _state = ThemeEngine::kStatePressed;
} else {
_state = ThemeEngine::kStateDisabled;
}
@@ -272,7 +276,7 @@ void StaticTextWidget::drawWidget() {
ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey)
: StaticTextWidget(boss, x, y, w, h, cleanupHotkey(label), Graphics::kTextAlignCenter, tooltip), CommandSender(boss),
- _cmd(cmd), _hotkey(hotkey) {
+ _cmd(cmd), _hotkey(hotkey), _lastTime(0) {
if (hotkey == 0)
_hotkey = parseHotkey(label);
@@ -283,7 +287,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Co
ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey)
: StaticTextWidget(boss, name, cleanupHotkey(label), tooltip), CommandSender(boss),
- _cmd(cmd) {
+ _cmd(cmd), _lastTime(0) {
if (hotkey == 0)
_hotkey = parseHotkey(label);
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
@@ -291,8 +295,14 @@ ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Co
}
void ButtonWidget::handleMouseUp(int x, int y, int button, int clickCount) {
- if (isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h)
+ if (isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h) {
+ startAnimatePressedState();
sendCommand(_cmd, 0);
+ }
+}
+
+void ButtonWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ setPressedState();
}
void ButtonWidget::drawWidget() {
@@ -324,6 +334,44 @@ ButtonWidget *addClearButton(GuiObject *boss, const Common::String &name, uint32
return button;
}
+void ButtonWidget::setHighLighted(bool enable) {
+ (enable) ? setFlags(WIDGET_HILITED) : clearFlags(WIDGET_HILITED);
+ draw();
+}
+
+void ButtonWidget::handleTickle() {
+ if (_lastTime) {
+ uint32 curTime = g_system->getMillis();
+ if (curTime - _lastTime > kPressedButtonTime) {
+ stopAnimatePressedState();
+ }
+ }
+}
+
+void ButtonWidget::setPressedState() {
+ wantTickle(true);
+ setFlags(WIDGET_PRESSED);
+ draw();
+}
+
+void ButtonWidget::stopAnimatePressedState() {
+ wantTickle(false);
+ _lastTime = 0;
+ clearFlags(WIDGET_PRESSED);
+ draw();
+}
+
+void ButtonWidget::startAnimatePressedState() {
+ _lastTime = g_system->getMillis();
+}
+
+void ButtonWidget::wantTickle(bool tickled) {
+ if (tickled)
+ ((GUI::Dialog *)_boss)->setTickleWidget(this);
+ else
+ ((GUI::Dialog *)_boss)->unSetTickleWidget();
+}
+
#pragma mark -
PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey)
@@ -336,7 +384,7 @@ PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, co
PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip, uint32 cmd, uint8 hotkey)
: ButtonWidget(boss, name, "", tooltip, cmd, hotkey),
- _alpha(256), _transparency(false) {
+ _gfx(), _alpha(256), _transparency(false) {
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
_type = kButtonWidget;
}
@@ -351,19 +399,44 @@ void PicButtonWidget::setGfx(const Graphics::Surface *gfx) {
if (!gfx || !gfx->pixels)
return;
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("PicButtonWidget::setGfx got paletted surface passed");
+ return;
+ }
+
+
if (gfx->w > _w || gfx->h > _h) {
warning("PicButtonWidget has size %dx%d, but a surface with %dx%d is to be set", _w, _h, gfx->w, gfx->h);
return;
}
- // TODO: add conversion to OverlayColor
_gfx.copyFrom(*gfx);
}
+void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) {
+ if (w == -1)
+ w = _w;
+ if (h == -1)
+ h = _h;
+
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+
+ _gfx.free();
+ _gfx.create(w, h, requiredFormat);
+ _gfx.fillRect(Common::Rect(0, 0, w, h), _gfx.format.RGBToColor(r, g, b));
+}
+
void PicButtonWidget::drawWidget() {
g_gui.theme()->drawButton(Common::Rect(_x, _y, _x+_w, _y+_h), "", _state, getFlags());
- if (sizeof(OverlayColor) == _gfx.format.bytesPerPixel && _gfx.pixels) {
+ if (_gfx.pixels) {
+ // Check whether the set up surface needs to be converted to the GUI
+ // color format.
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+ if (_gfx.format != requiredFormat) {
+ _gfx.convertToInPlace(requiredFormat);
+ }
+
const int x = _x + (_w - _gfx.w) / 2;
const int y = _y + (_h - _gfx.h) / 2;
@@ -576,12 +649,16 @@ void GraphicsWidget::setGfx(const Graphics::Surface *gfx) {
if (!gfx || !gfx->pixels)
return;
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("GraphicsWidget::setGfx got paletted surface passed");
+ return;
+ }
+
if (gfx->w > _w || gfx->h > _h) {
warning("GraphicsWidget has size %dx%d, but a surface with %dx%d is to be set", _w, _h, gfx->w, gfx->h);
return;
}
- // TODO: add conversion to OverlayColor
_gfx.copyFrom(*gfx);
}
@@ -591,22 +668,22 @@ void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) {
if (h == -1)
h = _h;
- Graphics::PixelFormat overlayFormat = g_system->getOverlayFormat();
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
_gfx.free();
- _gfx.create(w, h, overlayFormat);
-
- OverlayColor *dst = (OverlayColor *)_gfx.pixels;
- OverlayColor fillCol = overlayFormat.RGBToColor(r, g, b);
- while (h--) {
- for (int i = 0; i < w; ++i) {
- *dst++ = fillCol;
- }
- }
+ _gfx.create(w, h, requiredFormat);
+ _gfx.fillRect(Common::Rect(0, 0, w, h), _gfx.format.RGBToColor(r, g, b));
}
void GraphicsWidget::drawWidget() {
- if (sizeof(OverlayColor) == _gfx.format.bytesPerPixel && _gfx.pixels) {
+ if (_gfx.pixels) {
+ // Check whether the set up surface needs to be converted to the GUI
+ // color format.
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+ if (_gfx.format != requiredFormat) {
+ _gfx.convertToInPlace(requiredFormat);
+ }
+
const int x = _x + (_w - _gfx.w) / 2;
const int y = _y + (_h - _gfx.h) / 2;
@@ -626,6 +703,26 @@ ContainerWidget::ContainerWidget(GuiObject *boss, const Common::String &name) :
_type = kContainerWidget;
}
+ContainerWidget::~ContainerWidget() {
+ // We also remove the widget from the boss to avoid segfaults, when the
+ // deleted widget is an active widget in the boss.
+ for (Widget *w = _firstWidget; w; w = w->next()) {
+ _boss->removeWidget(w);
+ }
+}
+
+Widget *ContainerWidget::findWidget(int x, int y) {
+ return findWidgetInChain(_firstWidget, x, y);
+}
+
+void ContainerWidget::removeWidget(Widget *widget) {
+ // We also remove the widget from the boss to avoid a reference to a
+ // widget not in the widget chain anymore.
+ _boss->removeWidget(widget);
+
+ Widget::removeWidget(widget);
+}
+
void ContainerWidget::drawWidget() {
g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), 0, ThemeEngine::kWidgetBackgroundBorder);
}
diff --git a/gui/widget.h b/gui/widget.h
index 789fc09231..e3f712564f 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -38,6 +38,7 @@ enum {
WIDGET_INVISIBLE = 1 << 1,
WIDGET_HILITED = 1 << 2,
WIDGET_BORDER = 1 << 3,
+ WIDGET_PRESSED = 1 << 4,
//WIDGET_INV_BORDER = 1 << 4,
WIDGET_CLEARBG = 1 << 5,
WIDGET_WANT_TICKLE = 1 << 7,
@@ -73,6 +74,10 @@ enum {
kCaretBlinkTime = 300
};
+enum {
+ kPressedButtonTime = 200
+};
+
/* Widget */
class Widget : public GuiObject {
friend class Dialog;
@@ -83,7 +88,7 @@ protected:
uint16 _id;
bool _hasFocus;
ThemeEngine::WidgetStateInfo _state;
- const char *_tooltip;
+ Common::String _tooltip;
private:
uint16 _flags;
@@ -137,7 +142,9 @@ public:
uint8 parseHotkey(const Common::String &label);
Common::String cleanupHotkey(const Common::String &label);
- const char *getTooltip() const { return _tooltip; }
+ bool hasTooltip() const { return !_tooltip.empty(); }
+ const Common::String &getTooltip() const { return _tooltip; }
+ void setTooltip(const Common::String &tooltip) { _tooltip = tooltip; }
protected:
void updateState(int oldFlags, int newFlags);
@@ -189,11 +196,22 @@ public:
void setLabel(const Common::String &label);
void handleMouseUp(int x, int y, int button, int clickCount);
+ void handleMouseDown(int x, int y, int button, int clickCount);
void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); draw(); }
- void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); draw(); }
+ void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED | WIDGET_PRESSED); draw(); }
+ void handleTickle();
+
+ void setHighLighted(bool enable);
+ void setPressedState();
+ void startAnimatePressedState();
+ void stopAnimatePressedState();
+ void lostFocusWidget() { stopAnimatePressedState(); }
protected:
void drawWidget();
+ void wantTickle(bool tickled);
+private:
+ uint32 _lastTime;
};
/* PicButtonWidget */
@@ -204,6 +222,7 @@ public:
~PicButtonWidget();
void setGfx(const Graphics::Surface *gfx);
+ void setGfx(int w, int h, int r, int g, int b);
void useAlpha(int alpha) { _alpha = alpha; }
void useThemeTransparency(bool enable) { _transparency = enable; }
@@ -349,7 +368,10 @@ class ContainerWidget : public Widget {
public:
ContainerWidget(GuiObject *boss, int x, int y, int w, int h);
ContainerWidget(GuiObject *boss, const Common::String &name);
+ ~ContainerWidget();
+ virtual Widget *findWidget(int x, int y);
+ virtual void removeWidget(Widget *widget);
protected:
void drawWidget();
};
diff --git a/gui/widgets/editable.h b/gui/widgets/editable.h
index 4b51ac9145..4a18d5e689 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 Common::Rect getEditRect() const = 0;
virtual int getCaretOffset() const;
void drawCaret(bool erase);
- bool setCaretPos(int newPos);
bool adjustOffset();
void makeCaretVisible();
diff --git a/gui/widgets/list.h b/gui/widgets/list.h
index 41fae37a71..47613b79f3 100644
--- a/gui/widgets/list.h
+++ b/gui/widgets/list.h
@@ -105,6 +105,7 @@ public:
void scrollTo(int item);
void scrollToEnd();
+ int getCurrentScrollPos() const { return _currentPos; }
void enableQuickSelect(bool enable) { _quickSelect = enable; }
String getQuickSelectString() const { return _quickSelectStr; }