aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/ThemeEngine.cpp185
-rw-r--r--gui/ThemeEngine.h41
-rw-r--r--gui/ThemeParser.cpp66
-rw-r--r--gui/ThemeParser.h6
-rw-r--r--gui/animation/AccelerateInterpolator.h45
-rw-r--r--gui/animation/AlphaAnimation.h53
-rw-r--r--gui/animation/Animation.cpp98
-rw-r--r--gui/animation/Animation.h76
-rw-r--r--gui/animation/DeccelerateInterpolator.h41
-rw-r--r--gui/animation/Drawable.h109
-rw-r--r--gui/animation/Interpolator.h44
-rw-r--r--gui/animation/ParallelAnimation.h72
-rw-r--r--gui/animation/RepeatAnimationWrapper.cpp52
-rw-r--r--gui/animation/RepeatAnimationWrapper.h61
-rw-r--r--gui/animation/ScaleAnimation.h69
-rw-r--r--gui/animation/SequenceAnimationComposite.cpp72
-rw-r--r--gui/animation/SequenceAnimationComposite.h51
-rw-r--r--gui/animation/WaitForConditionAnimation.h71
-rw-r--r--gui/browser.cpp8
-rw-r--r--gui/credits.h8
-rw-r--r--gui/debugger.cpp4
-rw-r--r--gui/downloaddialog.cpp272
-rw-r--r--gui/downloaddialog.h81
-rw-r--r--gui/editgamedialog.cpp550
-rw-r--r--gui/editgamedialog.h97
-rw-r--r--gui/gui-manager.cpp2
-rw-r--r--gui/launcher.cpp668
-rw-r--r--gui/launcher.h2
-rw-r--r--gui/module.mk11
-rw-r--r--gui/object.h6
-rw-r--r--gui/options.cpp484
-rw-r--r--gui/options.h47
-rw-r--r--gui/remotebrowser.cpp232
-rw-r--r--gui/remotebrowser.h84
-rw-r--r--gui/saveload-dialog.cpp214
-rw-r--r--gui/saveload-dialog.h48
-rw-r--r--gui/saveload.cpp4
-rw-r--r--gui/storagewizarddialog.cpp363
-rw-r--r--gui/storagewizarddialog.h107
-rw-r--r--gui/themes/default.inc439
-rw-r--r--gui/themes/scummclassic.zipbin115643 -> 130414 bytes
-rw-r--r--gui/themes/scummclassic/classic_gfx.stx40
-rw-r--r--gui/themes/scummclassic/classic_layout.stx247
-rw-r--r--gui/themes/scummclassic/classic_layout_lowres.stx252
-rw-r--r--gui/themes/scummmodern.zipbin1491681 -> 1650222 bytes
-rw-r--r--gui/themes/scummmodern/box.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/dropbox.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/googledrive.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/onedrive.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/scummmodern_gfx.stx50
-rw-r--r--gui/themes/scummmodern/scummmodern_layout.stx247
-rw-r--r--gui/themes/scummmodern/scummmodern_layout_lowres.stx252
-rwxr-xr-xgui/themes/scummtheme.py2
-rw-r--r--gui/themes/translations.datbin570539 -> 582141 bytes
-rw-r--r--gui/widget.cpp140
-rw-r--r--gui/widget.h24
-rw-r--r--gui/widgets/editable.cpp15
-rw-r--r--gui/widgets/scrollcontainer.cpp22
-rw-r--r--gui/widgets/scrollcontainer.h7
59 files changed, 5533 insertions, 708 deletions
diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index c850a6a02e..96108bccce 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -31,11 +31,13 @@
#include "graphics/cursorman.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
#include "graphics/VectorRenderer.h"
#include "graphics/fonts/bdf.h"
#include "graphics/fonts/ttf.h"
#include "image/bmp.h"
+#include "image/png.h"
#include "gui/widget.h"
#include "gui/ThemeEngine.h"
@@ -59,6 +61,10 @@ const char *const ThemeEngine::kImageStopSmallButton = "stopbtn_small.bmp";
const char *const ThemeEngine::kImageEditSmallButton = "editbtn_small.bmp";
const char *const ThemeEngine::kImageSwitchModeSmallButton = "switchbtn_small.bmp";
const char *const ThemeEngine::kImageFastReplaySmallButton = "fastreplay_small.bmp";
+const char *const ThemeEngine::kImageDropboxLogo = "dropbox.bmp";
+const char *const ThemeEngine::kImageOneDriveLogo = "onedrive.bmp";
+const char *const ThemeEngine::kImageGoogleDriveLogo = "googledrive.bmp";
+const char *const ThemeEngine::kImageBoxLogo = "box.bmp";
struct TextDrawData {
const Graphics::Font *_fontPtr;
@@ -168,11 +174,23 @@ protected:
bool _alpha;
};
+class ThemeItemABitmap : public ThemeItem {
+public:
+ ThemeItemABitmap(ThemeEngine *engine, const Common::Rect &area, Graphics::TransparentSurface *bitmap, ThemeEngine::AutoScaleMode autoscale, int alpha) :
+ ThemeItem(engine, area), _bitmap(bitmap), _autoscale(autoscale), _alpha(alpha) {}
+
+ void drawSelf(bool draw, bool restore);
+
+protected:
+ Graphics::TransparentSurface *_bitmap;
+ ThemeEngine::AutoScaleMode _autoscale;
+ int _alpha;
+};
+
class ThemeItemBitmapClip : public ThemeItem {
public:
ThemeItemBitmapClip(ThemeEngine *engine, const Common::Rect &area, const Common::Rect &clip, const Graphics::Surface *bitmap, bool alpha) :
ThemeItem(engine, area), _bitmap(bitmap), _alpha(alpha), _clip(clip) {}
-
void drawSelf(bool draw, bool restore);
protected:
@@ -308,7 +326,7 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) {
if (draw) {
if (_alpha)
- _engine->renderer()->blitAlphaBitmap(_bitmap, _area);
+ _engine->renderer()->blitKeyBitmap(_bitmap, _area);
else
_engine->renderer()->blitSubSurface(_bitmap, _area);
}
@@ -316,13 +334,23 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) {
_engine->addDirtyRect(_area);
}
-void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) {
+void ThemeItemABitmap::drawSelf(bool draw, bool restore) {
if (restore)
_engine->restoreBackground(_area);
+ if (draw)
+ _engine->renderer()->blitAlphaBitmap(_bitmap, _area, _autoscale, Graphics::DrawStep::kVectorAlignManual, Graphics::DrawStep::kVectorAlignManual, _alpha);
+
+ _engine->addDirtyRect(_area);
+}
+
+void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) {
+ if (restore)
+ _engine->restoreBackground(_area);
+
if (draw) {
if (_alpha)
- _engine->renderer()->blitAlphaBitmapClip(_bitmap, _area, _clip);
+ _engine->renderer()->blitKeyBitmapClip(_bitmap, _area, _clip);
else
_engine->renderer()->blitSubSurfaceClip(_bitmap, _area, _clip);
}
@@ -401,6 +429,15 @@ ThemeEngine::~ThemeEngine() {
}
_bitmaps.clear();
+ for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
+ Graphics::TransparentSurface *surf = i->_value;
+ if (surf) {
+ surf->free();
+ delete surf;
+ }
+ }
+ _abitmaps.clear();
+
delete _parser;
delete _themeEval;
delete[] _cursor;
@@ -525,6 +562,15 @@ void ThemeEngine::refresh() {
}
}
_bitmaps.clear();
+
+ for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
+ Graphics::TransparentSurface *surf = i->_value;
+ if (surf) {
+ surf->free();
+ delete surf;
+ }
+ }
+ _abitmaps.clear();
}
init();
@@ -706,24 +752,51 @@ bool ThemeEngine::addBitmap(const Common::String &filename) {
if (surf)
return true;
- // If not, try to load the bitmap via the BitmapDecoder class.
- Image::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) {
- bitmapDecoder.loadStream(*stream);
- srcSurface = bitmapDecoder.getSurface();
- delete stream;
- if (srcSurface)
- break;
+
+ if (filename.hasSuffix(".png")) {
+ // Maybe it is PNG?
+#ifdef USE_PNG
+ Image::PNGDecoder decoder;
+ 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) {
+ if (!decoder.loadStream(*stream))
+ error("Error decoding PNG");
+
+ srcSurface = decoder.getSurface();
+ delete stream;
+ if (srcSurface)
+ break;
+ }
}
- }
- if (srcSurface && srcSurface->format.bytesPerPixel != 1)
- surf = srcSurface->convertTo(_overlayFormat);
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+#else
+ error("No PNG support compiled in");
+#endif
+ } else {
+ // If not, try to load the bitmap via the BitmapDecoder class.
+ Image::BitmapDecoder bitmapDecoder;
+ 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) {
+ bitmapDecoder.loadStream(*stream);
+ srcSurface = bitmapDecoder.getSurface();
+ delete stream;
+ 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;
@@ -731,6 +804,48 @@ bool ThemeEngine::addBitmap(const Common::String &filename) {
return surf != 0;
}
+bool ThemeEngine::addAlphaBitmap(const Common::String &filename) {
+ // Nothing has to be done if the bitmap already has been loaded.
+ Graphics::TransparentSurface *surf = _abitmaps[filename];
+ if (surf)
+ return true;
+
+ const Graphics::TransparentSurface *srcSurface = 0;
+
+ if (filename.hasSuffix(".png")) {
+ // Maybe it is PNG?
+#ifdef USE_PNG
+ Image::PNGDecoder decoder;
+ 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) {
+ if (!decoder.loadStream(*stream))
+ error("Error decoding PNG");
+
+ srcSurface = new Graphics::TransparentSurface(*decoder.getSurface(), true);
+ delete stream;
+ if (srcSurface)
+ break;
+ }
+ }
+
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+#else
+ error("No PNG support compiled in");
+#endif
+ } else {
+ error("Only PNG is supported as alphabitmap");
+ }
+
+ // Store the surface into our hashmap (attention, may store NULL entries!)
+ _abitmaps[filename] = surf;
+
+ return surf != 0;
+}
+
bool ThemeEngine::addDrawData(const Common::String &data, bool cached) {
DrawData id = parseDrawDataId(data);
@@ -977,7 +1092,10 @@ void ThemeEngine::queueDDTextClip(TextData type, TextColor color, const Common::
area.clip(_screen.w, _screen.h);
Common::Rect textArea = drawableTextArea;
if (textArea.isEmpty()) textArea = clippingArea;
- else textArea.clip(clippingArea);
+ else {
+ textArea.clip(clippingArea);
+ if (textArea.isEmpty()) textArea = Common::Rect(0, 0, 1, 1); // one small pixel should be invisible enough
+ }
ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], _textColors[color], area, textArea, text, alignH, alignV, ellipsis, restoreBg, deltax);
@@ -1004,6 +1122,21 @@ void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rec
}
}
+void ThemeEngine::queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha) {
+
+ Common::Rect area = r;
+ area.clip(_screen.w, _screen.h);
+
+ ThemeItemABitmap *q = new ThemeItemABitmap(this, area, bitmap, autoscale, alpha);
+
+ if (_buffering) {
+ _screenQueue.push_back(q);
+ } else {
+ q->drawSelf(true, false);
+ delete q;
+ }
+}
+
void ThemeEngine::queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &r, const Common::Rect &clip, bool alpha) {
Common::Rect area = r;
@@ -1291,6 +1424,8 @@ void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground b
case kDialogBackgroundDefault:
queueDD(kDDDefaultBackground, r);
break;
+ case kDialogBackgroundNone:
+ break;
}
}
@@ -1318,6 +1453,9 @@ void ThemeEngine::drawDialogBackgroundClip(const Common::Rect &r, const Common::
case kDialogBackgroundDefault:
queueDDClip(kDDDefaultBackground, r, clip);
break;
+ case kDialogBackgroundNone:
+ // no op
+ break;
}
}
@@ -1392,6 +1530,13 @@ void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &su
queueBitmap(&surface, r, themeTrans);
}
+void ThemeEngine::drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha) {
+ if (!ready())
+ return;
+
+ queueABitmap(&surface, r, autoscale, alpha);
+}
+
void ThemeEngine::drawSurfaceClip(const Common::Rect &r, const Common::Rect &clip, const Graphics::Surface &surface, WidgetStateInfo state, int alpha, bool themeTrans) {
if (!ready())
return;
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index 3c259b4f9d..7506cee95f 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -32,6 +32,7 @@
#include "common/rect.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
#include "graphics/font.h"
#include "graphics/pixelformat.h"
@@ -140,6 +141,7 @@ enum TextColor {
class ThemeEngine {
protected:
typedef Common::HashMap<Common::String, Graphics::Surface *> ImagesMap;
+ typedef Common::HashMap<Common::String, Graphics::TransparentSurface *> AImagesMap;
friend class GUI::Dialog;
friend class GUI::GuiObject;
@@ -169,7 +171,8 @@ public:
kDialogBackgroundSpecial,
kDialogBackgroundPlain,
kDialogBackgroundTooltip,
- kDialogBackgroundDefault
+ kDialogBackgroundDefault,
+ kDialogBackgroundNone
};
/// State of the widget to be drawn
@@ -223,6 +226,14 @@ public:
kShadingLuminance ///< Converting colors to luminance for unused areas
};
+ /// AlphaBitmap scale mode selector
+ enum AutoScaleMode {
+ kAutoScaleNone = 0, ///< Use image dimensions
+ kAutoScaleStretch = 1, ///< Stretch image to full widget size
+ kAutoScaleFit = 2, ///< Scale image to widget size but keep aspect ratio
+ kAutoScaleNinePatch = 3 ///< 9-patch image
+ };
+
// Special image ids for images used in the GUI
static const char *const kImageLogo; ///< ScummVM logo used in the launcher
static const char *const kImageLogoSmall; ///< ScummVM logo used in the GMM
@@ -239,6 +250,10 @@ public:
static const char *const kImageEditSmallButton; ///< Edit recording metadata in recorder onscreen dialog (for 320xY)
static const char *const kImageSwitchModeSmallButton; ///< Switch mode button in recorder onscreen dialog (for 320xY)
static const char *const kImageFastReplaySmallButton; ///< Fast playback mode button in recorder onscreen dialog (for 320xY)
+ static const char *const kImageDropboxLogo; ///< Dropbox logo used in the StorageWizardDialog
+ static const char *const kImageOneDriveLogo; ///< OneDrive logo used in the StorageWizardDialog
+ static const char *const kImageGoogleDriveLogo; ///< Google Drive logo used in the StorageWizardDialog
+ static const char *const kImageBoxLogo; ///< Box logo used in the StorageWizardDialog
/**
* Graphics mode enumeration.
@@ -353,6 +368,8 @@ public:
void drawSurfaceClip(const Common::Rect &r, const Common::Rect &clippingRect, const Graphics::Surface &surface,
WidgetStateInfo state = kStateEnabled, int alpha = 255, bool themeTrans = false);
+ void drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha);
+
void drawSlider(const Common::Rect &r, int width,
WidgetStateInfo state = kStateEnabled);
void drawSliderClip(const Common::Rect &r, const Common::Rect &clippingRect, int width,
@@ -483,6 +500,14 @@ public:
bool addBitmap(const Common::String &filename);
/**
+ * Interface for the ThemeParser class: Loads a bitmap with transparency file to use on the GUI.
+ * The filename is also used as its identifier.
+ *
+ * @param filename Name of the bitmap file.
+ */
+ bool addAlphaBitmap(const Common::String &filename);
+
+ /**
* Adds a new TextStep from the ThemeParser. This will be deprecated/removed once the
* new Font API is in place. FIXME: Is that so ???
*/
@@ -526,10 +551,18 @@ public:
return _bitmaps.contains(name) ? _bitmaps[name] : 0;
}
+ Graphics::TransparentSurface *getAlphaBitmap(const Common::String &name) {
+ return _abitmaps.contains(name) ? _abitmaps[name] : 0;
+ }
+
const Graphics::Surface *getImageSurface(const Common::String &name) const {
return _bitmaps.contains(name) ? _bitmaps[name] : 0;
}
+ const Graphics::TransparentSurface *getAImageSurface(const Common::String &name) const {
+ return _abitmaps.contains(name) ? _abitmaps[name] : 0;
+ }
+
/**
* Interface for the Theme Parser: Creates a new cursor by loading the given
* bitmap and sets it as the active cursor.
@@ -616,6 +649,7 @@ protected:
bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0));
void queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha);
void queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &clippingRect, const Common::Rect &r, bool alpha);
+ void queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha);
/**
* DEBUG: Draws a white square and writes some text next to it.
@@ -656,10 +690,10 @@ protected:
GUI::ThemeEval *_themeEval;
/** Main screen surface. This is blitted straight into the overlay. */
- Graphics::Surface _screen;
+ Graphics::TransparentSurface _screen;
/** Backbuffer surface. Stores previous states of the screen to blit back */
- Graphics::Surface _backBuffer;
+ Graphics::TransparentSurface _backBuffer;
/** Sets whether the current drawing is being buffered (stored for later
processing) or drawn directly to the screen. */
@@ -688,6 +722,7 @@ protected:
TextColorData *_textColors[kTextColorMAX];
ImagesMap _bitmaps;
+ AImagesMap _abitmaps;
Graphics::PixelFormat _overlayFormat;
#ifdef USE_RGB_COLOR
Graphics::PixelFormat _cursorFormat;
diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp
index bd5b406ca8..bd0d2c4898 100644
--- a/gui/ThemeParser.cpp
+++ b/gui/ThemeParser.cpp
@@ -241,6 +241,18 @@ bool ThemeParser::parserCallback_bitmap(ParserNode *node) {
return true;
}
+bool ThemeParser::parserCallback_alphabitmap(ParserNode *node) {
+ if (resolutionCheck(node->values["resolution"]) == false) {
+ node->ignore = true;
+ return true;
+ }
+
+ if (!_theme->addAlphaBitmap(node->values["filename"]))
+ return parserError("Error loading Bitmap file '" + node->values["filename"] + "'");
+
+ return true;
+}
+
bool ThemeParser::parserCallback_text(ParserNode *node) {
Graphics::TextAlign alignH;
GUI::ThemeEngine::TextAlignVertical alignV;
@@ -323,6 +335,8 @@ static Graphics::DrawingFunctionCallback getDrawingFunctionCallback(const Common
return &Graphics::VectorRenderer::drawCallback_BITMAP;
if (name == "cross")
return &Graphics::VectorRenderer::drawCallback_CROSS;
+ if (name == "alphabitmap")
+ return &Graphics::VectorRenderer::drawCallback_ALPHABITMAP;
return 0;
}
@@ -448,6 +462,58 @@ bool ThemeParser::parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawst
return parserError("The given filename hasn't been loaded into the GUI.");
}
+ if (functionName == "alphabitmap") {
+ if (!stepNode->values.contains("file"))
+ return parserError("Need to specify a filename for AlphaBitmap blitting.");
+
+ drawstep->blitAlphaSrc = _theme->getAlphaBitmap(stepNode->values["file"]);
+
+ if (!drawstep->blitAlphaSrc)
+ return parserError("The given filename hasn't been loaded into the GUI.");
+
+ if (stepNode->values.contains("autoscale")) {
+ if (stepNode->values["autoscale"] == "true" || stepNode->values["autoscale"] == "stretch") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleStretch;
+ } else if (stepNode->values["autoscale"] == "fit") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleFit;
+ } else if (stepNode->values["autoscale"] == "9patch") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleNinePatch;
+ } else {
+ drawstep->autoscale = ThemeEngine::kAutoScaleNone;
+ }
+ }
+
+ if (stepNode->values.contains("xpos")) {
+ val = stepNode->values["xpos"];
+
+ if (parseIntegerKey(val, 1, &x))
+ drawstep->x = x;
+ else if (val == "center")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignCenter;
+ else if (val == "left")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignLeft;
+ else if (val == "right")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignRight;
+ else
+ return parserError("Invalid value for X Position");
+ }
+
+ if (stepNode->values.contains("ypos")) {
+ val = stepNode->values["ypos"];
+
+ if (parseIntegerKey(val, 1, &x))
+ drawstep->y = x;
+ else if (val == "center")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignCenter;
+ else if (val == "top")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignTop;
+ else if (val == "bottom")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignBottom;
+ else
+ return parserError("Invalid value for Y Position");
+ }
+ }
+
if (functionName == "roundedsq" || functionName == "circle" || functionName == "tab") {
if (stepNode->values.contains("radius") && stepNode->values["radius"] == "auto") {
drawstep->radius = 0xFF;
diff --git a/gui/ThemeParser.h b/gui/ThemeParser.h
index 360e3da009..155731467f 100644
--- a/gui/ThemeParser.h
+++ b/gui/ThemeParser.h
@@ -80,6 +80,10 @@ protected:
XML_PROP(filename, true)
XML_PROP(resolution, false)
KEY_END()
+ XML_KEY(alphabitmap)
+ XML_PROP(filename, true)
+ XML_PROP(resolution, false)
+ KEY_END()
KEY_END()
XML_KEY(cursor)
@@ -142,6 +146,7 @@ protected:
XML_PROP(padding, false)
XML_PROP(orientation, false)
XML_PROP(file, false)
+ XML_PROP(autoscale, false)
KEY_END()
XML_KEY(text)
@@ -224,6 +229,7 @@ protected:
bool parserCallback_drawdata(ParserNode *node);
bool parserCallback_bitmaps(ParserNode *node) { return true; }
bool parserCallback_bitmap(ParserNode *node);
+ bool parserCallback_alphabitmap(ParserNode *node);
bool parserCallback_cursor(ParserNode *node);
diff --git a/gui/animation/AccelerateInterpolator.h b/gui/animation/AccelerateInterpolator.h
new file mode 100644
index 0000000000..31494d369f
--- /dev/null
+++ b/gui/animation/AccelerateInterpolator.h
@@ -0,0 +1,45 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
+#define GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
+
+#include "gui/animation/Interpolator.h"
+
+namespace GUI {
+
+class AccelerateInterpolator: public Interpolator {
+public:
+ AccelerateInterpolator() {}
+ virtual ~AccelerateInterpolator() {}
+
+ virtual float interpolate(float linearValue) {
+ return pow(linearValue, 2);
+ }
+
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_ACCELERATEINTERPOLATOR_H */
diff --git a/gui/animation/AlphaAnimation.h b/gui/animation/AlphaAnimation.h
new file mode 100644
index 0000000000..82cf3d4ba9
--- /dev/null
+++ b/gui/animation/AlphaAnimation.h
@@ -0,0 +1,53 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_ANIMATION_H
+#define GUI_ANIMATION_ANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class AlphaAnimation: public Animation {
+public:
+ AlphaAnimation() {}
+ virtual ~AlphaAnimation() {}
+ float getEndAlpha() const { return _endAlpha; }
+ void setEndAlpha(float endAlpha) { _endAlpha = endAlpha; }
+ float getStartAlpha() const { return _startAlpha; }
+ void setStartAlpha(float startAlpha) { _startAlpha = startAlpha; }
+
+protected:
+ virtual void updateInternal(Drawable* drawable, float interpolation) {
+ // Calculate alpha value based on properties and interpolation
+ drawable->setAlpha(_startAlpha * (1 - interpolation) + _endAlpha * interpolation);
+ }
+
+ float _startAlpha;
+ float _endAlpha;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_ANIMATION_H */
diff --git a/gui/animation/Animation.cpp b/gui/animation/Animation.cpp
new file mode 100644
index 0000000000..ee4d900b4d
--- /dev/null
+++ b/gui/animation/Animation.cpp
@@ -0,0 +1,98 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+Animation::Animation()
+ : _startTime(0), _duration(0), _finished(false), _finishOnEnd(true) {
+}
+
+Animation::~Animation() {
+}
+
+void Animation::start(long currentTime) {
+ _finished = false;
+ _startTime = currentTime;
+}
+
+void Animation::setDuration(long duration) {
+ _duration = duration;
+}
+
+void Animation::update(Drawable *drawable, long currentTime) {
+ float interpolation;
+
+ if (currentTime < _startTime) {
+ // If the start time is in the future, nothing changes - the interpolated value is 0
+ interpolation = 0;
+ } else if (currentTime > _startTime + _duration) {
+ // If the animation is finished, the interpolated value is 1 and the animation is marked as finished
+ interpolation = 1;
+ finishAnimation();
+ } else {
+ // Calculate the interpolated value
+ interpolation = (currentTime - _startTime) / (float) (_duration);
+ }
+
+ // Activate the interpolator if present
+ if (_interpolator.get() != NULL) {
+ interpolation = _interpolator->interpolate(interpolation);
+ }
+
+ updateInternal(drawable, interpolation);
+}
+
+void Animation::finishAnimation() {
+ if (_finishOnEnd) {
+ _finished = true;
+ }
+}
+
+void Animation::updateInternal(Drawable *drawable, float interpolation) {
+ // Default implementation
+}
+
+bool Animation::isFinished() const {
+ return _finished;
+}
+
+bool Animation::isFinishOnEnd() const {
+ return _finishOnEnd;
+}
+
+void Animation::setFinishOnEnd(bool finishOnEnd) {
+ _finishOnEnd = finishOnEnd;
+}
+
+InterpolatorPtr Animation::getInterpolator() const {
+ return _interpolator;
+}
+
+void Animation::setInterpolator(InterpolatorPtr interpolator) {
+ _interpolator = interpolator;
+}
+
+} // End of namespace GUI
diff --git a/gui/animation/Animation.h b/gui/animation/Animation.h
new file mode 100644
index 0000000000..300720b419
--- /dev/null
+++ b/gui/animation/Animation.h
@@ -0,0 +1,76 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_ANIMATION_H
+#define GUI_ANIMATION_ANIMATION_H
+
+#include "gui/animation/Interpolator.h"
+
+namespace GUI {
+
+class Drawable;
+
+class Animation {
+public:
+ Animation();
+ virtual ~Animation() = 0;
+
+ virtual void update(Drawable *drawable, long currentTime);
+
+ /**
+ * Set start time in millis
+ */
+ virtual void start(long currentTime);
+
+ /**
+ * Set duration in millis
+ */
+ virtual void setDuration(long duration);
+
+ virtual bool isFinished() const;
+
+ bool isFinishOnEnd() const;
+
+ void setFinishOnEnd(bool finishOnEnd);
+
+ InterpolatorPtr getInterpolator() const;
+ void setInterpolator(InterpolatorPtr interpolator);
+
+protected:
+ void finishAnimation();
+
+ virtual void updateInternal(Drawable *drawable, float interpolation);
+
+ long _startTime;
+ long _duration;
+ bool _finished;
+ bool _finishOnEnd;
+ InterpolatorPtr _interpolator;
+};
+
+typedef Common::SharedPtr<Animation> AnimationPtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_ANIMATION_H */
diff --git a/gui/animation/DeccelerateInterpolator.h b/gui/animation/DeccelerateInterpolator.h
new file mode 100644
index 0000000000..e25ff6a4ba
--- /dev/null
+++ b/gui/animation/DeccelerateInterpolator.h
@@ -0,0 +1,41 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
+#define GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
+
+#include "gui/animation/Interpolator.h"
+
+namespace GUI {
+
+class DeccelerateInterpolator: public Interpolator {
+public:
+ DeccelerateInterpolator() {}
+ virtual ~DeccelerateInterpolator() {}
+ virtual float interpolate(float linearValue) { return sqrt(linearValue); }
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_DECCELERATEINTERPOLATOR_H */
diff --git a/gui/animation/Drawable.h b/gui/animation/Drawable.h
new file mode 100644
index 0000000000..cf604270aa
--- /dev/null
+++ b/gui/animation/Drawable.h
@@ -0,0 +1,109 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_DRAWABLE_H
+#define GUI_ANIMATION_DRAWABLE_H
+
+#include "common/ptr.h"
+#include "graphics/transparent_surface.h"
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class Animation;
+typedef Common::SharedPtr<Animation> AnimationPtr;
+
+class Drawable {
+public:
+ Drawable() :
+ _bitmap(NULL), _positionX(0), _positionY(0), _width(0), _height(0), _alpha(1),
+ _usingSnapshot(false), _shouldCenter(false) {
+ _displayRatio = 1.0;
+ }
+
+ virtual ~Drawable() {
+ if (_usingSnapshot)
+ delete _bitmap;
+ }
+
+ void updateAnimation(long currentTime) {
+ if (_animation.get() != NULL) {
+ _animation->update(this, currentTime);
+ }
+ }
+
+ bool isAnimationFinished() {
+ if (_animation.get() != NULL)
+ return _animation->isFinished();
+
+ return false;
+ }
+
+ float getAlpha() const { return _alpha; }
+ void setAlpha(float alpha) { _alpha = alpha; }
+ AnimationPtr getAnimation() const { return _animation; }
+ void setAnimation(AnimationPtr animation) { _animation = animation; }
+ Graphics::TransparentSurface *getBitmap() const { return _bitmap; }
+ void setBitmap(Graphics::TransparentSurface *bitmap) { _bitmap = bitmap; }
+ float getPositionX() const { return _positionX; }
+ void setPositionX(float positionX) { _positionX = positionX; }
+ float getPositionY() const { return _positionY; }
+ void setPositionY(float positionY) { _positionY = positionY; }
+ virtual float getWidth() const { return _width; }
+ void setWidth(float width) { _width = width; }
+
+ virtual float getHeight() const {
+ if (_height == 0)
+ return getWidth() * _bitmap->getRatio() * _displayRatio;
+
+ return _height;
+ }
+
+ void setHeight(float height) { _height = height; }
+ void setDisplayRatio(float ratio) { _displayRatio = ratio; }
+ inline bool shouldCenter() const { return _shouldCenter; }
+ void setShouldCenter(bool shouldCenter) { _shouldCenter = shouldCenter; }
+
+protected:
+ bool _usingSnapshot;
+
+private:
+ Graphics::TransparentSurface *_bitmap;
+ float _positionX;
+ float _positionY;
+ float _width;
+ float _height;
+ float _alpha;
+ bool _shouldCenter;
+ AnimationPtr _animation;
+
+ float _displayRatio;
+};
+
+typedef Common::SharedPtr<Drawable> DrawablePtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_DRAWABLE_H */
diff --git a/gui/animation/Interpolator.h b/gui/animation/Interpolator.h
new file mode 100644
index 0000000000..72d7acb8d4
--- /dev/null
+++ b/gui/animation/Interpolator.h
@@ -0,0 +1,44 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_INTERPOLATOR_H
+#define GUI_ANIMATION_INTERPOLATOR_H
+
+#include "common/ptr.h"
+
+namespace GUI {
+
+class Interpolator {
+public:
+ Interpolator() {}
+ virtual ~Interpolator() {}
+
+ virtual float interpolate(float linearValue) = 0;
+};
+
+typedef Common::SharedPtr<Interpolator> InterpolatorPtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_INTERPOLATOR_H */
diff --git a/gui/animation/ParallelAnimation.h b/gui/animation/ParallelAnimation.h
new file mode 100644
index 0000000000..ce1f599fb1
--- /dev/null
+++ b/gui/animation/ParallelAnimation.h
@@ -0,0 +1,72 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_PARALLELANIMATION_H
+#define GUI_ANIMATION_PARALLELANIMATION_H
+
+#include "gui/animation/Animation.h"
+#include "common/array.h"
+
+namespace GUI {
+
+class ParallelAnimation: public Animation {
+public:
+ ParallelAnimation() {}
+ virtual ~ParallelAnimation() {}
+
+ virtual void addAnimation(AnimationPtr animation) {
+ _animations.push_back(animation);
+ }
+
+ virtual void update(Drawable *drawable, long currentTime) {
+ for (AnimationPtr anim : _animations) {
+ anim->update(drawable, currentTime);
+ if (anim->isFinished()) {
+ finishAnimation();
+ }
+ }
+ }
+
+ virtual void start(long currentTime) {
+ Animation::start(currentTime);
+
+ for (AnimationPtr anim : _animations)
+ anim->start(currentTime);
+ }
+
+ virtual void setDuration(long duration) {
+ Animation::setDuration(duration);
+
+ for (AnimationPtr anim : _animations)
+ anim->setDuration(duration);
+ }
+
+private:
+
+ Common::Array<AnimationPtr> _animations;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_PARALLELANIMATION_H */
diff --git a/gui/animation/RepeatAnimationWrapper.cpp b/gui/animation/RepeatAnimationWrapper.cpp
new file mode 100644
index 0000000000..a7e1413093
--- /dev/null
+++ b/gui/animation/RepeatAnimationWrapper.cpp
@@ -0,0 +1,52 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/RepeatAnimationWrapper.h"
+
+namespace GUI {
+
+void RepeatAnimationWrapper::update(Drawable* drawable, long currentTime) {
+ // Update wrapped animation
+ _animation->update(drawable, currentTime);
+
+ // If the animation is finished, increase the repeat count and restart it if needed
+ if (_animation->isFinished()) {
+ ++_repeatCount;
+ if (_timesToRepeat > 0 && _repeatCount >= _timesToRepeat) {
+ finishAnimation();
+ } else {
+ _animation->start(currentTime);
+ }
+ }
+}
+
+void RepeatAnimationWrapper::start(long currentTime) {
+ Animation::start(currentTime);
+ _repeatCount = 0;
+
+ // Start wrapped animation
+ _animation->start(currentTime);
+}
+
+} // End of namespace GUI
diff --git a/gui/animation/RepeatAnimationWrapper.h b/gui/animation/RepeatAnimationWrapper.h
new file mode 100644
index 0000000000..3d766dd1c5
--- /dev/null
+++ b/gui/animation/RepeatAnimationWrapper.h
@@ -0,0 +1,61 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
+#define GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class RepeatAnimationWrapper: public Animation {
+public:
+ /**
+ * Animation - animation to repeat
+ *
+ * timesToRepeat - 0 means infinite
+ */
+ RepeatAnimationWrapper(AnimationPtr animation, uint16 timesToRepeat) :
+ _animation(animation), _timesToRepeat(timesToRepeat) {}
+
+ virtual ~RepeatAnimationWrapper() {}
+
+ virtual void update(Drawable* drawable, long currentTime);
+
+ /**
+ * Set start time in millis
+ */
+ virtual void start(long currentTime);
+
+private:
+ uint16 _timesToRepeat;
+ uint16 _repeatCount;
+
+ AnimationPtr _animation;
+
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_REPEATANIMATIONWRAPPER_H */
diff --git a/gui/animation/ScaleAnimation.h b/gui/animation/ScaleAnimation.h
new file mode 100644
index 0000000000..80a4ae6305
--- /dev/null
+++ b/gui/animation/ScaleAnimation.h
@@ -0,0 +1,69 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_SCALEANIMATION_H
+#define GUI_ANIMATION_SCALEANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class ScaleAnimation: public Animation {
+public:
+ ScaleAnimation() : _endWidth(0), _endWidthFactor(0) {}
+
+ virtual ~ScaleAnimation() {}
+
+ float getEndWidth() const { return _endWidth; }
+ void setEndWidth(float endWidth) { _endWidth = endWidth; }
+ float getEndWidthFactor() const { return _endWidthFactor; }
+ void setEndWidthFactor(float endWidthFactor) { _endWidthFactor = endWidthFactor; }
+ float getStartWidth() const { return _startWidth; }
+ void setStartWidth(float startWidth) { _startWidth = startWidth; }
+
+ void updateInternal(Drawable *drawable, float interpolation) {
+ // If start width was set as 0 -> use the current width as the start dimension
+ if (_startWidth == 0)
+ _startWidth = drawable->getWidth();
+
+ // If end width was set as 0 - multiply the start width by the given factor
+ if (_endWidth == 0)
+ _endWidth = _startWidth * _endWidthFactor;
+
+ // Calculate width based on interpolation
+ float width = _startWidth * (1 - interpolation) + _endWidth * interpolation;
+ drawable->setWidth(width);
+ }
+
+private:
+ virtual void updateInternal(Drawable *drawable, float interpolation);
+ float _startWidth;
+ float _endWidth;
+ float _endWidthFactor;
+};
+
+} // End of namespace GUI
+
+
+#endif /* GUI_ANIMATION_SCALEANIMATION_H */
diff --git a/gui/animation/SequenceAnimationComposite.cpp b/gui/animation/SequenceAnimationComposite.cpp
new file mode 100644
index 0000000000..9ecfeebc8c
--- /dev/null
+++ b/gui/animation/SequenceAnimationComposite.cpp
@@ -0,0 +1,72 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/SequenceAnimationComposite.h"
+
+namespace GUI {
+
+void SequenceAnimationComposite::start(long currentTime) {
+ Animation::start(currentTime);
+
+ // The first animation in the sequence should a start time equal to this sequence
+ if (_sequence.size() >= 1)
+ _sequence[0]->start(currentTime);
+
+ // Set the index to 0
+ _index = 0;
+}
+
+void SequenceAnimationComposite::addAnimation(AnimationPtr animation) {
+ _sequence.push_back(animation);
+}
+
+void SequenceAnimationComposite::update(Drawable *drawable, long currentTime) {
+ uint16 sequenceSize = _sequence.size();
+
+ // Check index bounds
+ if (_index >= sequenceSize)
+ return;
+
+ // Get the current animation in the sequence
+ AnimationPtr anim = _sequence[_index];
+
+ // Update the drawable
+ anim->update(drawable, currentTime);
+
+ // Check if the current animation is finished
+ if (anim->isFinished()) {
+ // Increase the index - move to the next animation
+ ++_index;
+
+ if (_index >= sequenceSize) {
+ // Finished the sequence
+ finishAnimation();
+ } else {
+ // Set the start time for the next animation
+ _sequence[_index]->start(currentTime);
+ }
+ }
+}
+
+} // End of namespace GUI
diff --git a/gui/animation/SequenceAnimationComposite.h b/gui/animation/SequenceAnimationComposite.h
new file mode 100644
index 0000000000..4ec0331751
--- /dev/null
+++ b/gui/animation/SequenceAnimationComposite.h
@@ -0,0 +1,51 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_SEQUENCEANIMATION_H
+#define GUI_ANIMATION_SEQUENCEANIMATION_H
+
+#include "gui/animation/Animation.h"
+#include "common/array.h"
+
+namespace GUI {
+
+class SequenceAnimationComposite: public Animation {
+public:
+ SequenceAnimationComposite() {}
+ virtual ~SequenceAnimationComposite() {}
+
+ virtual void addAnimation(AnimationPtr animation);
+
+ virtual void update(Drawable* drawable, long currentTime);
+
+ virtual void start(long currentTime);
+
+private:
+ uint16 _index;
+ Common::Array<AnimationPtr> _sequence;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_SEQUENCEANIMATION_H */
diff --git a/gui/animation/WaitForConditionAnimation.h b/gui/animation/WaitForConditionAnimation.h
new file mode 100644
index 0000000000..5a67a37493
--- /dev/null
+++ b/gui/animation/WaitForConditionAnimation.h
@@ -0,0 +1,71 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
+#define GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class Condition {
+
+public:
+ virtual ~Condition() {}
+
+ virtual bool evaluate() = 0;
+};
+
+typedef Common::SharedPtr<Condition> ConditionPtr;
+
+/**
+ * Used for delaying the animation sequence until a certain condition has been met
+ */
+class WaitForConditionAnimation: public Animation {
+public:
+ WaitForConditionAnimation() {}
+ virtual ~WaitForConditionAnimation() {}
+
+ virtual void update(Drawable *drawable, long currentTime) {
+ // Check the condition - if it has been met, finish.
+ if (_condition.get() != NULL && _condition->evaluate()) {
+ finishAnimation();
+ }
+ }
+
+ ConditionPtr getCondition() const {
+ return _condition;
+ }
+
+ void setCondition(ConditionPtr condition) {
+ _condition = condition;
+ }
+
+private:
+ ConditionPtr _condition;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_WAITFORCONDITIONANIMATION_H */
diff --git a/gui/browser.cpp b/gui/browser.cpp
index 83e240a5bc..19fa791cee 100644
--- a/gui/browser.cpp
+++ b/gui/browser.cpp
@@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(const char *title, bool dirBrowser)
_isDirBrowser = dirBrowser;
_fileList = NULL;
_currentPath = NULL;
- _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
+ _showHidden = false;
// Headline - TODO: should be customizable during creation time
new StaticTextWidget(this, "Browser.Headline", title);
@@ -85,8 +85,10 @@ void BrowserDialog::open() {
if (!_node.isDirectory())
_node = Common::FSNode(".");
- // Alway refresh file list
- updateListing();
+ _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
+ _showHiddenWidget->setState(_showHidden);
+
+ // At this point the file list has already been refreshed by the kHiddenCmd handler
}
void BrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
diff --git a/gui/credits.h b/gui/credits.h
index c2c4c84ec6..720f917182 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -845,7 +845,7 @@ static const char *credits[] = {
"C2""For the original Reinherit (SAGA) code",
"C0""Sander Buskens",
"C2""For his work on the initial reversing of Monkey2",
-"C0""Canadacow",
+"C0""Dean Beeler",
"C2""For the original MT-32 emulator",
"C0""Kevin Carnes",
"C2""For Scumm16, the basis of ScummVM's older gfx codecs",
@@ -863,18 +863,18 @@ static const char *credits[] = {
"C2""For contributing some GUI icons",
"C0""Till Kresslein",
"C2""For design of modern ScummVM GUI",
-"C0""Jezar",
+"C0""Jezar Wakefield",
"C2""For his freeverb filter implementation",
"C0""Jim Leiterman",
"C2""Various info on his FM-TOWNS/Marty SCUMM ports",
-"C0""lloyd",
+"C0""Lloyd Rosen",
"C2""For deep tech details about C64 Zak & MM",
"C0""Sarien Team",
"C2""Original AGI engine code",
"A0""Jimmi Thogersen",
"C0""Jimmi Th\370gersen",
"C2""For ScummRev, and much obscure code/documentation",
-"C0""Tristan",
+"C0""Tristan Matthews",
"C2""For additional work on the original MT-32 emulator",
"C0""James Woodcock",
"C2""Soundtrack enhancements",
diff --git a/gui/debugger.cpp b/gui/debugger.cpp
index 72d05e2973..7595efcfbc 100644
--- a/gui/debugger.cpp
+++ b/gui/debugger.cpp
@@ -584,7 +584,7 @@ bool Debugger::cmdMd5(int argc, const char **argv) {
length = atoi(argv[2]);
paramOffset = 2;
}
-
+
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
@@ -624,7 +624,7 @@ bool Debugger::cmdMd5Mac(int argc, const char **argv) {
length = atoi(argv[2]);
paramOffset = 2;
}
-
+
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
diff --git a/gui/downloaddialog.cpp b/gui/downloaddialog.cpp
new file mode 100644
index 0000000000..bcbe956ae2
--- /dev/null
+++ b/gui/downloaddialog.cpp
@@ -0,0 +1,272 @@
+/* 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/downloaddialog.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/connection/islimited.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+#include "engines/metaengine.h"
+#include "gui/browser.h"
+#include "gui/chooser.h"
+#include "gui/editgamedialog.h"
+#include "gui/launcher.h"
+#include "gui/message.h"
+#include "gui/remotebrowser.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/list.h"
+
+namespace GUI {
+
+enum {
+ kDownloadDialogButtonCmd = 'Dldb'
+};
+
+DownloadDialog::DownloadDialog(uint32 storageId, LauncherDialog *launcher) :
+ Dialog("GlobalOptions_Cloud_DownloadDialog"), _launcher(launcher), _close(false) {
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ _browser = new BrowserDialog(_("Select directory where to download game data"), true);
+ _remoteBrowser = new RemoteBrowserDialog(_("Select directory with game data"));
+
+ _remoteDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.RemoteDirectory", _("From: "));
+ _localDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.LocalDirectory", _("To: "));
+ uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
+ _progressBar = new SliderWidget(this, "GlobalOptions_Cloud_DownloadDialog.ProgressBar");
+ _progressBar->setMinValue(0);
+ _progressBar->setMaxValue(100);
+ _progressBar->setValue(progress);
+ _progressBar->setEnabled(false);
+ _percentLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.PercentText", Common::String::format("%u %%", progress));
+ _downloadSizeLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSize", "");
+ _downloadSpeedLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSpeed", "");
+ if (g_system->getOverlayWidth() > 320)
+ _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _("Cancel download"), 0, kDownloadDialogButtonCmd);
+ else
+ _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _c("Cancel download", "lowres"), 0, kDownloadDialogButtonCmd);
+
+ _closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.CloseButton", _("Hide"), 0, kCloseCmd);
+ refreshWidgets();
+
+ CloudMan.setDownloadTarget(this);
+}
+
+DownloadDialog::~DownloadDialog() {
+ CloudMan.setDownloadTarget(nullptr);
+}
+
+void DownloadDialog::open() {
+ Dialog::open();
+ CloudMan.setDownloadTarget(this);
+ if (!CloudMan.isDownloading())
+ if (!selectDirectories())
+ close();
+ reflowLayout();
+ draw();
+}
+
+void DownloadDialog::close() {
+ CloudMan.setDownloadTarget(nullptr);
+ Dialog::close();
+}
+
+void DownloadDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kDownloadDialogButtonCmd:
+ {
+ CloudMan.setDownloadTarget(nullptr);
+ CloudMan.cancelDownload();
+ close();
+ break;
+ }
+ case kDownloadProgressCmd:
+ if (!_close) {
+ refreshWidgets();
+ draw();
+ }
+ break;
+ case kDownloadEndedCmd:
+ _close = true;
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+bool DownloadDialog::selectDirectories() {
+ if (Networking::Connection::isLimited()) {
+ MessageDialog alert(_("It looks like your connection is limited. "
+ "Do you really want to download files with it?"), _("Yes"), _("No"));
+ if (alert.runModal() != GUI::kMessageOK)
+ return false;
+ }
+
+ //first user should select remote directory to download
+ if (_remoteBrowser->runModal() <= 0)
+ return false;
+
+ Cloud::StorageFile remoteDirectory = _remoteBrowser->getResult();
+
+ //now user should select local directory to download into
+ if (_browser->runModal() <= 0)
+ return false;
+
+ Common::FSNode dir(_browser->getResult());
+ Common::FSList files;
+ if (!dir.getChildren(files, Common::FSNode::kListAll)) {
+ MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
+ alert.runModal();
+ return false;
+ }
+
+ //check that there is no file with the remote directory's name in the local one
+ for (Common::FSList::iterator i = files.begin(); i != files.end(); ++i) {
+ if (i->getName().equalsIgnoreCase(remoteDirectory.name())) {
+ //if there is, ask user whether it's OK
+ if (!i->isDirectory()) {
+ GUI::MessageDialog alert(_("Cannot create a directory to download - the specified directory has a file with the same name."), _("OK"));
+ alert.runModal();
+ return false;
+ }
+ GUI::MessageDialog alert(
+ Common::String::format(_("The \"%s\" already exists in the specified directory.\nDo you really want to download files into that directory?"), remoteDirectory.name().c_str()),
+ _("Yes"),
+ _("No")
+ );
+ if (alert.runModal() != GUI::kMessageOK)
+ return false;
+ break;
+ }
+ }
+
+ //make a local path
+ Common::String localPath = dir.getPath();
+
+ //simple heuristic to determine which path separator to use
+ if (localPath.size() && localPath.lastChar() != '/' && localPath.lastChar() != '\\') {
+ int backslashes = 0;
+ for (uint32 i = 0; i < localPath.size(); ++i)
+ if (localPath[i] == '/')
+ --backslashes;
+ else if (localPath[i] == '\\')
+ ++backslashes;
+
+ if (backslashes > 0)
+ localPath += '\\' + remoteDirectory.name();
+ else
+ localPath += '/' + remoteDirectory.name();
+ } else {
+ localPath += remoteDirectory.name();
+ }
+
+ CloudMan.startDownload(remoteDirectory.path(), localPath);
+ CloudMan.setDownloadTarget(this);
+ _localDirectory = localPath;
+ return true;
+}
+
+void DownloadDialog::handleTickle() {
+ if (_close) {
+ if (_launcher)
+ _launcher->doGameDetection(_localDirectory);
+ close();
+ _close = false;
+ return;
+ }
+
+ int32 progress = (int32)(100 * CloudMan.getDownloadingProgress());
+ if (_progressBar->getValue() != progress) {
+ refreshWidgets();
+ draw();
+ }
+
+ Dialog::handleTickle();
+}
+
+void DownloadDialog::reflowLayout() {
+ Dialog::reflowLayout();
+ refreshWidgets();
+}
+
+namespace {
+Common::String getHumanReadableBytes(uint64 bytes, Common::String &unitsOut) {
+ Common::String result = Common::String::format("%lu", bytes);
+ unitsOut = "B";
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ result = Common::String::format("%lu", bytes);
+ unitsOut = "KB";
+ }
+
+ double floating = bytes;
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "MB";
+ }
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "GB";
+ }
+
+ if (bytes >= 1024) { // woah
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "TB";
+ }
+
+ // print one digit after floating point
+ result = Common::String::format("%.1f", floating);
+ return result;
+}
+}
+
+Common::String DownloadDialog::getSizeLabelText() {
+ Common::String downloaded, downloadedUnits, total, totalUnits;
+ downloaded = getHumanReadableBytes(CloudMan.getDownloadBytesNumber(), downloadedUnits);
+ total = getHumanReadableBytes(CloudMan.getDownloadTotalBytesNumber(), totalUnits);
+ return Common::String::format(_("Downloaded %s %s / %s %s"), downloaded.c_str(), _(downloadedUnits.c_str()), total.c_str(), _(totalUnits.c_str()));
+}
+
+Common::String DownloadDialog::getSpeedLabelText() {
+ Common::String speed, speedUnits;
+ speed = getHumanReadableBytes(CloudMan.getDownloadSpeed(), speedUnits);
+ speedUnits += "/s";
+ return Common::String::format(_("Download speed: %s %s"), speed.c_str(), _(speedUnits.c_str()));
+}
+
+void DownloadDialog::refreshWidgets() {
+ _localDirectory = CloudMan.getDownloadLocalDirectory();
+ _remoteDirectoryLabel->setLabel(_("From: ") + CloudMan.getDownloadRemoteDirectory());
+ _localDirectoryLabel->setLabel(_("To: ") + _localDirectory);
+ uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
+ _percentLabel->setLabel(Common::String::format("%u %%", progress));
+ _downloadSizeLabel->setLabel(getSizeLabelText());
+ _downloadSpeedLabel->setLabel(getSpeedLabelText());
+ _progressBar->setValue(progress);
+}
+
+} // End of namespace GUI
diff --git a/gui/downloaddialog.h b/gui/downloaddialog.h
new file mode 100644
index 0000000000..9080717195
--- /dev/null
+++ b/gui/downloaddialog.h
@@ -0,0 +1,81 @@
+/* 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_DOWNLOADDIALOG_H
+#define GUI_DOWNLOADDIALOG_H
+
+#include "gui/dialog.h"
+#include "common/str.h"
+
+namespace GUI {
+class LauncherDialog;
+
+class CommandSender;
+class EditTextWidget;
+class StaticTextWidget;
+class ButtonWidget;
+class SliderWidget;
+class BrowserDialog;
+class RemoteBrowserDialog;
+
+enum DownloadProgress {
+ kDownloadProgressCmd = 'DLPR',
+ kDownloadEndedCmd = 'DLEN'
+};
+
+class DownloadDialog : public Dialog {
+ LauncherDialog *_launcher;
+ BrowserDialog *_browser;
+ RemoteBrowserDialog *_remoteBrowser;
+
+ StaticTextWidget *_remoteDirectoryLabel;
+ StaticTextWidget *_localDirectoryLabel;
+ StaticTextWidget *_percentLabel;
+ StaticTextWidget *_downloadSizeLabel;
+ StaticTextWidget *_downloadSpeedLabel;
+ SliderWidget *_progressBar;
+ ButtonWidget *_cancelButton;
+ ButtonWidget *_closeButton;
+
+ Common::String _localDirectory;
+ bool _close;
+
+ Common::String getSizeLabelText();
+ Common::String getSpeedLabelText();
+
+ void refreshWidgets();
+ bool selectDirectories();
+
+public:
+ DownloadDialog(uint32 storageId, LauncherDialog *launcher);
+ virtual ~DownloadDialog();
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+ virtual void reflowLayout();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/editgamedialog.cpp b/gui/editgamedialog.cpp
new file mode 100644
index 0000000000..fbe76d228a
--- /dev/null
+++ b/gui/editgamedialog.cpp
@@ -0,0 +1,550 @@
+/* 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/editgamedialog.h"
+
+#include "common/config-manager.h"
+#include "common/gui_options.h"
+#include "common/translation.h"
+#include "common/system.h"
+
+#include "gui/browser.h"
+#include "gui/message.h"
+#ifdef ENABLE_EVENTRECORDER
+#include "gui/onscreendialog.h"
+#include "gui/recorderdialog.h"
+#include "gui/EventRecorder.h"
+#endif
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/popup.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
+
+using Common::ConfigManager;
+
+namespace GUI {
+
+enum {
+ kStartCmd = 'STRT',
+ kAboutCmd = 'ABOU',
+ kOptionsCmd = 'OPTN',
+ kAddGameCmd = 'ADDG',
+ kEditGameCmd = 'EDTG',
+ kRemoveGameCmd = 'REMG',
+ kLoadGameCmd = 'LOAD',
+ kQuitCmd = 'QUIT',
+ kSearchCmd = 'SRCH',
+ kListSearchCmd = 'LSSR',
+ kSearchClearCmd = 'SRCL',
+
+ kCmdGlobalGraphicsOverride = 'OGFX',
+ kCmdGlobalAudioOverride = 'OSFX',
+ kCmdGlobalMIDIOverride = 'OMID',
+ kCmdGlobalMT32Override = 'OM32',
+ kCmdGlobalVolumeOverride = 'OVOL',
+
+ kCmdChooseSoundFontCmd = 'chsf',
+
+ kCmdExtraBrowser = 'PEXT',
+ kCmdExtraPathClear = 'PEXC',
+ kCmdGameBrowser = 'PGME',
+ kCmdSaveBrowser = 'PSAV',
+ kCmdSavePathClear = 'PSAC'
+};
+
+/*
+* TODO: Clean up this ugly design: we subclass EditTextWidget to perform
+* input validation. It would be much more elegant to use a decorator pattern,
+* or a validation callback, or something like that.
+*/
+class DomainEditTextWidget : public EditTextWidget {
+public:
+ DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0)
+ : EditTextWidget(boss, name, text, tooltip) {}
+
+protected:
+ bool tryInsertChar(byte c, int pos) {
+ if (Common::isAlnum(c) || c == '-' || c == '_') {
+ _editString.insertChar(c, pos);
+ return true;
+ }
+ return false;
+ }
+};
+
+EditGameDialog::EditGameDialog(const String &domain, const String &desc)
+ : OptionsDialog(domain, "GameOptions") {
+ // Retrieve all game specific options.
+ const EnginePlugin *plugin = 0;
+ // To allow for game domains without a gameid.
+ // TODO: Is it intentional that this is still supported?
+ String gameId(ConfMan.get("gameid", domain));
+ if (gameId.empty())
+ gameId = domain;
+ // Retrieve the plugin, since we need to access the engine's MetaEngine
+ // implementation.
+ EngineMan.findGame(gameId, &plugin);
+ if (plugin) {
+ _engineOptions = (*plugin)->getExtraGuiOptions(domain);
+ } else {
+ warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str());
+ }
+
+ // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
+ String gamePath(ConfMan.get("path", _domain));
+ String extraPath(ConfMan.get("extrapath", _domain));
+ String savePath(ConfMan.get("savepath", _domain));
+
+ // GAME: Determine the description string
+ String description(ConfMan.get("description", domain));
+ if (description.empty() && !desc.empty()) {
+ description = desc;
+ }
+
+ // GUI: Add tab widget
+ TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
+
+ //
+ // 1) The game tab
+ //
+ tab->addTab(_("Game"));
+
+ // GUI: Label & edit widget for the game ID
+ if (g_system->getOverlayWidth() > 320)
+ new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line"));
+ else
+ new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line"));
+ _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line"));
+
+ // GUI: Label & edit widget for the description
+ if (g_system->getOverlayWidth() > 320)
+ new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game"));
+ else
+ new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game"));
+ _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game"));
+
+ // Language popup
+ _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English"));
+ _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English"));
+ _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG);
+ _langPopUp->appendEntry("", (uint32)Common::UNK_LANG);
+ const Common::LanguageDescription *l = Common::g_languages;
+ for (; l->code; ++l) {
+ if (checkGameGUIOptionLanguage(l->id, _guioptionsString))
+ _langPopUp->appendEntry(l->description, l->id);
+ }
+
+ // Platform popup
+ if (g_system->getOverlayWidth() > 320)
+ _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for"));
+ else
+ _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for"));
+ _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for"));
+ _platformPopUp->appendEntry(_("<default>"));
+ _platformPopUp->appendEntry("");
+ const Common::PlatformDescription *p = Common::g_platforms;
+ for (; p->code; ++p) {
+ _platformPopUp->appendEntry(p->description, p->id);
+ }
+
+ //
+ // 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"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride);
+ else
+ _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride);
+
+ addGraphicControls(tab, "GameOptions_Graphics.");
+
+ //
+ // 4) The audio tab
+ //
+ tab->addTab(_("Audio"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride);
+ else
+ _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride);
+
+ addAudioControls(tab, "GameOptions_Audio.");
+ addSubtitleControls(tab, "GameOptions_Audio.");
+
+ //
+ // 5) The volume tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Volume"));
+ else
+ tab->addTab(_c("Volume", "lowres"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride);
+ else
+ _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride);
+
+ addVolumeControls(tab, "GameOptions_Volume.");
+
+ //
+ // 6) The MIDI tab
+ //
+ _globalMIDIOverride = NULL;
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ tab->addTab(_("MIDI"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride);
+ else
+ _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride);
+
+ addMIDIControls(tab, "GameOptions_MIDI.");
+ }
+
+ //
+ // 7) The MT-32 tab
+ //
+ _globalMT32Override = NULL;
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ tab->addTab(_("MT-32"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override);
+ else
+ _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override);
+
+ addMT32Controls(tab, "GameOptions_MT32.");
+ }
+
+ //
+ // 8) The Paths tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Paths"));
+ else
+ tab->addTab(_c("Paths", "lowres"));
+
+ // These buttons have to be extra wide, or the text will be truncated
+ // in the small version of the GUI.
+
+ // GUI: Button + Label for the game path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser);
+ _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath);
+
+ // GUI: Button + Label for the additional path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
+ _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game"));
+
+ _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear);
+
+ // GUI: Button + Label for the save path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
+ _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put"));
+
+ _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
+
+ // Activate the first tab
+ tab->setActiveTab(0);
+ _tabWidget = tab;
+
+ // Add OK & Cancel buttons
+ new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd);
+ new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd);
+}
+
+void EditGameDialog::open() {
+ OptionsDialog::open();
+
+ String extraPath(ConfMan.get("extrapath", _domain));
+ if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) {
+ _extraPathWidget->setLabel(_c("None", "path"));
+ }
+
+ String savePath(ConfMan.get("savepath", _domain));
+ if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) {
+ _savePathWidget->setLabel(_("Default"));
+ }
+
+ int sel, i;
+ bool e;
+
+ // En-/disable dialog items depending on whether overrides are active or not.
+
+ e = ConfMan.hasKey("gfx_mode", _domain) ||
+ ConfMan.hasKey("render_mode", _domain) ||
+ ConfMan.hasKey("fullscreen", _domain) ||
+ ConfMan.hasKey("aspect_ratio", _domain);
+ _globalGraphicsOverride->setState(e);
+
+ e = ConfMan.hasKey("music_driver", _domain) ||
+ ConfMan.hasKey("output_rate", _domain) ||
+ ConfMan.hasKey("opl_driver", _domain) ||
+ ConfMan.hasKey("subtitles", _domain) ||
+ ConfMan.hasKey("talkspeed", _domain);
+ _globalAudioOverride->setState(e);
+
+ e = ConfMan.hasKey("music_volume", _domain) ||
+ ConfMan.hasKey("sfx_volume", _domain) ||
+ ConfMan.hasKey("speech_volume", _domain);
+ _globalVolumeOverride->setState(e);
+
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ e = ConfMan.hasKey("soundfont", _domain) ||
+ ConfMan.hasKey("multi_midi", _domain) ||
+ ConfMan.hasKey("midi_gain", _domain);
+ _globalMIDIOverride->setState(e);
+ }
+
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ e = ConfMan.hasKey("native_mt32", _domain) ||
+ ConfMan.hasKey("enable_gs", _domain);
+ _globalMT32Override->setState(e);
+ }
+
+ // TODO: game path
+
+ const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
+
+ if (ConfMan.hasKey("language", _domain)) {
+ _langPopUp->setSelectedTag(lang);
+ } else {
+ _langPopUp->setSelectedTag((uint32)Common::UNK_LANG);
+ }
+
+ if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable
+ _langPopUpDesc->setEnabled(false);
+ _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));
+ sel = 0;
+ for (i = 0; p->code; ++p, ++i) {
+ if (platform == p->id)
+ sel = i + 2;
+ }
+ _platformPopUp->setSelected(sel);
+}
+
+
+void EditGameDialog::close() {
+ if (getResult()) {
+ ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
+
+ Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
+ if (lang < 0)
+ ConfMan.removeKey("language", _domain);
+ else
+ ConfMan.set("language", Common::getLanguageCode(lang), _domain);
+
+ String gamePath(_gamePathWidget->getLabel());
+ if (!gamePath.empty())
+ ConfMan.set("path", gamePath, _domain);
+
+ String extraPath(_extraPathWidget->getLabel());
+ if (!extraPath.empty() && (extraPath != _c("None", "path")))
+ ConfMan.set("extrapath", extraPath, _domain);
+ else
+ ConfMan.removeKey("extrapath", _domain);
+
+ String savePath(_savePathWidget->getLabel());
+ if (!savePath.empty() && (savePath != _("Default")))
+ ConfMan.set("savepath", savePath, _domain);
+ else
+ ConfMan.removeKey("savepath", _domain);
+
+ Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag();
+ if (platform < 0)
+ 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();
+}
+
+void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kCmdGlobalGraphicsOverride:
+ setGraphicSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalAudioOverride:
+ setAudioSettingsState(data != 0);
+ setSubtitleSettingsState(data != 0);
+ if (_globalVolumeOverride == NULL)
+ setVolumeSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalMIDIOverride:
+ setMIDISettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalMT32Override:
+ setMT32SettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalVolumeOverride:
+ setVolumeSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdChooseSoundFontCmd:
+ {
+ BrowserDialog browser(_("Select SoundFont"), false);
+
+ if (browser.runModal() > 0) {
+ // User made this choice...
+ Common::FSNode file(browser.getResult());
+ _soundFont->setLabel(file.getPath());
+
+ if (!file.getPath().empty() && (file.getPath() != _c("None", "path")))
+ _soundFontClearButton->setEnabled(true);
+ else
+ _soundFontClearButton->setEnabled(false);
+
+ draw();
+ }
+ break;
+ }
+
+ // Change path for the game
+ case kCmdGameBrowser:
+ {
+ BrowserDialog browser(_("Select directory with game data"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+
+ // TODO: Verify the game can be found in the new directory... Best
+ // done with optional specific gameid to pluginmgr detectgames?
+ // FSList files = dir.listDir(FSNode::kListFilesOnly);
+
+ _gamePathWidget->setLabel(dir.getPath());
+ draw();
+ }
+ draw();
+ break;
+ }
+
+ // Change path for extra game data (eg, using sword cutscenes when playing via CD)
+ case kCmdExtraBrowser:
+ {
+ BrowserDialog browser(_("Select additional game directory"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ _extraPathWidget->setLabel(dir.getPath());
+ draw();
+ }
+ draw();
+ break;
+ }
+ // Change path for stored save game (perm and temp) data
+ case kCmdSaveBrowser:
+ {
+ BrowserDialog browser(_("Select directory for saved games"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ _savePathWidget->setLabel(dir.getPath());
+#ifdef USE_LIBCURL
+ MessageDialog warningMessage(_("Saves sync feature doesn't work with non-default directories. If you want your saves to sync, use default directory."));
+ warningMessage.runModal();
+#endif
+ draw();
+ }
+ draw();
+ break;
+ }
+
+ case kCmdExtraPathClear:
+ _extraPathWidget->setLabel(_c("None", "path"));
+ break;
+
+ case kCmdSavePathClear:
+ _savePathWidget->setLabel(_("Default"));
+ break;
+
+ case kOKCmd:
+ {
+ // Write back changes made to config object
+ String newDomain(_domainWidget->getEditString());
+ if (newDomain != _domain) {
+ if (newDomain.empty()
+ || newDomain.hasPrefix("_")
+ || newDomain == ConfigManager::kApplicationDomain
+ || ConfMan.hasGameDomain(newDomain)) {
+ MessageDialog alert(_("This game ID is already taken. Please choose another one."));
+ alert.runModal();
+ return;
+ }
+ ConfMan.renameGameDomain(_domain, newDomain);
+ _domain = newDomain;
+ }
+ }
+ // FALL THROUGH to default case
+ default:
+ OptionsDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+} // End of namespace GUI
diff --git a/gui/editgamedialog.h b/gui/editgamedialog.h
new file mode 100644
index 0000000000..0be6c1650e
--- /dev/null
+++ b/gui/editgamedialog.h
@@ -0,0 +1,97 @@
+/* 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_EDITGAMEDIALOG_H
+#define GUI_EDITGAMEDIALOG_H
+
+#include "engines/game.h"
+#include "gui/dialog.h"
+#include "gui/options.h"
+
+namespace GUI {
+
+class BrowserDialog;
+class CommandSender;
+class DomainEditTextWidget;
+class ListWidget;
+class ButtonWidget;
+class PicButtonWidget;
+class GraphicsWidget;
+class StaticTextWidget;
+class EditTextWidget;
+class SaveLoadChooser;
+
+Common::String addGameToConf(const GameDescriptor &result);
+
+/*
+* A dialog that allows the user to edit a config game entry.
+* TODO: add widgets for some/all of the following
+* - Maybe scaler/graphics mode. But there are two problems:
+* 1) Different backends can have different scalers with different names,
+* so we first have to add a way to query those... no Ender, I don't
+* think a bitmasked property() value is nice for this, because we would
+* have to add to the bitmask values whenever a backends adds a new scaler).
+* 2) At the time the launcher is running, the GFX backend is already setup.
+* So when a game is run via the launcher, the custom scaler setting for it won't be
+* used. So we'd also have to add an API to change the scaler during runtime
+* (the SDL backend can already do that based on user input, but there is no API
+* to achieve it)
+* If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
+*/
+
+class EditGameDialog : public OptionsDialog {
+ typedef Common::String String;
+ typedef Common::Array<Common::String> StringArray;
+public:
+ EditGameDialog(const String &domain, const String &desc);
+
+ void open();
+ void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+protected:
+ EditTextWidget *_descriptionWidget;
+ DomainEditTextWidget *_domainWidget;
+
+ StaticTextWidget *_gamePathWidget;
+ StaticTextWidget *_extraPathWidget;
+ StaticTextWidget *_savePathWidget;
+ ButtonWidget *_extraPathClearButton;
+ ButtonWidget *_savePathClearButton;
+
+ StaticTextWidget *_langPopUpDesc;
+ PopUpWidget *_langPopUp;
+ StaticTextWidget *_platformPopUpDesc;
+ PopUpWidget *_platformPopUp;
+
+ CheckboxWidget *_globalGraphicsOverride;
+ CheckboxWidget *_globalAudioOverride;
+ CheckboxWidget *_globalMIDIOverride;
+ CheckboxWidget *_globalMT32Override;
+ CheckboxWidget *_globalVolumeOverride;
+
+ ExtraGuiOptions _engineOptions;
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 9acd9434ff..9d68d76c9c 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -216,7 +216,7 @@ void GuiManager::redraw() {
// Tanoku: Do not apply shading more than once when opening many dialogs
// on top of each other. Screen ends up being too dark and it's a
// performance hog.
- if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2)
+ if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 3)
shading = ThemeEngine::kShadingNone;
switch (_redrawStatus) {
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index 9a3300b11f..50f060de65 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -33,6 +33,7 @@
#include "gui/about.h"
#include "gui/browser.h"
#include "gui/chooser.h"
+#include "gui/editgamedialog.h"
#include "gui/launcher.h"
#include "gui/massadd.h"
#include "gui/message.h"
@@ -51,6 +52,9 @@
#include "gui/ThemeEval.h"
#include "graphics/cursorman.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
using Common::ConfigManager;
@@ -84,521 +88,6 @@ enum {
kCmdSavePathClear = 'PSAC'
};
-/*
- * TODO: Clean up this ugly design: we subclass EditTextWidget to perform
- * input validation. It would be much more elegant to use a decorator pattern,
- * or a validation callback, or something like that.
- */
-class DomainEditTextWidget : public EditTextWidget {
-public:
- DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0)
- : EditTextWidget(boss, name, text, tooltip) {
- }
-
-protected:
- bool tryInsertChar(byte c, int pos) {
- if (Common::isAlnum(c) || c == '-' || c == '_') {
- _editString.insertChar(c, pos);
- return true;
- }
- return false;
- }
-};
-
-/*
- * A dialog that allows the user to edit a config game entry.
- * TODO: add widgets for some/all of the following
- * - Maybe scaler/graphics mode. But there are two problems:
- * 1) Different backends can have different scalers with different names,
- * so we first have to add a way to query those... no Ender, I don't
- * think a bitmasked property() value is nice for this, because we would
- * have to add to the bitmask values whenever a backends adds a new scaler).
- * 2) At the time the launcher is running, the GFX backend is already setup.
- * So when a game is run via the launcher, the custom scaler setting for it won't be
- * used. So we'd also have to add an API to change the scaler during runtime
- * (the SDL backend can already do that based on user input, but there is no API
- * to achieve it)
- * If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
- */
-
-class EditGameDialog : public OptionsDialog {
- typedef Common::String String;
- typedef Common::Array<Common::String> StringArray;
-public:
- EditGameDialog(const String &domain, const String &desc);
-
- void open();
- void close();
- virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
-
-protected:
- EditTextWidget *_descriptionWidget;
- DomainEditTextWidget *_domainWidget;
-
- StaticTextWidget *_gamePathWidget;
- StaticTextWidget *_extraPathWidget;
- StaticTextWidget *_savePathWidget;
- ButtonWidget *_extraPathClearButton;
- ButtonWidget *_savePathClearButton;
-
- StaticTextWidget *_langPopUpDesc;
- PopUpWidget *_langPopUp;
- StaticTextWidget *_platformPopUpDesc;
- PopUpWidget *_platformPopUp;
-
- CheckboxWidget *_globalGraphicsOverride;
- CheckboxWidget *_globalAudioOverride;
- CheckboxWidget *_globalMIDIOverride;
- CheckboxWidget *_globalMT32Override;
- CheckboxWidget *_globalVolumeOverride;
-
- ExtraGuiOptions _engineOptions;
-};
-
-EditGameDialog::EditGameDialog(const String &domain, const String &desc)
- : OptionsDialog(domain, "GameOptions") {
- // Retrieve all game specific options.
- const EnginePlugin *plugin = 0;
- // To allow for game domains without a gameid.
- // TODO: Is it intentional that this is still supported?
- String gameId(ConfMan.get("gameid", domain));
- if (gameId.empty())
- gameId = domain;
- // Retrieve the plugin, since we need to access the engine's MetaEngine
- // implementation.
- EngineMan.findGame(gameId, &plugin);
- if (plugin) {
- _engineOptions = (*plugin)->getExtraGuiOptions(domain);
- } else {
- warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str());
- }
-
- // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
- String gamePath(ConfMan.get("path", _domain));
- String extraPath(ConfMan.get("extrapath", _domain));
- String savePath(ConfMan.get("savepath", _domain));
-
- // GAME: Determine the description string
- String description(ConfMan.get("description", domain));
- if (description.empty() && !desc.empty()) {
- description = desc;
- }
-
- // GUI: Add tab widget
- TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
-
- //
- // 1) The game tab
- //
- tab->addTab(_("Game"));
-
- // GUI: Label & edit widget for the game ID
- if (g_system->getOverlayWidth() > 320)
- new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line"));
- else
- new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line"));
- _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line"));
-
- // GUI: Label & edit widget for the description
- if (g_system->getOverlayWidth() > 320)
- new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game"));
- else
- new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game"));
- _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game"));
-
- // Language popup
- _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English"));
- _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English"));
- _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG);
- _langPopUp->appendEntry("", (uint32)Common::UNK_LANG);
- const Common::LanguageDescription *l = Common::g_languages;
- for (; l->code; ++l) {
- if (checkGameGUIOptionLanguage(l->id, _guioptionsString))
- _langPopUp->appendEntry(l->description, l->id);
- }
-
- // Platform popup
- if (g_system->getOverlayWidth() > 320)
- _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for"));
- else
- _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for"));
- _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for"));
- _platformPopUp->appendEntry(_("<default>"));
- _platformPopUp->appendEntry("");
- const Common::PlatformDescription *p = Common::g_platforms;
- for (; p->code; ++p) {
- _platformPopUp->appendEntry(p->description, p->id);
- }
-
- //
- // 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"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride);
- else
- _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride);
-
- addGraphicControls(tab, "GameOptions_Graphics.");
-
- //
- // 4) The audio tab
- //
- tab->addTab(_("Audio"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride);
- else
- _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride);
-
- addAudioControls(tab, "GameOptions_Audio.");
- addSubtitleControls(tab, "GameOptions_Audio.");
-
- //
- // 5) The volume tab
- //
- if (g_system->getOverlayWidth() > 320)
- tab->addTab(_("Volume"));
- else
- tab->addTab(_c("Volume", "lowres"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride);
- else
- _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride);
-
- addVolumeControls(tab, "GameOptions_Volume.");
-
- //
- // 6) The MIDI tab
- //
- _globalMIDIOverride = NULL;
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- tab->addTab(_("MIDI"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride);
- else
- _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride);
-
- addMIDIControls(tab, "GameOptions_MIDI.");
- }
-
- //
- // 7) The MT-32 tab
- //
- _globalMT32Override = NULL;
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- tab->addTab(_("MT-32"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override);
- else
- _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override);
-
- addMT32Controls(tab, "GameOptions_MT32.");
- }
-
- //
- // 8) The Paths tab
- //
- if (g_system->getOverlayWidth() > 320)
- tab->addTab(_("Paths"));
- else
- tab->addTab(_c("Paths", "lowres"));
-
- // These buttons have to be extra wide, or the text will be truncated
- // in the small version of the GUI.
-
- // GUI: Button + Label for the game path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser);
- _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath);
-
- // GUI: Button + Label for the additional path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
- _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game"));
-
- _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear);
-
- // GUI: Button + Label for the save path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
- _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put"));
-
- _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
-
- // Activate the first tab
- tab->setActiveTab(0);
- _tabWidget = tab;
-
- // Add OK & Cancel buttons
- new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd);
- new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd);
-}
-
-void EditGameDialog::open() {
- OptionsDialog::open();
-
- String extraPath(ConfMan.get("extrapath", _domain));
- if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) {
- _extraPathWidget->setLabel(_c("None", "path"));
- }
-
- String savePath(ConfMan.get("savepath", _domain));
- if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) {
- _savePathWidget->setLabel(_("Default"));
- }
-
- int sel, i;
- bool e;
-
- // En-/disable dialog items depending on whether overrides are active or not.
-
- e = ConfMan.hasKey("gfx_mode", _domain) ||
- ConfMan.hasKey("render_mode", _domain) ||
- ConfMan.hasKey("fullscreen", _domain) ||
- ConfMan.hasKey("aspect_ratio", _domain);
- _globalGraphicsOverride->setState(e);
-
- e = ConfMan.hasKey("music_driver", _domain) ||
- ConfMan.hasKey("output_rate", _domain) ||
- ConfMan.hasKey("opl_driver", _domain) ||
- ConfMan.hasKey("subtitles", _domain) ||
- ConfMan.hasKey("talkspeed", _domain);
- _globalAudioOverride->setState(e);
-
- e = ConfMan.hasKey("music_volume", _domain) ||
- ConfMan.hasKey("sfx_volume", _domain) ||
- ConfMan.hasKey("speech_volume", _domain);
- _globalVolumeOverride->setState(e);
-
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- e = ConfMan.hasKey("soundfont", _domain) ||
- ConfMan.hasKey("multi_midi", _domain) ||
- ConfMan.hasKey("midi_gain", _domain);
- _globalMIDIOverride->setState(e);
- }
-
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- e = ConfMan.hasKey("native_mt32", _domain) ||
- ConfMan.hasKey("enable_gs", _domain);
- _globalMT32Override->setState(e);
- }
-
- // TODO: game path
-
- const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
-
- if (ConfMan.hasKey("language", _domain)) {
- _langPopUp->setSelectedTag(lang);
- } else {
- _langPopUp->setSelectedTag((uint32)Common::UNK_LANG);
- }
-
- if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable
- _langPopUpDesc->setEnabled(false);
- _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));
- sel = 0;
- for (i = 0; p->code; ++p, ++i) {
- if (platform == p->id)
- sel = i + 2;
- }
- _platformPopUp->setSelected(sel);
-}
-
-
-void EditGameDialog::close() {
- if (getResult()) {
- ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
-
- Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
- if (lang < 0)
- ConfMan.removeKey("language", _domain);
- else
- ConfMan.set("language", Common::getLanguageCode(lang), _domain);
-
- String gamePath(_gamePathWidget->getLabel());
- if (!gamePath.empty())
- ConfMan.set("path", gamePath, _domain);
-
- String extraPath(_extraPathWidget->getLabel());
- if (!extraPath.empty() && (extraPath != _c("None", "path")))
- ConfMan.set("extrapath", extraPath, _domain);
- else
- ConfMan.removeKey("extrapath", _domain);
-
- String savePath(_savePathWidget->getLabel());
- if (!savePath.empty() && (savePath != _("Default")))
- ConfMan.set("savepath", savePath, _domain);
- else
- ConfMan.removeKey("savepath", _domain);
-
- Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag();
- if (platform < 0)
- 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();
-}
-
-void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
- switch (cmd) {
- case kCmdGlobalGraphicsOverride:
- setGraphicSettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalAudioOverride:
- setAudioSettingsState(data != 0);
- setSubtitleSettingsState(data != 0);
- if (_globalVolumeOverride == NULL)
- setVolumeSettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalMIDIOverride:
- setMIDISettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalMT32Override:
- setMT32SettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalVolumeOverride:
- setVolumeSettingsState(data != 0);
- draw();
- break;
- case kCmdChooseSoundFontCmd: {
- BrowserDialog browser(_("Select SoundFont"), false);
-
- if (browser.runModal() > 0) {
- // User made this choice...
- Common::FSNode file(browser.getResult());
- _soundFont->setLabel(file.getPath());
-
- if (!file.getPath().empty() && (file.getPath() != _c("None", "path")))
- _soundFontClearButton->setEnabled(true);
- else
- _soundFontClearButton->setEnabled(false);
-
- draw();
- }
- break;
- }
-
- // Change path for the game
- case kCmdGameBrowser: {
- BrowserDialog browser(_("Select directory with game data"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
-
- // TODO: Verify the game can be found in the new directory... Best
- // done with optional specific gameid to pluginmgr detectgames?
- // FSList files = dir.listDir(FSNode::kListFilesOnly);
-
- _gamePathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
-
- // Change path for extra game data (eg, using sword cutscenes when playing via CD)
- case kCmdExtraBrowser: {
- BrowserDialog browser(_("Select additional game directory"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
- _extraPathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
- // Change path for stored save game (perm and temp) data
- case kCmdSaveBrowser: {
- BrowserDialog browser(_("Select directory for saved games"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
- _savePathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
-
- case kCmdExtraPathClear:
- _extraPathWidget->setLabel(_c("None", "path"));
- break;
-
- case kCmdSavePathClear:
- _savePathWidget->setLabel(_("Default"));
- break;
-
- case kOKCmd: {
- // Write back changes made to config object
- String newDomain(_domainWidget->getEditString());
- if (newDomain != _domain) {
- if (newDomain.empty()
- || newDomain.hasPrefix("_")
- || newDomain == ConfigManager::kApplicationDomain
- || ConfMan.hasGameDomain(newDomain)) {
- MessageDialog alert(_("This game ID is already taken. Please choose another one."));
- alert.runModal();
- return;
- }
- ConfMan.renameGameDomain(_domain, newDomain);
- _domain = newDomain;
- }
- }
- // FALL THROUGH to default case
- default:
- OptionsDialog::handleCommand(sender, cmd, data);
- }
-}
-
#pragma mark -
LauncherDialog::LauncherDialog()
@@ -839,65 +328,25 @@ void LauncherDialog::addGame() {
if (_browser->runModal() > 0) {
// User made his choice...
- Common::FSNode dir(_browser->getResult());
- Common::FSList files;
- if (!dir.getChildren(files, Common::FSNode::kListAll)) {
- MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
- alert.runModal();
- return;
- }
-
- // ...so let's determine a list of candidates, games that
- // could be contained in the specified directory.
- GameList candidates(EngineMan.detectGames(files));
-
- int idx;
- if (candidates.empty()) {
- // No game was found in the specified directory
- MessageDialog alert(_("ScummVM could not find any game in the specified directory!"));
- alert.runModal();
- idx = -1;
-
- looping = true;
- } else if (candidates.size() == 1) {
- // Exact match
- idx = 0;
- } else {
- // Display the candidates to the user and let her/him pick one
- StringArray list;
- for (idx = 0; idx < (int)candidates.size(); idx++)
- list.push_back(candidates[idx].description());
-
- ChooserDialog dialog(_("Pick the game:"));
- dialog.setList(list);
- idx = dialog.runModal();
- }
- if (0 <= idx && idx < (int)candidates.size()) {
- GameDescriptor result = candidates[idx];
-
- // TODO: Change the detectors to set "path" !
- result["path"] = dir.getPath();
-
- Common::String domain = addGameToConf(result);
-
- // Display edit dialog for the new entry
- EditGameDialog editDialog(domain, result.description());
- if (editDialog.runModal() > 0) {
- // User pressed OK, so make changes permanent
-
- // Write config to disk
- ConfMan.flushToDisk();
-
- // Update the ListWidget, select the new item, and force a redraw
- updateListing();
- selectTarget(editDialog.getDomain());
- draw();
+#ifdef USE_LIBCURL
+ String selectedDirectory = _browser->getResult().getPath();
+ String bannedDirectory = CloudMan.getDownloadLocalDirectory();
+ if (selectedDirectory.size() && selectedDirectory.lastChar() != '/' && selectedDirectory.lastChar() != '\\')
+ selectedDirectory += '/';
+ if (bannedDirectory.size() && bannedDirectory.lastChar() != '/' && bannedDirectory.lastChar() != '\\') {
+ if (selectedDirectory.size()) {
+ bannedDirectory += selectedDirectory.lastChar();
} else {
- // User aborted, remove the the new domain again
- ConfMan.removeGameDomain(domain);
+ bannedDirectory += '/';
}
-
}
+ if (selectedDirectory.equalsIgnoreCase(bannedDirectory)) {
+ MessageDialog alert(_("This directory cannot be used yet, it is being downloaded into!"));
+ alert.runModal();
+ return;
+ }
+#endif
+ looping = !doGameDetection(_browser->getResult().getPath());
}
} while (looping);
}
@@ -1076,6 +525,81 @@ void LauncherDialog::handleKeyUp(Common::KeyState state) {
updateButtons();
}
+bool LauncherDialog::doGameDetection(const Common::String &path) {
+ // Allow user to add a new game to the list.
+ // 2) try to auto detect which game is in the directory, if we cannot
+ // determine it uniquely present a list of candidates to the user
+ // to pick from
+ // 3) Display the 'Edit' dialog for that item, letting the user specify
+ // an alternate description (to distinguish multiple versions of the
+ // game, e.g. 'Monkey German' and 'Monkey English') and set default
+ // options for that game
+ // 4) If no game is found in the specified directory, return to the
+ // dialog.
+
+ // User made his choice...
+ Common::FSNode dir(path);
+ Common::FSList files;
+ if (!dir.getChildren(files, Common::FSNode::kListAll)) {
+ MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
+ alert.runModal();
+ return true;
+ }
+
+ // ...so let's determine a list of candidates, games that
+ // could be contained in the specified directory.
+ GameList candidates(EngineMan.detectGames(files));
+
+ int idx;
+ if (candidates.empty()) {
+ // No game was found in the specified directory
+ MessageDialog alert(_("ScummVM could not find any game in the specified directory!"));
+ alert.runModal();
+ idx = -1;
+ return false;
+ } else if (candidates.size() == 1) {
+ // Exact match
+ idx = 0;
+ } else {
+ // Display the candidates to the user and let her/him pick one
+ StringArray list;
+ for (idx = 0; idx < (int)candidates.size(); idx++)
+ list.push_back(candidates[idx].description());
+
+ ChooserDialog dialog(_("Pick the game:"));
+ dialog.setList(list);
+ idx = dialog.runModal();
+ }
+ if (0 <= idx && idx < (int)candidates.size()) {
+ GameDescriptor result = candidates[idx];
+
+ // TODO: Change the detectors to set "path" !
+ result["path"] = dir.getPath();
+
+ Common::String domain = addGameToConf(result);
+
+ // Display edit dialog for the new entry
+ EditGameDialog editDialog(domain, result.description());
+ if (editDialog.runModal() > 0) {
+ // User pressed OK, so make changes permanent
+
+ // Write config to disk
+ ConfMan.flushToDisk();
+
+ // Update the ListWidget, select the new item, and force a redraw
+ updateListing();
+ selectTarget(editDialog.getDomain());
+ draw();
+ } else {
+ // User aborted, remove the the new domain again
+ ConfMan.removeGameDomain(domain);
+ }
+
+ }
+
+ return true;
+}
+
void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
int item = _list->getSelected();
@@ -1093,7 +617,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
loadGameButtonPressed(item);
break;
case kOptionsCmd: {
- GlobalOptionsDialog options;
+ GlobalOptionsDialog options(this);
options.runModal();
}
break;
diff --git a/gui/launcher.h b/gui/launcher.h
index e9c76a5320..58f1c930ed 100644
--- a/gui/launcher.h
+++ b/gui/launcher.h
@@ -51,7 +51,7 @@ public:
virtual void handleKeyDown(Common::KeyState state);
virtual void handleKeyUp(Common::KeyState state);
-
+ bool doGameDetection(const Common::String &path);
protected:
EditTextWidget *_searchWidget;
ListWidget *_list;
diff --git a/gui/module.mk b/gui/module.mk
index 6cbc63d24d..54818c264e 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
console.o \
debugger.o \
dialog.o \
+ editgamedialog.o \
error.o \
EventRecorder.o \
filebrowser-dialog.o \
@@ -24,6 +25,9 @@ MODULE_OBJS := \
ThemeLayout.o \
ThemeParser.o \
Tooltip.o \
+ animation/Animation.o \
+ animation/RepeatAnimationWrapper.o \
+ animation/SequenceAnimationComposite.o \
widget.o \
widgets/editable.o \
widgets/edittext.o \
@@ -54,6 +58,13 @@ MODULE_OBJS += \
endif
endif
+ifdef USE_LIBCURL
+MODULE_OBJS += \
+ downloaddialog.o \
+ remotebrowser.o \
+ storagewizarddialog.o
+endif
+
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
editrecorddialog.o \
diff --git a/gui/object.h b/gui/object.h
index 219bf77f69..1541c35aa8 100644
--- a/gui/object.h
+++ b/gui/object.h
@@ -76,6 +76,8 @@ public:
virtual void setTextDrawableArea(const Common::Rect &r) { _textDrawableArea = r; }
+ virtual int16 getRelX() const { return _x; }
+ virtual int16 getRelY() const { return _y; }
virtual int16 getAbsX() const { return _x; }
virtual int16 getAbsY() const { return _y; }
virtual int16 getChildX() const { return getAbsX(); }
@@ -91,6 +93,10 @@ public:
virtual void removeWidget(Widget *widget);
+ virtual bool isPointIn(int x, int y) {
+ return (x >= _x && x < (_x + _w) && (y >= _y) && (y < _y + _h));
+ }
+
protected:
virtual void releaseFocus() = 0;
};
diff --git a/gui/options.cpp b/gui/options.cpp
index e410971818..68add45260 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -42,6 +42,18 @@
#include "audio/musicplugin.h"
#include "audio/mixer.h"
#include "audio/fmopl.h"
+#include "widgets/scrollcontainer.h"
+#include "widgets/edittext.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "gui/downloaddialog.h"
+#include "gui/storagewizarddialog.h"
+#endif
+
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
namespace GUI {
@@ -84,6 +96,19 @@ enum {
};
#endif
+#ifdef USE_CLOUD
+enum {
+ kConfigureStorageCmd = 'cfst',
+ kRefreshStorageCmd = 'rfst',
+ kDownloadStorageCmd = 'dlst',
+ kRunServerCmd = 'rnsv',
+ kCloudTabContainerReflowCmd = 'ctcr',
+ kServerPortClearCmd = 'spcl',
+ kChooseRootDirCmd = 'chrp',
+ kRootPathClearCmd = 'clrp'
+};
+#endif
+
static const char *savePeriodLabels[] = { _s("Never"), _s("every 5 mins"), _s("every 10 mins"), _s("every 15 mins"), _s("every 30 mins"), 0 };
static const int savePeriodValues[] = { 0, 5 * 60, 10 * 60, 15 * 60, 30 * 60, -1 };
static const char *outputRateLabels[] = { _s("<default>"), _s("8 kHz"), _s("11 kHz"), _s("22 kHz"), _s("44 kHz"), _s("48 kHz"), 0 };
@@ -1078,8 +1103,8 @@ void OptionsDialog::reflowLayout() {
#pragma mark -
-GlobalOptionsDialog::GlobalOptionsDialog()
- : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions") {
+GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher)
+ : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions"), _launcher(launcher) {
// The tab widget
TabWidget *tab = new TabWidget(this, "GlobalOptions.TabWidget");
@@ -1251,6 +1276,76 @@ GlobalOptionsDialog::GlobalOptionsDialog()
new ButtonWidget(tab, "GlobalOptions_Misc.UpdatesCheckManuallyButton", _("Check now"), 0, kUpdatesCheckCmd);
#endif
+#ifdef USE_CLOUD
+ //
+ // 7) The cloud tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Cloud"));
+ else
+ tab->addTab(_c("Cloud", "lowres"));
+
+ ScrollContainerWidget *container = new ScrollContainerWidget(tab, "GlobalOptions_Cloud.Container", kCloudTabContainerReflowCmd);
+ container->setTarget(this);
+
+#ifdef USE_LIBCURL
+ _selectedStorageIndex = CloudMan.getStorageIndex();
+#else
+ _selectedStorageIndex = 0;
+#endif
+
+ _storagePopUpDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StoragePopupDesc", _("Storage:"), _("Active cloud storage"));
+ _storagePopUp = new PopUpWidget(container, "GlobalOptions_Cloud_Container.StoragePopup");
+#ifdef USE_LIBCURL
+ Common::StringArray list = CloudMan.listStorages();
+ for (uint32 i = 0; i < list.size(); ++i)
+ _storagePopUp->appendEntry(list[i], i);
+#else
+ _storagePopUp->appendEntry(_("<none>"), 0);
+#endif
+ _storagePopUp->setSelected(_selectedStorageIndex);
+
+ _storageUsernameDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameDesc", _("Username:"), _("Username used by this storage"));
+ _storageUsername = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameLabel", "<none>");
+
+ _storageUsedSpaceDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceDesc", _("Used space:"), _("Space used by ScummVM's saves on this storage"));
+ _storageUsedSpace = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceLabel", "0 bytes");
+
+ _storageLastSyncDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncDesc", _("Last sync time:"), _("When the last saves sync for this storage occured"));
+ _storageLastSync = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncLabel", "<never>");
+
+ _storageConnectButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.ConnectButton", _("Connect"), _("Open wizard dialog to connect your cloud storage account"), kConfigureStorageCmd);
+ _storageRefreshButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RefreshButton", _("Refresh"), _("Refresh current cloud storage information (username and usage)"), kRefreshStorageCmd);
+ _storageDownloadButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.DownloadButton", _("Download"), _("Open downloads manager dialog"), kDownloadStorageCmd);
+
+ _runServerButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RunServerButton", _("Run server"), _("Run local webserver"), kRunServerCmd);
+ _serverInfoLabel = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerInfoLabel", _("Not running"));
+
+ // Root path
+ if (g_system->getOverlayWidth() > 320)
+ _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _("/root/ Path:"), _("Specifies which directory the Files Manager can access"), kChooseRootDirCmd);
+ else
+ _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _c("/root/ Path:", "lowres"), _("Specifies which directory the Files Manager can access"), kChooseRootDirCmd);
+ _rootPath = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.RootPath", "/foo/bar", _("Specifies which directory the Files Manager can access"));
+
+ _rootPathClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.RootPathClearButton", kRootPathClearCmd);
+
+#ifdef USE_SDL_NET
+ uint32 port = Networking::LocalWebserver::getPort();
+#else
+ uint32 port = 0; // the following widgets are hidden anyway
+#endif
+ _serverPortDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortDesc", _("Server's port:"), _("Which port is used by the server\nAuth with server is not available with non-default port"));
+ _serverPort = new EditTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortEditText", Common::String::format("%u", port), 0);
+ _serverPortClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.ServerPortClearButton", kServerPortClearCmd);
+
+ setupCloudTab();
+ _redrawCloudTab = false;
+#ifdef USE_SDL_NET
+ _serverWasRunning = false;
+#endif
+#endif
+
// Activate the first tab
tab->setActiveTab(0);
_tabWidget = tab;
@@ -1327,6 +1422,15 @@ void GlobalOptionsDialog::open() {
if (mode == ThemeEngine::kGfxDisabled)
mode = ThemeEngine::_defaultRendererMode;
_rendererPopUp->setSelectedTag(mode);
+
+#ifdef USE_CLOUD
+ Common::String rootPath(ConfMan.get("rootpath", "cloud"));
+ if (rootPath.empty() || !ConfMan.hasKey("rootpath", "cloud")) {
+ _rootPath->setLabel(_c("None", "path"));
+ } else {
+ _rootPath->setLabel(rootPath);
+ }
+#endif
}
void GlobalOptionsDialog::close() {
@@ -1357,6 +1461,14 @@ void GlobalOptionsDialog::close() {
ConfMan.removeKey("pluginspath", _domain);
#endif
+#ifdef USE_CLOUD
+ Common::String rootPath(_rootPath->getLabel());
+ if (!rootPath.empty() && (rootPath != _c("None", "path")))
+ ConfMan.set("rootpath", rootPath, "cloud");
+ else
+ ConfMan.removeKey("rootpath", "cloud");
+#endif
+
ConfMan.setInt("autosave_period", _autosavePeriodPopUp->getSelectedTag(), _domain);
GUI::ThemeEngine::GraphicsMode selected = (GUI::ThemeEngine::GraphicsMode)_rendererPopUp->getSelectedTag();
@@ -1402,7 +1514,38 @@ void GlobalOptionsDialog::close() {
}
#endif
+#ifdef USE_LIBCURL
+ if (CloudMan.getStorageIndex() != _selectedStorageIndex) {
+ if (!CloudMan.switchStorage(_selectedStorageIndex)) {
+ bool anotherStorageIsWorking = CloudMan.isWorking();
+ Common::String message = _("Failed to change cloud storage!");
+ if (anotherStorageIsWorking) {
+ message += "\n";
+ message += _("Another cloud storage is already active.");
+ }
+ MessageDialog dialog(message);
+ dialog.runModal();
+ }
+ }
+#endif
+#ifdef USE_SDL_NET
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+#endif
+#endif
}
+#ifdef USE_SDL_NET
+ if (LocalServer.isRunning()) {
+ LocalServer.stop();
+ }
+#endif
OptionsDialog::close();
}
@@ -1456,6 +1599,21 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
break;
}
#endif
+#ifdef USE_CLOUD
+ case kChooseRootDirCmd: {
+ BrowserDialog browser(_("Select directory for Files Manager /root/"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ Common::String path = dir.getPath();
+ if (path.empty())
+ path = "/"; // absolute root
+ _rootPath->setLabel(path);
+ draw();
+ }
+ break;
+ }
+#endif
case kThemePathClearCmd:
_themePath->setLabel(_c("None", "path"));
break;
@@ -1465,6 +1623,11 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
case kSavePathClearCmd:
_savePath->setLabel(_("Default"));
break;
+#ifdef USE_CLOUD
+ case kRootPathClearCmd:
+ _rootPath->setLabel(_c("None", "path"));
+ break;
+#endif
case kChooseSoundFontCmd: {
BrowserDialog browser(_("Select SoundFont"), false);
if (browser.runModal() > 0) {
@@ -1481,7 +1644,8 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
break;
}
- case kChooseThemeCmd: {
+ case kChooseThemeCmd:
+ {
ThemeBrowser browser;
if (browser.runModal() > 0) {
// User made his choice...
@@ -1513,6 +1677,91 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
break;
}
+#ifdef USE_CLOUD
+ case kCloudTabContainerReflowCmd:
+ setupCloudTab();
+ break;
+#endif
+#ifdef USE_LIBCURL
+ case kPopUpItemSelectedCmd:
+ {
+ //update container's scrollbar
+ reflowLayout();
+ break;
+ }
+ case kConfigureStorageCmd:
+ {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+ ConfMan.flushToDisk();
+#endif
+ StorageWizardDialog dialog(_selectedStorageIndex);
+ dialog.runModal();
+ //update container's scrollbar
+ reflowLayout();
+ break;
+ }
+ case kRefreshStorageCmd:
+ {
+ CloudMan.info(
+ new Common::Callback<GlobalOptionsDialog, Cloud::Storage::StorageInfoResponse>(this, &GlobalOptionsDialog::storageInfoCallback),
+ new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback)
+ );
+ Common::String dir = CloudMan.savesDirectoryPath();
+ if (dir.lastChar() == '/')
+ dir.deleteLastChar();
+ CloudMan.listDirectory(
+ dir,
+ new Common::Callback<GlobalOptionsDialog, Cloud::Storage::ListDirectoryResponse>(this, &GlobalOptionsDialog::storageListDirectoryCallback),
+ new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback)
+ );
+ break;
+ }
+ case kDownloadStorageCmd:
+ {
+ DownloadDialog dialog(_selectedStorageIndex, _launcher);
+ dialog.runModal();
+ break;
+ }
+#endif
+#ifdef USE_SDL_NET
+ case kRunServerCmd:
+ {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+ ConfMan.flushToDisk();
+#endif
+
+ if (LocalServer.isRunning())
+ LocalServer.stopOnIdle();
+ else
+ LocalServer.start();
+
+ break;
+ }
+
+ case kServerPortClearCmd: {
+ if (_serverPort) {
+ _serverPort->setEditString(Common::String::format("%u", Networking::LocalWebserver::DEFAULT_SERVER_PORT));
+ }
+ draw();
+ break;
+ }
+#endif
#ifdef GUI_ENABLE_KEYSDIALOG
case kChooseKeyMappingCmd:
_keysDialog->runModal();
@@ -1534,6 +1783,23 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
}
+void GlobalOptionsDialog::handleTickle() {
+ OptionsDialog::handleTickle();
+#ifdef USE_CLOUD
+#ifdef USE_SDL_NET
+ if (LocalServer.isRunning() != _serverWasRunning) {
+ _serverWasRunning = !_serverWasRunning;
+ _redrawCloudTab = true;
+ }
+#endif
+ if (_redrawCloudTab) {
+ setupCloudTab();
+ draw();
+ _redrawCloudTab = false;
+ }
+#endif
+}
+
void GlobalOptionsDialog::reflowLayout() {
int activeTab = _tabWidget->getActiveTab();
@@ -1567,6 +1833,218 @@ void GlobalOptionsDialog::reflowLayout() {
_tabWidget->setActiveTab(activeTab);
OptionsDialog::reflowLayout();
+#ifdef USE_CLOUD
+ setupCloudTab();
+#endif
}
+#ifdef USE_CLOUD
+void GlobalOptionsDialog::setupCloudTab() {
+ int serverLabelPosition = -1; //no override
+#ifdef USE_LIBCURL
+ _selectedStorageIndex = _storagePopUp->getSelectedTag();
+
+ if (_storagePopUpDesc) _storagePopUpDesc->setVisible(true);
+ if (_storagePopUp) _storagePopUp->setVisible(true);
+
+ bool shown = (_selectedStorageIndex != Cloud::kStorageNoneId);
+ if (_storageUsernameDesc) _storageUsernameDesc->setVisible(shown);
+ if (_storageUsername) {
+ Common::String username = CloudMan.getStorageUsername(_selectedStorageIndex);
+ if (username == "")
+ username = _("<none>");
+ _storageUsername->setLabel(username);
+ _storageUsername->setVisible(shown);
+ }
+ if (_storageUsedSpaceDesc) _storageUsedSpaceDesc->setVisible(shown);
+ if (_storageUsedSpace) {
+ uint64 usedSpace = CloudMan.getStorageUsedSpace(_selectedStorageIndex);
+ _storageUsedSpace->setLabel(Common::String::format(_("%llu bytes"), usedSpace));
+ _storageUsedSpace->setVisible(shown);
+ }
+ if (_storageLastSyncDesc) _storageLastSyncDesc->setVisible(shown);
+ if (_storageLastSync) {
+ Common::String sync = CloudMan.getStorageLastSync(_selectedStorageIndex);
+ if (sync == "") {
+ if (_selectedStorageIndex == CloudMan.getStorageIndex() && CloudMan.isSyncing())
+ sync = _("<right now>");
+ else
+ sync = _("<never>");
+ }
+ _storageLastSync->setLabel(sync);
+ _storageLastSync->setVisible(shown);
+ }
+ if (_storageConnectButton)
+ _storageConnectButton->setVisible(shown);
+ if (_storageRefreshButton)
+ _storageRefreshButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex());
+ if (_storageDownloadButton)
+ _storageDownloadButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex());
+ if (!shown)
+ serverLabelPosition = (_storageUsernameDesc ? _storageUsernameDesc->getRelY() : 0);
+#else
+ _selectedStorageIndex = 0;
+
+ if (_storagePopUpDesc)
+ _storagePopUpDesc->setVisible(false);
+ if (_storagePopUp)
+ _storagePopUp->setVisible(false);
+ if (_storageUsernameDesc)
+ _storageUsernameDesc->setVisible(false);
+ if (_storageUsernameDesc)
+ _storageUsernameDesc->setVisible(false);
+ if (_storageUsername)
+ _storageUsername->setVisible(false);
+ if (_storageUsedSpaceDesc)
+ _storageUsedSpaceDesc->setVisible(false);
+ if (_storageUsedSpace)
+ _storageUsedSpace->setVisible(false);
+ if (_storageLastSyncDesc)
+ _storageLastSyncDesc->setVisible(false);
+ if (_storageLastSync)
+ _storageLastSync->setVisible(false);
+ if (_storageConnectButton)
+ _storageConnectButton->setVisible(false);
+ if (_storageRefreshButton)
+ _storageRefreshButton->setVisible(false);
+ if (_storageDownloadButton)
+ _storageDownloadButton->setVisible(false);
+
+ serverLabelPosition = (_storagePopUpDesc ? _storagePopUpDesc->getRelY() : 0);
+#endif
+#ifdef USE_SDL_NET
+ //determine original widget's positions
+ int16 x, y;
+ uint16 w, h;
+ int serverButtonY, serverInfoY;
+ int serverRootButtonY, serverRootY, serverRootClearButtonY;
+ int serverPortDescY, serverPortY, serverPortClearButtonY;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RunServerButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RunServerButton's position is undefined");
+ serverButtonY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerInfoLabel", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerInfoLabel's position is undefined");
+ serverInfoY = y;
+
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPathButton's position is undefined");
+ serverRootButtonY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPath", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPath's position is undefined");
+ serverRootY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathClearButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPathClearButton's position is undefined");
+ serverRootClearButtonY = y;
+
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortDesc", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortDesc's position is undefined");
+ serverPortDescY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortEditText", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortEditText's position is undefined");
+ serverPortY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortClearButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortClearButton's position is undefined");
+ serverPortClearButtonY = y;
+
+ bool serverIsRunning = LocalServer.isRunning();
+
+ if (serverLabelPosition < 0)
+ serverLabelPosition = serverInfoY;
+ if (_runServerButton) {
+ _runServerButton->setVisible(true);
+ _runServerButton->setPos(_runServerButton->getRelX(), serverLabelPosition + serverButtonY - serverInfoY);
+ _runServerButton->setLabel(_(serverIsRunning ? "Stop server" : "Run server"));
+ _runServerButton->setTooltip(_(serverIsRunning ? "Stop local webserver" : "Run local webserver"));
+ }
+ if (_serverInfoLabel) {
+ _serverInfoLabel->setVisible(true);
+ _serverInfoLabel->setPos(_serverInfoLabel->getRelX(), serverLabelPosition);
+ if (serverIsRunning)
+ _serverInfoLabel->setLabel(LocalServer.getAddress());
+ else
+ _serverInfoLabel->setLabel(_("Not running"));
+ }
+ if (_rootPathButton) {
+ _rootPathButton->setVisible(true);
+ _rootPathButton->setPos(_rootPathButton->getRelX(), serverLabelPosition + serverRootButtonY - serverInfoY);
+ }
+ if (_rootPath) {
+ _rootPath->setVisible(true);
+ _rootPath->setPos(_rootPath->getRelX(), serverLabelPosition + serverRootY - serverInfoY);
+ }
+ if (_rootPathClearButton) {
+ _rootPathClearButton->setVisible(true);
+ _rootPathClearButton->setPos(_rootPathClearButton->getRelX(), serverLabelPosition + serverRootClearButtonY - serverInfoY);
+ }
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ if (_serverPortDesc) {
+ _serverPortDesc->setVisible(true);
+ _serverPortDesc->setPos(_serverPortDesc->getRelX(), serverLabelPosition + serverPortDescY - serverInfoY);
+ _serverPortDesc->setEnabled(!serverIsRunning);
+ }
+ if (_serverPort) {
+ _serverPort->setVisible(true);
+ _serverPort->setPos(_serverPort->getRelX(), serverLabelPosition + serverPortY - serverInfoY);
+ _serverPort->setEnabled(!serverIsRunning);
+ }
+ if (_serverPortClearButton) {
+ _serverPortClearButton->setVisible(true);
+ _serverPortClearButton->setPos(_serverPortClearButton->getRelX(), serverLabelPosition + serverPortClearButtonY - serverInfoY);
+ _serverPortClearButton->setEnabled(!serverIsRunning);
+ }
+#else
+ if (_serverPortDesc)
+ _serverPortDesc->setVisible(false);
+ if (_serverPort)
+ _serverPort->setVisible(false);
+ if (_serverPortClearButton)
+ _serverPortClearButton->setVisible(false);
+#endif
+#else
+ if (_runServerButton)
+ _runServerButton->setVisible(false);
+ if (_serverInfoLabel)
+ _serverInfoLabel->setVisible(false);
+ if (_rootPathButton)
+ _rootPathButton->setVisible(false);
+ if (_rootPath)
+ _rootPath->setVisible(false);
+ if (_rootPathClearButton)
+ _rootPathClearButton->setVisible(false);
+ if (_serverPortDesc)
+ _serverPortDesc->setVisible(false);
+ if (_serverPort)
+ _serverPort->setVisible(false);
+ if (_serverPortClearButton)
+ _serverPortClearButton->setVisible(false);
+#endif
+}
+#endif
+#ifdef USE_LIBCURL
+void GlobalOptionsDialog::storageInfoCallback(Cloud::Storage::StorageInfoResponse response) {
+ //we could've used response.value.email()
+ //but Storage already notified CloudMan
+ //so we just set the flag to redraw our cloud tab
+ _redrawCloudTab = true;
+}
+
+void GlobalOptionsDialog::storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response) {
+ Common::Array<Cloud::StorageFile> &files = response.value;
+ uint64 totalSize = 0;
+ for (uint32 i = 0; i < files.size(); ++i)
+ if (!files[i].isDirectory())
+ totalSize += files[i].size();
+ CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
+ _redrawCloudTab = true;
+}
+
+void GlobalOptionsDialog::storageErrorCallback(Networking::ErrorResponse response) {
+ debug(9, "GlobalOptionsDialog: error response (%s, %ld):", (response.failed ? "failed" : "interrupted"), response.httpResponseCode);
+ debug(9, "%s", response.response.c_str());
+
+ if (!response.interrupted)
+ g_system->displayMessageOnOSD(_("Request failed.\nCheck your Internet connection."));
+}
+#endif
+
} // End of namespace GUI
diff --git a/gui/options.h b/gui/options.h
index 294b41794b..03dbdac492 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -37,9 +37,15 @@
#include "gui/fluidsynth-dialog.h"
#endif
+#ifdef USE_LIBCURL
+#include "backends/cloud/storage.h"
+#endif
+
namespace GUI {
+class LauncherDialog;
class CheckboxWidget;
+class EditTextWidget;
class PopUpWidget;
class SliderWidget;
class StaticTextWidget;
@@ -200,16 +206,18 @@ protected:
class GlobalOptionsDialog : public OptionsDialog {
public:
- GlobalOptionsDialog();
+ GlobalOptionsDialog(LauncherDialog *launcher);
~GlobalOptionsDialog();
void open();
void close();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ void handleTickle();
virtual void reflowLayout();
protected:
+ LauncherDialog *_launcher;
#ifdef GUI_ENABLE_KEYSDIALOG
KeysDialog *_keysDialog;
#endif
@@ -241,6 +249,43 @@ protected:
StaticTextWidget *_updatesPopUpDesc;
PopUpWidget *_updatesPopUp;
#endif
+
+#ifdef USE_CLOUD
+ //
+ // Cloud controls
+ //
+ uint32 _selectedStorageIndex;
+ StaticTextWidget *_storagePopUpDesc;
+ PopUpWidget *_storagePopUp;
+ StaticTextWidget *_storageUsernameDesc;
+ StaticTextWidget *_storageUsername;
+ StaticTextWidget *_storageUsedSpaceDesc;
+ StaticTextWidget *_storageUsedSpace;
+ StaticTextWidget *_storageLastSyncDesc;
+ StaticTextWidget *_storageLastSync;
+ ButtonWidget *_storageConnectButton;
+ ButtonWidget *_storageRefreshButton;
+ ButtonWidget *_storageDownloadButton;
+ ButtonWidget *_runServerButton;
+ StaticTextWidget *_serverInfoLabel;
+ ButtonWidget *_rootPathButton;
+ StaticTextWidget *_rootPath;
+ ButtonWidget *_rootPathClearButton;
+ StaticTextWidget *_serverPortDesc;
+ EditTextWidget *_serverPort;
+ ButtonWidget *_serverPortClearButton;
+ bool _redrawCloudTab;
+#ifdef USE_SDL_NET
+ bool _serverWasRunning;
+#endif
+
+ void setupCloudTab();
+#endif
+#ifdef USE_LIBCURL
+ void storageInfoCallback(Cloud::Storage::StorageInfoResponse response);
+ void storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response);
+ void storageErrorCallback(Networking::ErrorResponse response);
+#endif
};
} // End of namespace GUI
diff --git a/gui/remotebrowser.cpp b/gui/remotebrowser.cpp
new file mode 100644
index 0000000000..029320596f
--- /dev/null
+++ b/gui/remotebrowser.cpp
@@ -0,0 +1,232 @@
+/* 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/remotebrowser.h"
+#include "gui/widgets/list.h"
+
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "common/algorithm.h"
+
+#include "common/translation.h"
+#include <backends/networking/curl/request.h>
+#include <backends/cloud/storage.h>
+#include <backends/cloud/cloudmanager.h>
+#include "message.h"
+
+namespace GUI {
+
+enum {
+ kChooseCmd = 'Chos',
+ kGoUpCmd = 'GoUp'
+};
+
+RemoteBrowserDialog::RemoteBrowserDialog(const char *title):
+ Dialog("Browser"), _navigationLocked(false), _updateList(false), _showError(false),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ new StaticTextWidget(this, "Browser.Headline", title);
+ _currentPath = new StaticTextWidget(this, "Browser.Path", "DUMMY");
+
+ _fileList = new ListWidget(this, "Browser.List");
+ _fileList->setNumberingMode(kListNumberingOff);
+ _fileList->setEditable(false);
+
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(this, "Browser.Up", _("Go up"), _("Go to previous directory level"), kGoUpCmd);
+ else
+ new ButtonWidget(this, "Browser.Up", _c("Go up", "lowres"), _("Go to previous directory level"), kGoUpCmd);
+ new ButtonWidget(this, "Browser.Cancel", _("Cancel"), 0, kCloseCmd);
+ new ButtonWidget(this, "Browser.Choose", _("Choose"), 0, kChooseCmd);
+}
+
+RemoteBrowserDialog::~RemoteBrowserDialog() {
+ if (_workingRequest) {
+ _ignoreCallback = true;
+ _workingRequest->finish();
+ }
+}
+
+void RemoteBrowserDialog::open() {
+ Dialog::open();
+ listDirectory(Cloud::StorageFile());
+}
+
+void RemoteBrowserDialog::close() {
+ Dialog::close();
+ if (_workingRequest) {
+ _ignoreCallback = true;
+ _workingRequest->finish();
+ _ignoreCallback = false;
+ }
+}
+
+void RemoteBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kChooseCmd: {
+ // If nothing is selected in the list widget, choose the current dir.
+ // Else, choose the dir that is selected.
+ int selection = _fileList->getSelected();
+ if (selection >= 0)
+ _choice = _nodeContent[selection];
+ else
+ _choice = _node;
+ setResult(1);
+ close();
+ break;
+ }
+ case kGoUpCmd:
+ goUp();
+ break;
+ case kListItemActivatedCmd:
+ case kListItemDoubleClickedCmd:
+ if (_nodeContent[data].isDirectory()) {
+ _rememberedNodeContents[_node.path()] = _nodeContent;
+ listDirectory(_nodeContent[data]);
+ }
+ break;
+ case kListSelectionChangedCmd:
+ // We do not allow selecting directories,
+ // thus we will invalidate the selection
+ // when the user selects a directory over here.
+ if (data != (uint32)-1 && !_nodeContent[data].isDirectory())
+ _fileList->setSelected(-1);
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void RemoteBrowserDialog::handleTickle() {
+ if (_updateList) {
+ updateListing();
+ _updateList = false;
+ }
+
+ if (_showError) {
+ _showError = false;
+ MessageDialog alert(_("ScummVM could not access the directory!"));
+ alert.runModal();
+ }
+
+ Dialog::handleTickle();
+}
+
+void RemoteBrowserDialog::updateListing() {
+ // Update the path display
+ Common::String path = _node.path();
+ if (path.empty())
+ path = "/"; //root
+ if (_navigationLocked)
+ path = "Loading... " + path;
+ _currentPath->setLabel(path);
+
+ if (!_navigationLocked) {
+ // Populate the ListWidget
+ ListWidget::StringArray list;
+ ListWidget::ColorList colors;
+ for (Common::Array<Cloud::StorageFile>::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ if (i->isDirectory()) {
+ list.push_back(i->name() + "/");
+ colors.push_back(ThemeEngine::kFontColorNormal);
+ } else {
+ list.push_back(i->name());
+ colors.push_back(ThemeEngine::kFontColorAlternate);
+ }
+ }
+
+ _fileList->setList(list, &colors);
+ _fileList->scrollTo(0);
+ }
+
+ _fileList->setEnabled(!_navigationLocked);
+
+ // Finally, redraw
+ draw();
+}
+
+void RemoteBrowserDialog::goUp() {
+ if (_rememberedNodeContents.contains(_node.path()))
+ _rememberedNodeContents.erase(_node.path());
+
+ Common::String path = _node.path();
+ if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\'))
+ path.deleteLastChar();
+ if (path.empty()) {
+ _rememberedNodeContents.erase("");
+ } else {
+ for (int i = path.size() - 1; i >= 0; --i)
+ if (i == 0 || path[i] == '/' || path[i] == '\\') {
+ path.erase(i);
+ break;
+ }
+ }
+
+ listDirectory(Cloud::StorageFile(path, 0, 0, true));
+}
+
+void RemoteBrowserDialog::listDirectory(Cloud::StorageFile node) {
+ if (_navigationLocked || _workingRequest)
+ return;
+
+ if (_rememberedNodeContents.contains(node.path())) {
+ _nodeContent = _rememberedNodeContents[node.path()];
+ } else {
+ _navigationLocked = true;
+
+ _workingRequest = CloudMan.listDirectory(
+ node.path(),
+ new Common::Callback<RemoteBrowserDialog, Cloud::Storage::ListDirectoryResponse>(this, &RemoteBrowserDialog::directoryListedCallback),
+ new Common::Callback<RemoteBrowserDialog, Networking::ErrorResponse>(this, &RemoteBrowserDialog::directoryListedErrorCallback),
+ false
+ );
+ }
+
+ _backupNode = _node;
+ _node = node;
+ updateListing();
+}
+
+void RemoteBrowserDialog::directoryListedCallback(Cloud::Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ _navigationLocked = false;
+ _nodeContent = response.value;
+ Common::sort(_nodeContent.begin(), _nodeContent.end(), FileListOrder());
+ _updateList = true;
+}
+
+void RemoteBrowserDialog::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ _navigationLocked = false;
+ _node = _backupNode;
+ _updateList = true;
+ _showError = true;
+}
+
+} // End of namespace GUI
diff --git a/gui/remotebrowser.h b/gui/remotebrowser.h
new file mode 100644
index 0000000000..190d8c6895
--- /dev/null
+++ b/gui/remotebrowser.h
@@ -0,0 +1,84 @@
+/* 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_REMOTEBROWSER_DIALOG_H
+#define GUI_REMOTEBROWSER_DIALOG_H
+
+#include "gui/dialog.h"
+#include "common/fs.h"
+#include <backends/cloud/storagefile.h>
+#include <backends/networking/curl/request.h>
+#include <backends/cloud/storage.h>
+
+namespace GUI {
+
+class ListWidget;
+class StaticTextWidget;
+class CheckboxWidget;
+class CommandSender;
+
+class RemoteBrowserDialog : public Dialog {
+public:
+ RemoteBrowserDialog(const char *title);
+ virtual ~RemoteBrowserDialog();
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+
+ const Cloud::StorageFile &getResult() { return _choice; }
+
+protected:
+ ListWidget *_fileList;
+ StaticTextWidget *_currentPath;
+ Cloud::StorageFile _node, _backupNode;
+ Common::Array<Cloud::StorageFile> _nodeContent;
+ Common::HashMap<Common::String, Common::Array<Cloud::StorageFile> > _rememberedNodeContents;
+ Cloud::StorageFile _choice;
+ bool _navigationLocked;
+ bool _updateList;
+ bool _showError;
+
+ Networking::Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void updateListing();
+ void goUp();
+ void listDirectory(Cloud::StorageFile node);
+ void directoryListedCallback(Cloud::Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+
+ struct FileListOrder : public Common::BinaryFunction<Cloud::StorageFile, Cloud::StorageFile, bool> {
+ bool operator()(const Cloud::StorageFile &x, const Cloud::StorageFile &y) const {
+ if (x.isDirectory() != y.isDirectory()) {
+ return x.isDirectory(); //x < y (directory < not directory) or x > y (not directory > directory)
+ }
+
+ return x.name() < y.name();
+ }
+ };
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/saveload-dialog.cpp b/gui/saveload-dialog.cpp
index 3d4adfff2b..ae3612f778 100644
--- a/gui/saveload-dialog.cpp
+++ b/gui/saveload-dialog.cpp
@@ -21,6 +21,13 @@
*/
#include "gui/saveload-dialog.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/savessyncrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#endif
+
#include "common/translation.h"
#include "common/config-manager.h"
@@ -30,9 +37,68 @@
#include "gui/widgets/edittext.h"
#include "graphics/scaler.h"
+#include <common/savefile.h>
namespace GUI {
+#ifdef USE_LIBCURL
+
+enum {
+ kCancelSyncCmd = 'PDCS',
+ kBackgroundSyncCmd = 'PDBS'
+};
+
+SaveLoadCloudSyncProgressDialog::SaveLoadCloudSyncProgressDialog(bool canRunInBackground): Dialog("SaveLoadCloudSyncProgress"), _close(false) {
+ _label = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.TitleText", "Downloading saves...");
+ uint32 progress = (uint32)(100 * CloudMan.getSyncDownloadingProgress());
+ _progressBar = new SliderWidget(this, "SaveLoadCloudSyncProgress.ProgressBar");
+ _progressBar->setMinValue(0);
+ _progressBar->setMaxValue(100);
+ _progressBar->setValue(progress);
+ _progressBar->setEnabled(false);
+ _percentLabel = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.PercentText", Common::String::format("%u %%", progress));
+ new ButtonWidget(this, "SaveLoadCloudSyncProgress.Cancel", "Cancel", 0, kCancelSyncCmd, Common::ASCII_ESCAPE); // Cancel dialog
+ ButtonWidget *backgroundButton = new ButtonWidget(this, "SaveLoadCloudSyncProgress.Background", "Run in background", 0, kBackgroundSyncCmd, Common::ASCII_RETURN); // Confirm dialog
+ backgroundButton->setEnabled(canRunInBackground);
+ draw();
+}
+
+SaveLoadCloudSyncProgressDialog::~SaveLoadCloudSyncProgressDialog() {
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+}
+
+void SaveLoadCloudSyncProgressDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch(cmd) {
+ case kSavesSyncProgressCmd:
+ _percentLabel->setLabel(Common::String::format("%u%%", data));
+ _progressBar->setValue(data);
+ _progressBar->draw();
+ break;
+
+ case kCancelSyncCmd:
+ setResult(kCancelSyncCmd);
+ close();
+ break;
+
+ case kSavesSyncEndedCmd:
+ case kBackgroundSyncCmd:
+ _close = true;
+ break;
+ }
+
+ Dialog::handleCommand(sender, cmd, data);
+}
+
+void SaveLoadCloudSyncProgressDialog::handleTickle() {
+ if (_close) {
+ setResult(kBackgroundSyncCmd);
+ close();
+ }
+
+ Dialog::handleTickle();
+}
+#endif
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) {
const Common::String &userConfig = ConfMan.get("gui_saveload_chooser", Common::ConfigManager::kApplicationDomain);
@@ -45,9 +111,9 @@ SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) {
g_gui.checkScreenChange();
if (g_gui.getWidth() >= 640 && g_gui.getHeight() >= 400
- && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo)
- && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail)
- && userConfig.equalsIgnoreCase("grid")) {
+ && 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.
@@ -66,7 +132,8 @@ enum {
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)
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode),
+ _dialogWasShown(false)
#ifndef DISABLE_SAVELOADCHOOSER_GRID
, _listButton(0), _gridButton(0)
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -78,7 +145,8 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, c
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)
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode),
+ _dialogWasShown(false)
#ifndef DISABLE_SAVELOADCHOOSER_GRID
, _listButton(0), _gridButton(0)
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -88,12 +156,27 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const b
#endif // !DISABLE_SAVELOADCHOOSER_GRID
}
+SaveLoadChooserDialog::~SaveLoadChooserDialog() {
+#ifdef USE_LIBCURL
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+#endif
+}
+
void SaveLoadChooserDialog::open() {
Dialog::open();
// So that quitting ScummVM will not cause the dialog result to say a
// saved game was selected.
setResult(-1);
+
+ _dialogWasShown = false;
+}
+
+void SaveLoadChooserDialog::close() {
+#ifdef USE_LIBCURL
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+#endif
+ Dialog::close();
}
int SaveLoadChooserDialog::run(const Common::String &target, const MetaEngine *metaEngine) {
@@ -132,9 +215,53 @@ void SaveLoadChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uin
}
#endif // !DISABLE_SAVELOADCHOOSER_GRID
+#ifdef USE_LIBCURL
+ if (cmd == kSavesSyncProgressCmd || cmd == kSavesSyncEndedCmd) {
+ //this dialog only gets these commands if the progress dialog was shown and user clicked "run in background"
+ return updateSaveList();
+ }
+#endif
+
return Dialog::handleCommand(sender, cmd, data);
}
+#ifdef USE_LIBCURL
+void SaveLoadChooserDialog::runSaveSync(bool hasSavepathOverride) {
+ if (!CloudMan.isSyncing()) {
+ if (hasSavepathOverride) {
+ ConnMan.showCloudDisabledIcon();
+ } else {
+ Cloud::SavesSyncRequest *request = CloudMan.syncSaves();
+ if (request)
+ request->setTarget(this);
+ }
+ }
+}
+#endif
+
+void SaveLoadChooserDialog::handleTickle() {
+#ifdef USE_LIBCURL
+ if (!_dialogWasShown && CloudMan.isSyncing()) {
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles();
+ if (!files.empty()) {
+ {
+ SaveLoadCloudSyncProgressDialog dialog(_metaEngine ? _metaEngine->hasFeature(MetaEngine::kSimpleSavesNames) : false);
+ CloudMan.setSyncTarget(&dialog);
+ int result = dialog.runModal();
+ if (result == kCancelSyncCmd) {
+ CloudMan.cancelSync();
+ }
+ }
+ //dialog changes syncTarget to nullptr after that }
+ CloudMan.setSyncTarget(this);
+ _dialogWasShown = true;
+ updateSaveList();
+ }
+ }
+#endif
+ Dialog::handleTickle();
+}
+
void SaveLoadChooserDialog::reflowLayout() {
#ifndef DISABLE_SAVELOADCHOOSER_GRID
addChooserButtons();
@@ -152,6 +279,46 @@ void SaveLoadChooserDialog::reflowLayout() {
Dialog::reflowLayout();
}
+void SaveLoadChooserDialog::updateSaveList() {
+#ifdef USE_LIBCURL
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ g_system->getSavefileManager()->updateSavefilesList(files);
+#endif
+ listSaves();
+}
+
+void SaveLoadChooserDialog::listSaves() {
+ if (!_metaEngine) return; //very strange
+ _saveList = _metaEngine->listSaves(_target.c_str());
+
+#ifdef USE_LIBCURL
+ //if there is Cloud support, add currently synced files as "locked" saves in the list
+ if (_metaEngine->hasFeature(MetaEngine::kSimpleSavesNames)) {
+ Common::String pattern = _target + ".###";
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ for (uint32 i = 0; i < files.size(); ++i) {
+ if (!files[i].matchString(pattern, true))
+ continue;
+
+ //make up some slot number
+ int slotNum = 0;
+ for (uint32 j = (files[i].size() > 3 ? files[i].size() - 3 : 0); j < files[i].size(); ++j) { //3 last chars
+ char c = files[i][j];
+ if (c < '0' || c > '9')
+ continue;
+ slotNum = slotNum * 10 + (c - '0');
+ }
+
+ SaveStateDescriptor slot(slotNum, files[i]);
+ slot.setLocked(true);
+ _saveList.push_back(slot);
+ }
+
+ Common::sort(_saveList.begin(), _saveList.end(), SaveStateDescriptorSlotComparator());
+ }
+#endif
+}
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
void SaveLoadChooserDialog::addChooserButtons() {
if (_listButton) {
@@ -353,6 +520,7 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
bool isDeletable = _delSupport;
bool isWriteProtected = false;
bool startEditMode = _list->isEditable();
+ bool isLocked = false;
// We used to support letting the themes specify the fill color with our
// initial theme based GUI. But this support was dropped.
@@ -362,10 +530,11 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
_playtime->setLabel(_("No playtime saved"));
if (selItem >= 0 && _metaInfoSupport) {
- SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot());
+ SaveStateDescriptor desc = (_saveList[selItem].getLocked() ? _saveList[selItem] : _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot()));
isDeletable = desc.getDeletableFlag() && _delSupport;
isWriteProtected = desc.getWriteProtectedFlag();
+ isLocked = desc.getLocked();
// Don't allow the user to change the description of write protected games
if (isWriteProtected)
@@ -398,9 +567,9 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
if (_list->isEditable()) {
- // Disable the save button if nothing is selected, or if the selected
- // game is write protected
- _chooseButton->setEnabled(selItem >= 0 && !isWriteProtected);
+ // Disable the save button if slot is locked, nothing is selected,
+ // or if the selected game is write protected
+ _chooseButton->setEnabled(!isLocked && selItem >= 0 && !isWriteProtected);
if (startEditMode) {
_list->startEditMode();
@@ -412,13 +581,13 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
}
}
} else {
- // Disable the load button if nothing is selected, or if an empty
- // list item is selected.
- _chooseButton->setEnabled(selItem >= 0 && !_list->getSelectedString().empty());
+ // Disable the load button if slot is locked, nothing is selected,
+ // or if an empty list item is selected.
+ _chooseButton->setEnabled(!isLocked && 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()));
+ _deleteButton->setEnabled(isDeletable && !isLocked && (selItem >= 0) && (!_list->getSelectedString().empty()));
if (redraw) {
_gfxWidget->draw();
@@ -463,7 +632,7 @@ void SaveLoadChooserSimple::close() {
}
void SaveLoadChooserSimple::updateSaveList() {
- _saveList = _metaEngine->listSaves(_target.c_str());
+ SaveLoadChooserDialog::updateSaveList();
int curSlot = 0;
int saveSlot = 0;
@@ -496,7 +665,7 @@ void SaveLoadChooserSimple::updateSaveList() {
description = _("Untitled savestate");
colors.push_back(ThemeEngine::kFontColorAlternate);
} else {
- colors.push_back(ThemeEngine::kFontColorNormal);
+ colors.push_back((x->getLocked() ? ThemeEngine::kFontColorAlternate : ThemeEngine::kFontColorNormal));
}
saveNames.push_back(description);
@@ -524,6 +693,7 @@ void SaveLoadChooserSimple::updateSaveList() {
}
_list->setList(saveNames, &colors);
+ draw();
}
// SaveLoadChooserGrid implementation
@@ -619,10 +789,16 @@ void SaveLoadChooserGrid::handleMouseWheel(int x, int y, int direction) {
}
}
+void SaveLoadChooserGrid::updateSaveList() {
+ SaveLoadChooserDialog::updateSaveList();
+ updateSaves();
+ draw();
+}
+
void SaveLoadChooserGrid::open() {
SaveLoadChooserDialog::open();
- _saveList = _metaEngine->listSaves(_target.c_str());
+ listSaves();
_resultString.clear();
// Load information to restore the last page the user had open.
@@ -863,7 +1039,7 @@ void SaveLoadChooserGrid::updateSaves() {
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);
+ SaveStateDescriptor desc = (_saveList[i].getLocked() ? _saveList[i] : _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot));
SlotButton &curButton = _buttons[curNum];
curButton.setVisible(true);
const Graphics::Surface *thumbnail = desc.getThumbnail();
@@ -908,6 +1084,10 @@ void SaveLoadChooserGrid::updateSaves() {
} else {
curButton.button->setEnabled(true);
}
+
+ //that would make it look "disabled" if slot is locked
+ curButton.button->setEnabled(!desc.getLocked());
+ curButton.description->setEnabled(!desc.getLocked());
}
const uint numPages = (_entriesPerPage != 0 && !_saveList.empty()) ? ((_saveList.size() + _entriesPerPage - 1) / _entriesPerPage) : 1;
diff --git a/gui/saveload-dialog.h b/gui/saveload-dialog.h
index 31f28f6452..fb2833355f 100644
--- a/gui/saveload-dialog.h
+++ b/gui/saveload-dialog.h
@@ -30,6 +30,25 @@
namespace GUI {
+#ifdef USE_LIBCURL
+enum SaveLoadCloudSyncProgress {
+ kSavesSyncProgressCmd = 'SSPR',
+ kSavesSyncEndedCmd = 'SSEN'
+};
+
+class SaveLoadCloudSyncProgressDialog : public Dialog { //protected?
+ StaticTextWidget *_label, *_percentLabel;
+ SliderWidget *_progressBar;
+ bool _close;
+public:
+ SaveLoadCloudSyncProgressDialog(bool canRunInBackground);
+ virtual ~SaveLoadCloudSyncProgressDialog();
+
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+};
+#endif
+
#define kSwitchSaveLoadDialog -2
// TODO: We might want to disable the grid based save/load chooser for more
@@ -53,13 +72,21 @@ 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 ~SaveLoadChooserDialog();
virtual void open();
+ virtual void close();
virtual void reflowLayout();
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+#ifdef USE_LIBCURL
+ virtual void runSaveSync(bool hasSavepathOverride);
+#endif
+
+ virtual void handleTickle();
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
virtual SaveLoadChooserType getType() const = 0;
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -70,6 +97,19 @@ public:
protected:
virtual int runIntern() = 0;
+ /** Common function to refresh the list on the screen. */
+ virtual void updateSaveList();
+
+ /**
+ * Common function to get saves list from MetaEngine.
+ *
+ * It also checks whether there are some locked saves
+ * because of saves sync and adds such saves as locked
+ * slots. User sees these slots, but is unable to save
+ * or load from these.
+ */
+ virtual void listSaves();
+
const bool _saveMode;
const MetaEngine *_metaEngine;
bool _delSupport;
@@ -78,6 +118,8 @@ protected:
bool _saveDateSupport;
bool _playTimeSupport;
Common::String _target;
+ bool _dialogWasShown;
+ SaveStateList _saveList;
#ifndef DISABLE_SAVELOADCHOOSER_GRID
ButtonWidget *_listButton;
@@ -106,6 +148,8 @@ public:
virtual void open();
virtual void close();
+protected:
+ virtual void updateSaveList();
private:
virtual int runIntern();
@@ -118,10 +162,8 @@ private:
StaticTextWidget *_time;
StaticTextWidget *_playtime;
- SaveStateList _saveList;
String _resultString;
- void updateSaveList();
void updateSelection(bool redraw);
};
@@ -164,13 +206,13 @@ public:
protected:
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
virtual void handleMouseWheel(int x, int y, int direction);
+ virtual void updateSaveList();
private:
virtual int runIntern();
uint _columns, _lines;
uint _entriesPerPage;
uint _curPage;
- SaveStateList _saveList;
ButtonWidget *_nextButton;
ButtonWidget *_prevButton;
diff --git a/gui/saveload.cpp b/gui/saveload.cpp
index b94d30289b..3072aa6082 100644
--- a/gui/saveload.cpp
+++ b/gui/saveload.cpp
@@ -87,6 +87,10 @@ int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, con
if (!_impl)
return -1;
+#ifdef USE_LIBCURL
+ _impl->runSaveSync(ConfMan.hasKey("savepath", target));
+#endif
+
// Set up the game domain as newly active domain, so
// target specific savepath will be checked
String oldDomain = ConfMan.getActiveDomainName();
diff --git a/gui/storagewizarddialog.cpp b/gui/storagewizarddialog.cpp
new file mode 100644
index 0000000000..22b87f5244
--- /dev/null
+++ b/gui/storagewizarddialog.cpp
@@ -0,0 +1,363 @@
+/* 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/storagewizarddialog.h"
+#include "gui/gui-manager.h"
+#include "gui/message.h"
+#include "gui/widget.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/scrollcontainer.h"
+#include "backends/cloud/cloudmanager.h"
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
+#include "common/translation.h"
+
+namespace GUI {
+
+enum {
+ kConnectCmd = 'Cnnt',
+ kCodeBoxCmd = 'CdBx',
+ kOpenUrlCmd = 'OpUr',
+ kPasteCodeCmd = 'PsCd',
+ kStorageWizardContainerReflowCmd = 'SWCr'
+};
+
+StorageWizardDialog::StorageWizardDialog(uint32 storageId):
+ Dialog("GlobalOptions_Cloud_ConnectionWizard"), _storageId(storageId), _close(false) {
+#ifdef USE_SDL_NET
+ _stopServerOnClose = false;
+#endif
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ ScrollContainerWidget *container = new ScrollContainerWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Container", kStorageWizardContainerReflowCmd);
+ container->setTarget(this);
+
+ Common::String headline = Common::String::format(_("%s Storage Connection Wizard"), CloudMan.listStorages()[_storageId].c_str());
+ _headlineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Headline", headline);
+
+ _navigateLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.NavigateLine", _("Navigate to the following URL:"));
+ _urlLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.URLLine", getUrl());
+
+ _returnLine1 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine1", _("Obtain the code from the storage, enter it"));
+ _returnLine2 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine2", _("in the following field and press 'Connect':"));
+ for (uint32 i = 0; i < CODE_FIELDS; ++i)
+ _codeWidget[i] = new EditTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CodeBox" + Common::String::format("%d", i+1), "", 0, kCodeBoxCmd);
+ _messageWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.MessageLine", "");
+
+ // Buttons
+ _cancelWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CancelButton", _("Cancel"), 0, kCloseCmd);
+ _openUrlWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.OpenUrlButton", _("Open URL"), 0, kOpenUrlCmd);
+ _pasteCodeWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.PasteCodeButton", _("Paste"), _("Pastes clipboard contents into fields"), kPasteCodeCmd);
+ _connectWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ConnectButton", _("Connect"), 0, kConnectCmd);
+
+ // Initialy the code is empty, so disable the connect button
+ _connectWidget->setEnabled(false);
+
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ // hide fields and even the button if local webserver is on
+ _returnLine1->setLabel(_("You will be directed to ScummVM's page where"));
+ _returnLine2->setLabel(_("you should allow it to access your storage."));
+ }
+
+ _picture = new GraphicsWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Picture");
+#ifndef DISABLE_FANCY_THEMES
+ if (g_gui.theme()->supportsImages()) {
+ _picture->useThemeTransparency(true);
+ switch (_storageId) {
+ case Cloud::kStorageDropboxId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDropboxLogo));
+ break;
+ case Cloud::kStorageOneDriveId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageOneDriveLogo));
+ break;
+ case Cloud::kStorageGoogleDriveId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageGoogleDriveLogo));
+ break;
+ case Cloud::kStorageBoxId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageBoxLogo));
+ break;
+ }
+ }
+#endif
+
+ containerWidgetsReflow();
+}
+
+void StorageWizardDialog::open() {
+ Dialog::open();
+
+ if (CloudMan.isWorking()) {
+ bool doClose = true;
+
+ MessageDialog alert(_("Another Storage is active. Do you want to interrupt it?"), _("Yes"), _("No"));
+ if (alert.runModal() == GUI::kMessageOK) {
+ if (CloudMan.isDownloading())
+ CloudMan.cancelDownload();
+ if (CloudMan.isSyncing())
+ CloudMan.cancelSync();
+
+ // I believe it still would return `true` here, but just in case
+ if (CloudMan.isWorking()) {
+ MessageDialog alert2(_("Wait until current Storage finishes up and try again."));
+ alert2.runModal();
+ } else {
+ doClose = false;
+ }
+ }
+
+ if (doClose) {
+ close();
+ return;
+ }
+ }
+
+#ifdef USE_SDL_NET
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ _stopServerOnClose = !LocalServer.isRunning();
+ LocalServer.start(true); // using "minimal mode" (no "/files", "/download", etc available)
+ LocalServer.indexPageHandler().setTarget(this);
+ }
+#endif
+}
+
+void StorageWizardDialog::close() {
+#ifdef USE_SDL_NET
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ if (_stopServerOnClose)
+ LocalServer.stopOnIdle();
+ LocalServer.indexPageHandler().setTarget(nullptr);
+ }
+#endif
+ Dialog::close();
+}
+
+void StorageWizardDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kCodeBoxCmd: {
+ Common::String code, message;
+ uint32 correctFields = 0;
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ Common::String subcode = _codeWidget[i]->getEditString();
+ if (subcode.size() == 0) {
+ ++correctFields;
+ continue;
+ }
+ bool correct = correctChecksum(subcode);
+ if (correct) {
+ code += subcode;
+ code.deleteLastChar();
+ ++correctFields;
+ } else {
+ if (i == correctFields) { //first incorrect field
+ message += Common::String::format("#%d", i + 1);
+ } else {
+ message += Common::String::format(", #%d", i + 1);
+ }
+ }
+ }
+
+ if (message.size() > 0) {
+ Common::String messageTemplate;
+ if (CODE_FIELDS - correctFields == 1)
+ messageTemplate = _("Field %s has a mistake in it.");
+ else
+ messageTemplate = _("Fields %s have mistakes in them.");
+ message = Common::String::format(messageTemplate.c_str(), message.c_str());
+ }
+
+ bool ok = false;
+ if (correctFields == CODE_FIELDS && code.size() > 0) {
+ //the last 3 chars must be an encoded crc16
+ if (code.size() > 3) {
+ uint32 size = code.size();
+ uint32 gotcrc = decodeHashchar(code[size - 3]) | (decodeHashchar(code[size - 2]) << 6) | (decodeHashchar(code[size - 1]) << 12);
+ code.erase(size - 3);
+ uint32 crc = crc16(code);
+ ok = (crc == gotcrc);
+ }
+ if (ok)
+ message = _("All OK!");
+ else
+ message = _("Invalid code");
+ }
+ _connectWidget->setEnabled(ok);
+ _messageWidget->setLabel(message);
+ break;
+ }
+ case kOpenUrlCmd: {
+ if (!g_system->openUrl(getUrl())) {
+ MessageDialog alert(_("Failed to open URL!\nPlease navigate to this page manually."));
+ alert.runModal();
+ }
+ break;
+ }
+ case kPasteCodeCmd: {
+ if (g_system->hasTextInClipboard()) {
+ Common::String message = g_system->getTextFromClipboard();
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ if (message.empty()) break;
+ Common::String subcode = "";
+ for (uint32 j = 0; j < message.size(); ++j) {
+ if (message[j] == ' ') {
+ message.erase(0, j+1);
+ break;
+ }
+ subcode += message[j];
+ if (j+1 == message.size()) {
+ message = "";
+ break;
+ }
+ }
+ _codeWidget[i]->setEditString(subcode);
+ }
+ handleCommand(sender, kCodeBoxCmd, data);
+ draw();
+ }
+ break;
+ }
+ case kConnectCmd: {
+ Common::String code;
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ Common::String subcode = _codeWidget[i]->getEditString();
+ if (subcode.size() == 0)
+ continue;
+ code += subcode;
+ code.deleteLastChar();
+ }
+ if (code.size() > 3) {
+ code.erase(code.size() - 3);
+ CloudMan.connectStorage(_storageId, code);
+ setResult(1);
+ close();
+ }
+ break;
+ }
+#ifdef USE_SDL_NET
+ case kStorageCodePassedCmd:
+ CloudMan.connectStorage(_storageId, LocalServer.indexPageHandler().code());
+ _close = true;
+ break;
+#endif
+ case kStorageWizardContainerReflowCmd:
+ containerWidgetsReflow();
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void StorageWizardDialog::handleTickle() {
+ if (_close) {
+ setResult(1);
+ close();
+ }
+
+ Dialog::handleTickle();
+}
+
+void StorageWizardDialog::containerWidgetsReflow() {
+ // contents
+ if (_headlineWidget) _headlineWidget->setVisible(true);
+ if (_navigateLineWidget) _navigateLineWidget->setVisible(true);
+ if (_urlLineWidget) _urlLineWidget->setVisible(true);
+ if (_returnLine1) _returnLine1->setVisible(true);
+ if (_returnLine2) _returnLine2->setVisible(true);
+
+ bool showFields = (!Cloud::CloudManager::couldUseLocalServer());
+ for (uint32 i = 0; i < CODE_FIELDS; ++i)
+ _codeWidget[i]->setVisible(showFields);
+ _messageWidget->setVisible(showFields);
+
+ // left column / first bottom row
+ if (_picture) {
+ _picture->setVisible(g_system->getOverlayWidth() > 320);
+ }
+ if (_openUrlWidget) {
+ bool visible = g_system->hasFeature(OSystem::kFeatureOpenUrl);
+ _openUrlWidget->setVisible(visible);
+ }
+ if (_pasteCodeWidget) {
+ bool visible = showFields && g_system->hasFeature(OSystem::kFeatureClipboardSupport);
+ _pasteCodeWidget->setVisible(visible);
+ }
+
+ // bottom row
+ if (_cancelWidget) _cancelWidget->setVisible(true);
+ if (_connectWidget) {
+ _connectWidget->setVisible(showFields);
+ }
+}
+
+Common::String StorageWizardDialog::getUrl() const {
+ Common::String url = "https://www.scummvm.org/c/";
+ switch (_storageId) {
+ case Cloud::kStorageDropboxId:
+ url += "db";
+ break;
+ case Cloud::kStorageOneDriveId:
+ url += "od";
+ break;
+ case Cloud::kStorageGoogleDriveId:
+ url += "gd";
+ break;
+ case Cloud::kStorageBoxId:
+ url += "bx";
+ break;
+ }
+
+ if (Cloud::CloudManager::couldUseLocalServer())
+ url += "s";
+
+ return url;
+}
+
+int StorageWizardDialog::decodeHashchar(char c) {
+ const char HASHCHARS[65] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!";
+ for (uint32 i = 0; i < 64; ++i)
+ if (c == HASHCHARS[i])
+ return i;
+ return -1;
+}
+
+bool StorageWizardDialog::correctChecksum(Common::String s) {
+ if (s.size() == 0)
+ return false; //no last char
+ int providedChecksum = decodeHashchar(s.lastChar());
+ int calculatedChecksum = 0x2A; //any initial value would do, but it must equal to the one used on the page where these checksums were generated
+ for (uint32 i = 0; i < s.size()-1; ++i) {
+ calculatedChecksum = calculatedChecksum ^ s[i];
+ }
+ return providedChecksum == (calculatedChecksum % 64);
+}
+
+uint32 StorageWizardDialog::crc16(Common::String s) { //"CRC16_CCITT_FALSE"
+ uint32 crc = 0xFFFF, x;
+ for (uint32 i = 0; i < s.size(); ++i) {
+ x = ((crc >> 8) ^ s[i]) & 0xFF;
+ x ^= x >> 4;
+ crc = ((crc << 8) ^ (x << 12) ^ (x << 5) ^ x) & 0xFFFF;
+ }
+ return crc;
+}
+
+} // End of namespace GUI
diff --git a/gui/storagewizarddialog.h b/gui/storagewizarddialog.h
new file mode 100644
index 0000000000..61bc8ac873
--- /dev/null
+++ b/gui/storagewizarddialog.h
@@ -0,0 +1,107 @@
+/* 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_STORAGEWIZARDDIALOG_H
+#define GUI_STORAGEWIZARDDIALOG_H
+
+#include "gui/dialog.h"
+#include "common/str.h"
+
+namespace GUI {
+
+class CommandSender;
+class EditTextWidget;
+class StaticTextWidget;
+class ButtonWidget;
+class GraphicsWidget;
+
+#ifdef USE_SDL_NET
+enum StorageWizardDialogCommands {
+ kStorageCodePassedCmd = 'SWDC'
+};
+#endif
+
+class StorageWizardDialog : public Dialog {
+ static const uint32 CODE_FIELDS = 8;
+ uint32 _storageId;
+
+ StaticTextWidget *_headlineWidget;
+ StaticTextWidget *_navigateLineWidget;
+ StaticTextWidget *_urlLineWidget;
+ StaticTextWidget *_returnLine1;
+ StaticTextWidget *_returnLine2;
+ EditTextWidget *_codeWidget[CODE_FIELDS];
+ StaticTextWidget *_messageWidget;
+
+ GraphicsWidget *_picture;
+ ButtonWidget *_openUrlWidget;
+ ButtonWidget *_pasteCodeWidget;
+
+ ButtonWidget *_cancelWidget;
+ ButtonWidget *_connectWidget;
+
+ bool _close;
+#ifdef USE_SDL_NET
+ bool _stopServerOnClose;
+#endif
+
+ /** Hides/shows widgets for Container to work with them correctly. */
+ void containerWidgetsReflow();
+
+ /** Return short scummvm.org URL for user to navigate to. */
+ Common::String getUrl() const;
+
+ /**
+ * Return the value corresponding to the given character.
+ *
+ * There is a value corresponding to each of 64 selected
+ * printable characters (0-9, A-Z, a-z, ? and !).
+ *
+ * When given another character, -1 is returned.
+ */
+ int decodeHashchar(char c);
+
+ /**
+ * Return whether checksum is correct.
+ *
+ * The last character of the string is treated as
+ * the checksum of all the others (decoded with
+ * decodeHashchar()).
+ *
+ * Checksum = (c[0] ^ c[1] ^ ...) % 64
+ */
+ bool correctChecksum(Common::String s);
+
+ /** The "CRC16_CCITT_FALSE" CRC-16 algorithm. */
+ uint32 crc16(Common::String s);
+public:
+ StorageWizardDialog(uint32 storageId);
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index c0ea733de8..d46a603830 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1092,6 +1092,192 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container'/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StoragePopupDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StoragePopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageUsernameDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageUsernameLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageUsedSpaceDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageUsedSpaceLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageLastSyncDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageLastSyncLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"<widget name='RefreshButton' "
+"type='Button' "
+"/>"
+"<widget name='DownloadButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='RunServerButton' "
+"type='Button' "
+"/>"
+"<widget name='ServerInfoLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='ServerPortDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='ServerPortEditText' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ServerPortClearButton' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,8' spacing='8'>"
+"<widget name='RemoteDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='LocalDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ProgressBar' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<widget name='DownloadSize' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadSpeed' "
+"height='Globals.Line.Height' "
+"/>"
+"<space/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10'>"
+"<widget name='MainButton' "
+"type='Button' "
+"/>"
+"<space/>"
+"<widget name='CloseButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='Picture' "
+"type='OptionsLabel' "
+"/>"
+"<layout type='vertical' padding='0,0,0,0' spacing='6'>"
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"<widget name='NavigateLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='URLLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"<widget name='ReturnLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ReturnLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox1' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox2' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox3' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox4' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox5' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox6' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox7' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox8' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<widget name='MessageLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='6' />"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='CancelButton' "
+"type='Button' "
+"/>"
+"<widget name='OpenUrlButton' "
+"type='Button' "
+"/>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
@@ -1592,6 +1778,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>"
+"<layout type='vertical' padding='8,8,8,8' center='true'>"
+"<widget name='TitleText' "
+"width='496' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<widget name='ProgressBar' "
+"width='496' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"width='496' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>"
+"<widget name='Cancel' "
+"width='150' "
+"height='Globals.Button.Height' "
+"/>"
+"<widget name='Background' "
+"width='150' "
+"height='Globals.Button.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='SavenameDialog' overlays='screen_center'>"
"<layout type='vertical' padding='8,8,8,8'>"
"<widget name='DescriptionText' "
@@ -2394,6 +2611,197 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container'/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StoragePopupDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StoragePopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageUsernameDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageUsernameLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageUsedSpaceDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageUsedSpaceLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageLastSyncDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageLastSyncLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"<widget name='RefreshButton' "
+"type='Button' "
+"/>"
+"<widget name='DownloadButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='RunServerButton' "
+"type='Button' "
+"/>"
+"<widget name='ServerInfoLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='ServerPortDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='ServerPortEditText' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='ServerPortClearButton' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='8,8,8,4' spacing='8'>"
+"<widget name='RemoteDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='LocalDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ProgressBar' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<widget name='DownloadSize' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadSpeed' "
+"height='Globals.Line.Height' "
+"/>"
+"<space/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6'>"
+"<widget name='MainButton' "
+"type='Button' "
+"/>"
+"<space/>"
+"<widget name='CloseButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='vertical' padding='0,0,0,0' spacing='4'>"
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='2' />"
+"<widget name='NavigateLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='URLLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='2' />"
+"<widget name='ReturnLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ReturnLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox1' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox2' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox3' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox4' "
+"width='60' "
+"height='16' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox5' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox6' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox7' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox8' "
+"width='60' "
+"height='16' "
+"/>"
+"</layout>"
+"<widget name='MessageLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='CancelButton' "
+"type='Button' "
+"/>"
+"<widget name='OpenUrlButton' "
+"type='Button' "
+"/>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
@@ -2888,6 +3296,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>"
+"<layout type='vertical' padding='8,8,8,8' center='true'>"
+"<widget name='TitleText' "
+"width='240' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<widget name='ProgressBar' "
+"width='240' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"width='240' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>"
+"<widget name='Cancel' "
+"width='100' "
+"height='Globals.Button.Height' "
+"/>"
+"<widget name='Background' "
+"width='100' "
+"height='Globals.Button.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='SavenameDialog' overlays='screen_center'>"
"<layout type='vertical' padding='8,8,8,8'>"
"<widget name='DescriptionText' "
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index 561f2a5dd3..e574fe5039 100644
--- a/gui/themes/scummclassic.zip
+++ b/gui/themes/scummclassic.zip
Binary files differ
diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx
index 49ecea2e11..1311345621 100644
--- a/gui/themes/scummclassic/classic_gfx.stx
+++ b/gui/themes/scummclassic/classic_gfx.stx
@@ -192,7 +192,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_idle' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
@@ -226,7 +226,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
@@ -314,7 +314,7 @@
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -325,7 +325,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -336,20 +336,20 @@
padding = '0, 0, 7, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_idle' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -360,7 +360,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -371,7 +371,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -394,7 +394,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -411,13 +411,13 @@
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -428,7 +428,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -439,7 +439,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -462,7 +462,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -479,13 +479,13 @@
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -496,7 +496,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -507,7 +507,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -552,7 +552,7 @@
fill = 'foreground'
fg_color = 'green'
/>
- </drawdata>
+ </drawdata>
<drawdata id = 'button_idle' cache = 'false'>
<text font = 'text_button'
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 5172326859..c67631ad22 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -524,6 +524,221 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1042,6 +1257,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '496'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index 0013b91ee2..e19256695c 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -529,6 +529,226 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPathPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1040,6 +1260,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '240'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index d80c481ffc..78b62d4286 100644
--- a/gui/themes/scummmodern.zip
+++ b/gui/themes/scummmodern.zip
Binary files differ
diff --git a/gui/themes/scummmodern/box.bmp b/gui/themes/scummmodern/box.bmp
new file mode 100644
index 0000000000..21fb650f02
--- /dev/null
+++ b/gui/themes/scummmodern/box.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/dropbox.bmp b/gui/themes/scummmodern/dropbox.bmp
new file mode 100644
index 0000000000..4ed95f0009
--- /dev/null
+++ b/gui/themes/scummmodern/dropbox.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/googledrive.bmp b/gui/themes/scummmodern/googledrive.bmp
new file mode 100644
index 0000000000..30377a5f74
--- /dev/null
+++ b/gui/themes/scummmodern/googledrive.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/onedrive.bmp b/gui/themes/scummmodern/onedrive.bmp
new file mode 100644
index 0000000000..cd26d71d3c
--- /dev/null
+++ b/gui/themes/scummmodern/onedrive.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx
index 3a1ec5a5f0..e3d2152e4b 100644
--- a/gui/themes/scummmodern/scummmodern_gfx.stx
+++ b/gui/themes/scummmodern/scummmodern_gfx.stx
@@ -119,6 +119,10 @@
<bitmap filename = 'editbtn_small.bmp'/>
<bitmap filename = 'switchbtn_small.bmp'/>
<bitmap filename = 'fastreplay_small.bmp'/>
+ <bitmap filename = 'dropbox.bmp'/>
+ <bitmap filename = 'onedrive.bmp'/>
+ <bitmap filename = 'googledrive.bmp'/>
+ <bitmap filename = 'box.bmp'/>
</bitmaps>
<fonts>
@@ -181,7 +185,7 @@
<text_color id = 'color_button_hover'
color = 'white'
/>
-
+
<text_color id = 'color_alternative_inverted'
color = 'white'
/>
@@ -327,7 +331,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y>399'>
<drawstep func = 'roundedsq'
radius = '10'
@@ -350,7 +354,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '10'
@@ -476,7 +480,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -487,7 +491,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -498,14 +502,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_idle' cache = 'false' resolution ='y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -515,7 +519,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -526,7 +530,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -537,7 +541,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -566,7 +570,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -577,14 +581,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal_hover'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -594,7 +598,7 @@
bg_color = 'xtrabrightred'
shadow = '2'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -605,7 +609,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -616,7 +620,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -645,7 +649,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -656,14 +660,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal_hover'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -673,7 +677,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -684,7 +688,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -695,7 +699,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -777,7 +781,7 @@
bevel = '1'
bevel_color = 'black'
/>
- </drawdata>
+ </drawdata>
<!-- Idle button -->
<drawdata id = 'button_idle' cache = 'false'>
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index 026fa7bc64..bb182c9dbb 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -538,6 +538,221 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1056,6 +1271,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '496'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index 169e61a9bb..3416fbeb5e 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -527,6 +527,226 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPathPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1038,6 +1258,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '240'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummtheme.py b/gui/themes/scummtheme.py
index d5fa4dfca7..365787275b 100755
--- a/gui/themes/scummtheme.py
+++ b/gui/themes/scummtheme.py
@@ -5,7 +5,7 @@ import re
import os
import zipfile
-THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf')
+THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf', '.png')
def buildTheme(themeName):
if not os.path.isdir(themeName) or not os.path.isfile(os.path.join(themeName, "THEMERC")):
diff --git a/gui/themes/translations.dat b/gui/themes/translations.dat
index 7533d41454..49e03a05b9 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 f2a29c3100..898d5671c6 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -398,25 +398,28 @@ void ButtonWidget::setUnpressedState() {
PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey)
: ButtonWidget(boss, x, y, w, h, "", tooltip, cmd, hotkey),
- _gfx(), _alpha(256), _transparency(false) {
+ _alpha(255), _transparency(false), _showButton(true), _isAlpha(false) {
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
_type = kButtonWidget;
+ _mode = ThemeEngine::kAutoScaleNone;
}
PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip, uint32 cmd, uint8 hotkey)
: ButtonWidget(boss, name, "", tooltip, cmd, hotkey),
- _gfx(), _alpha(256), _transparency(false) {
+ _alpha(255), _transparency(false), _showButton(true), _isAlpha(false) {
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
_type = kButtonWidget;
+ _mode = ThemeEngine::kAutoScaleNone;
}
PicButtonWidget::~PicButtonWidget() {
- _gfx.free();
+ for (int i = 0; i < kPicButtonStateMax + 1; i++)
+ _gfx[i].free();
}
-void PicButtonWidget::setGfx(const Graphics::Surface *gfx) {
- _gfx.free();
+void PicButtonWidget::setGfx(const Graphics::Surface *gfx, int statenum) {
+ _gfx[statenum].free();
if (!gfx || !gfx->getPixels())
return;
@@ -432,10 +435,27 @@ void PicButtonWidget::setGfx(const Graphics::Surface *gfx) {
return;
}
- _gfx.copyFrom(*gfx);
+ _gfx[statenum].copyFrom(*gfx);
+}
+
+void PicButtonWidget::setAGfx(const Graphics::TransparentSurface *gfx, int statenum, ThemeEngine::AutoScaleMode mode) {
+ _agfx[statenum].free();
+
+ if (!gfx || !gfx->getPixels())
+ return;
+
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("PicButtonWidget::setGfx got paletted surface passed");
+ return;
+ }
+
+ _agfx[statenum].copyFrom(*gfx);
+
+ _isAlpha = true;
+ _mode = mode;
}
-void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) {
+void PicButtonWidget::setGfx(int w, int h, int r, int g, int b, int statenum) {
if (w == -1)
w = _w;
if (h == -1)
@@ -443,26 +463,69 @@ void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) {
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));
+ _gfx[statenum].free();
+ _gfx[statenum].create(w, h, requiredFormat);
+ _gfx[statenum].fillRect(Common::Rect(0, 0, w, h), _gfx[statenum].format.RGBToColor(r, g, b));
}
void PicButtonWidget::drawWidget() {
- g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags());
+ if (_showButton)
+ g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags());
+
+ if (!_isAlpha) {
+ Graphics::Surface *gfx;
+
+ if (_state == ThemeEngine::kStateHighlight)
+ gfx = &_gfx[kPicButtonHighlight];
+ else if (_state == ThemeEngine::kStateDisabled)
+ gfx = &_gfx[kPicButtonStateDisabled];
+ else if (_state == ThemeEngine::kStatePressed)
+ gfx = &_gfx[kPicButtonStatePressed];
+ else
+ gfx = &_gfx[kPicButtonStateEnabled];
- if (_gfx.getPixels()) {
+ if (!gfx->getPixels())
+ gfx = &_gfx[kPicButtonStateEnabled];
+
+ if (gfx->getPixels()) {
// 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 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;
+
+ g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + gfx->w, y + gfx->h), getBossClipRect(), *gfx, _state, _alpha, _transparency);
}
+ } else {
+ Graphics::TransparentSurface *gfx;
+
+ if (_state == ThemeEngine::kStateHighlight)
+ gfx = &_agfx[kPicButtonHighlight];
+ else if (_state == ThemeEngine::kStateDisabled)
+ gfx = &_agfx[kPicButtonStateDisabled];
+ else if (_state == ThemeEngine::kStatePressed)
+ gfx = &_agfx[kPicButtonStatePressed];
+ else
+ gfx = &_agfx[kPicButtonStateEnabled];
- const int x = _x + (_w - _gfx.w) / 2;
- const int y = _y + (_h - _gfx.h) / 2;
+ if (!gfx->getPixels())
+ gfx = &_agfx[kPicButtonStateEnabled];
- g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency);
+ if (gfx->getPixels()) {
+ if (_mode == GUI::ThemeEngine::kAutoScaleNone) {
+ const int x = _x + (_w - gfx->w) / 2;
+ const int y = _y + (_h - gfx->h) / 2;
+
+ g_gui.theme()->drawASurface(Common::Rect(x, y, x + gfx->w, y + gfx->h), *gfx, _mode, _alpha);
+
+ } else {
+ g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), *gfx, _mode, _alpha);
+ }
+ }
}
}
@@ -652,13 +715,13 @@ int SliderWidget::posToValue(int pos) {
#pragma mark -
GraphicsWidget::GraphicsWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip)
- : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(256), _transparency(false) {
+ : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(255), _transparency(false) {
setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
_type = kGraphicsWidget;
}
GraphicsWidget::GraphicsWidget(GuiObject *boss, const Common::String &name, const char *tooltip)
- : Widget(boss, name, tooltip), _gfx(), _alpha(256), _transparency(false) {
+ : Widget(boss, name, tooltip), _gfx(), _alpha(255), _transparency(false) {
setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
_type = kGraphicsWidget;
}
@@ -686,6 +749,26 @@ void GraphicsWidget::setGfx(const Graphics::Surface *gfx) {
_gfx.copyFrom(*gfx);
}
+void GraphicsWidget::setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode) {
+ _agfx.free();
+
+ if (!gfx || !gfx->getPixels())
+ return;
+
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("GraphicsWidget::setGfx got paletted surface passed");
+ return;
+ }
+
+ if ((gfx->w > _w || gfx->h > _h) && mode == ThemeEngine::kAutoScaleNone) {
+ warning("GraphicsWidget has size %dx%d, but a surface with %dx%d is to be set", _w, _h, gfx->w, gfx->h);
+ return;
+ }
+
+ _agfx.copyFrom(*gfx);
+ _mode = mode;
+}
+
void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) {
if (w == -1)
w = _w;
@@ -712,6 +795,23 @@ void GraphicsWidget::drawWidget() {
const int y = _y + (_h - _gfx.h) / 2;
g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency);
+ } else if (_agfx.getPixels()) {
+ // Check whether the set up surface needs to be converted to the GUI
+ // color format.
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+ if (_agfx.format != requiredFormat) {
+ _agfx.convertToInPlace(requiredFormat);
+ }
+
+ if (_mode == GUI::ThemeEngine::kAutoScaleNone) {
+ const int x = _x + (_w - _agfx.w) / 2;
+ const int y = _y + (_h - _agfx.h) / 2;
+
+ g_gui.theme()->drawASurface(Common::Rect(x, y, x + _agfx.w, y + _agfx.h), _agfx, _mode, _alpha);
+
+ } else {
+ g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), _agfx, _mode, _alpha);
+ }
}
}
diff --git a/gui/widget.h b/gui/widget.h
index 0f4b300233..e9343f264c 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -80,6 +80,15 @@ enum {
kPressedButtonTime = 200
};
+enum {
+ kPicButtonStateEnabled = 0,
+ kPicButtonHighlight = 1,
+ kPicButtonStateDisabled = 2,
+ kPicButtonStatePressed = 3,
+
+ kPicButtonStateMax = 3
+};
+
/* Widget */
class Widget : public GuiObject {
friend class Dialog;
@@ -221,18 +230,24 @@ public:
PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip = 0, uint32 cmd = 0, uint8 hotkey = 0);
~PicButtonWidget();
- void setGfx(const Graphics::Surface *gfx);
- void setGfx(int w, int h, int r, int g, int b);
+ void setGfx(const Graphics::Surface *gfx, int statenum = kPicButtonStateEnabled);
+ void setAGfx(const Graphics::TransparentSurface *gfx, int statenum = kPicButtonStateEnabled, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone);
+ void setGfx(int w, int h, int r, int g, int b, int statenum = kPicButtonStateEnabled);
void useAlpha(int alpha) { _alpha = alpha; }
void useThemeTransparency(bool enable) { _transparency = enable; }
+ void setButtonDisplay(bool enable) {_showButton = enable; }
protected:
void drawWidget();
- Graphics::Surface _gfx;
+ Graphics::Surface _gfx[kPicButtonStateMax + 1];
+ Graphics::TransparentSurface _agfx[kPicButtonStateMax + 1];
int _alpha;
bool _transparency;
+ bool _showButton;
+ bool _isAlpha;
+ ThemeEngine::AutoScaleMode _mode;
};
/* CheckboxWidget */
@@ -351,6 +366,7 @@ public:
void setGfx(const Graphics::Surface *gfx);
void setGfx(int w, int h, int r, int g, int b);
+ void setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone);
void useAlpha(int alpha) { _alpha = alpha; }
void useThemeTransparency(bool enable) { _transparency = enable; }
@@ -359,8 +375,10 @@ protected:
void drawWidget();
Graphics::Surface _gfx;
+ Graphics::TransparentSurface _agfx;
int _alpha;
bool _transparency;
+ ThemeEngine::AutoScaleMode _mode;
};
/* ContainerWidget */
diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp
index 4f7e584c14..02defe9a56 100644
--- a/gui/widgets/editable.cpp
+++ b/gui/widgets/editable.cpp
@@ -185,6 +185,21 @@ bool EditableWidget::handleKeyDown(Common::KeyState state) {
forcecaret = true;
break;
+ case Common::KEYCODE_v:
+ if (g_system->hasFeature(OSystem::kFeatureClipboardSupport) && state.flags & Common::KBD_CTRL) {
+ if (g_system->hasTextInClipboard()) {
+ String text = g_system->getTextFromClipboard();
+ for (uint32 i = 0; i < text.size(); ++i) {
+ if (tryInsertChar(text[i], _caretPos))
+ ++_caretPos;
+ }
+ dirty = true;
+ }
+ } else {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ }
+ break;
+
#ifdef MACOSX
// Let ctrl-a / ctrl-e move the caret to the start / end of the line.
//
diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 1b38478c11..9a7792730d 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -28,13 +28,13 @@
namespace GUI {
-ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h)
- : Widget(boss, x, y, w, h) {
+ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd)
+ : Widget(boss, x, y, w, h), CommandSender(nullptr), _reflowCmd(reflowCmd) {
init();
}
-ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name)
- : Widget(boss, name) {
+ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd)
+ : Widget(boss, name), CommandSender(nullptr), _reflowCmd(reflowCmd) {
init();
}
@@ -52,14 +52,14 @@ void ScrollContainerWidget::init() {
void ScrollContainerWidget::recalc() {
int scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
_limitH = _h;
-
+
//calculate virtual height
const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom
int h = 0;
int min = spacing, max = 0;
Widget *ptr = _firstWidget;
while (ptr) {
- if (ptr != _verticalScroll) {
+ if (ptr != _verticalScroll && ptr->isVisible()) {
int y = ptr->getAbsY() - getChildY();
min = MIN(min, y - spacing);
max = MAX(max, y + ptr->getHeight() + spacing);
@@ -68,6 +68,8 @@ void ScrollContainerWidget::recalc() {
}
h = max - min;
+ if (h <= _limitH) _scrolledY = 0;
+
_verticalScroll->_numEntries = h;
_verticalScroll->_currentPos = _scrolledY;
_verticalScroll->_entriesPerPage = _limitH;
@@ -115,7 +117,10 @@ void ScrollContainerWidget::reflowLayout() {
ptr->reflowLayout();
ptr = ptr->next();
}
-
+
+ //hide and move widgets, if needed
+ sendCommand(_reflowCmd, 0);
+
//recalculate height
recalc();
@@ -124,7 +129,7 @@ void ScrollContainerWidget::reflowLayout() {
while (ptr) {
int y = ptr->getAbsY() - getChildY();
int h = ptr->getHeight();
- bool visible = true;
+ bool visible = ptr->isVisible();
if (y + h - _scrolledY < 0) visible = false;
if (y - _scrolledY > _limitH) visible = false;
ptr->setVisible(visible);
@@ -132,6 +137,7 @@ void ScrollContainerWidget::reflowLayout() {
}
_verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll
+ _verticalScroll->recalc();
}
void ScrollContainerWidget::drawWidget() {
diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h
index 692c7e3507..c2d47370ee 100644
--- a/gui/widgets/scrollcontainer.h
+++ b/gui/widgets/scrollcontainer.h
@@ -29,16 +29,17 @@
namespace GUI {
-class ScrollContainerWidget: public Widget {
+class ScrollContainerWidget: public Widget, public CommandSender {
ScrollBarWidget *_verticalScroll;
int16 _scrolledX, _scrolledY;
uint16 _limitH;
+ uint32 _reflowCmd;
void recalc();
public:
- ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h);
- ScrollContainerWidget(GuiObject *boss, const Common::String &name);
+ ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd = 0);
+ ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd = 0);
~ScrollContainerWidget();
void init();