diff options
Diffstat (limited to 'gui')
87 files changed, 7450 insertions, 1202 deletions
diff --git a/gui/EventRecorder.cpp b/gui/EventRecorder.cpp index d0371f5e78..3f91cfa259 100644 --- a/gui/EventRecorder.cpp +++ b/gui/EventRecorder.cpp @@ -330,7 +330,7 @@ bool EventRecorder::openRecordFile(const Common::String &fileName) { } bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) { - if (_playbackFile->getHeader().hashRecords.size() != 0) { + if (_playbackFile->getHeader().hashRecords.size() == 0) { warning("Engine doesn't contain description table"); return false; } @@ -676,4 +676,3 @@ void EventRecorder::deleteTemporarySave() { } // End of namespace GUI #endif // ENABLE_EVENTRECORDER - diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index 1b95a11e03..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; @@ -121,6 +127,19 @@ protected: const WidgetDrawData *_data; }; +class ThemeItemDrawDataClip: public ThemeItem{ +public: + ThemeItemDrawDataClip(ThemeEngine *engine, const WidgetDrawData *data, const Common::Rect &area, const Common::Rect &clip, uint32 dynData) : + ThemeItem(engine, area), _dynamicData(dynData), _data(data), _clip(clip) {} + + void drawSelf(bool draw, bool restore); + +protected: + uint32 _dynamicData; + const WidgetDrawData *_data; + const Common::Rect _clip; +}; + class ThemeItemTextData : public ThemeItem { public: ThemeItemTextData(ThemeEngine *engine, const TextDrawData *data, const TextColorData *color, const Common::Rect &area, const Common::Rect &textDrawableArea, @@ -155,7 +174,30 @@ 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: + const Graphics::Surface *_bitmap; + bool _alpha; + const Common::Rect _clip; +}; /********************************************************** * Data definitions for theme engine elements @@ -164,7 +206,7 @@ struct DrawDataInfo { DrawData id; ///< The actual ID of the DrawData item. const char *name; ///< The name of the DrawData item as it appears in the Theme Description files bool buffer; ///< Sets whether this item is buffered on the backbuffer or drawn directly to the screen. - DrawData parent; ///< Parent DrawData item, for items that overlay. E.g. kButtonIdle -> kButtonHover + DrawData parent; ///< Parent DrawData item, for items that overlay. E.g. kDDButtonIdle -> kDDButtonHover }; /** @@ -242,16 +284,40 @@ void ThemeItemDrawData::drawSelf(bool draw, bool restore) { _engine->addDirtyRect(extendedRect); } +void ThemeItemDrawDataClip::drawSelf(bool draw, bool restore) { + + Common::Rect extendedRect = _area; + extendedRect.grow(_engine->kDirtyRectangleThreshold + _data->_backgroundOffset); + + if (restore) + _engine->restoreBackground(extendedRect); + + if (draw) { + Common::List<Graphics::DrawStep>::const_iterator step; + for (step = _data->_steps.begin(); step != _data->_steps.end(); ++step) { + _engine->renderer()->drawStepClip(_area, _clip, *step, _dynamicData); + } + } + + extendedRect.clip(_clip); + + _engine->addDirtyRect(extendedRect); +} + void ThemeItemTextData::drawSelf(bool draw, bool restore) { + Common::Rect dirty = _textDrawableArea; + if (dirty.isEmpty()) dirty = _area; + else dirty.clip(_area); + if (_restoreBg || restore) - _engine->restoreBackground(_area); + _engine->restoreBackground(dirty); if (draw) { _engine->renderer()->setFgColor(_color->r, _color->g, _color->b); _engine->renderer()->drawString(_data->_fontPtr, _text, _area, _alignH, _alignV, _deltax, _ellipsis, _textDrawableArea); } - _engine->addDirtyRect(_area); + _engine->addDirtyRect(dirty); } void ThemeItemBitmap::drawSelf(bool draw, bool restore) { @@ -260,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); } @@ -268,7 +334,31 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) { _engine->addDirtyRect(_area); } +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()->blitKeyBitmapClip(_bitmap, _area, _clip); + else + _engine->renderer()->blitSubSurfaceClip(_bitmap, _area, _clip); + } + Common::Rect dirtyRect = _area; + dirtyRect.clip(_clip); + _engine->addDirtyRect(dirtyRect); +} /********************************************************** * ThemeEngine class @@ -311,6 +401,12 @@ ThemeEngine::ThemeEngine(Common::String id, GraphicsMode mode) : _themeArchive = 0; _initOk = false; + _cursorHotspotX = _cursorHotspotY = 0; + _cursorWidth = _cursorHeight = 0; + _cursorPalSize = 0; + + _needPaletteUpdates = false; + // We prefer files in archive bundles over the common search paths. _themeFiles.add("default", &SearchMan, 0, false); } @@ -333,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; @@ -457,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(); @@ -558,7 +672,7 @@ void ThemeEngine::restoreBackground(Common::Rect r) { void ThemeEngine::addDrawStep(const Common::String &drawDataId, const Graphics::DrawStep &step) { DrawData id = parseDrawDataId(drawDataId); - assert(_widgets[id] != 0); + assert(id != kDDNone && _widgets[id] != 0); _widgets[id]->_steps.push_back(step); } @@ -638,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); +#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); + if (srcSurface && srcSurface->format.bytesPerPixel != 1) + surf = srcSurface->convertTo(_overlayFormat); + } // Store the surface into our hashmap (attention, may store NULL entries!) _bitmaps[filename] = surf; @@ -663,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); @@ -856,10 +1039,34 @@ void ThemeEngine::queueDD(DrawData type, const Common::Rect &r, uint32 dynamic, } } +void ThemeEngine::queueDDClip(DrawData type, const Common::Rect &r, const Common::Rect &clippingRect, uint32 dynamic, bool restore) { + if (_widgets[type] == 0) + return; + + Common::Rect area = r; + area.clip(_screen.w, _screen.h); + + ThemeItemDrawDataClip *q = new ThemeItemDrawDataClip(this, _widgets[type], area, clippingRect, dynamic); + + if (_buffering) { + if (_widgets[type]->_buffer) { + _bufferQueue.push_back(q); + } else { + if (kDrawDataDefaults[type].parent != kDDNone && kDrawDataDefaults[type].parent != type) + queueDDClip(kDrawDataDefaults[type].parent, r, clippingRect); + + _screenQueue.push_back(q); + } + } else { + q->drawSelf(!_widgets[type]->_buffer, restore || _widgets[type]->_buffer); + delete q; + } +} + void ThemeEngine::queueDDText(TextData type, TextColor color, const Common::Rect &r, const Common::String &text, bool restoreBg, bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV, int deltax, const Common::Rect &drawableTextArea) { - if (_texts[type] == 0) + if (type == kTextDataNone || _texts[type] == 0) return; Common::Rect area = r; @@ -875,6 +1082,31 @@ void ThemeEngine::queueDDText(TextData type, TextColor color, const Common::Rect } } +void ThemeEngine::queueDDTextClip(TextData type, TextColor color, const Common::Rect &r, const Common::Rect &clippingArea, const Common::String &text, bool restoreBg, + bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV, int deltax, const Common::Rect &drawableTextArea) { + + if (_texts[type] == 0) + return; + + Common::Rect area = r; + area.clip(_screen.w, _screen.h); + Common::Rect textArea = drawableTextArea; + if (textArea.isEmpty()) textArea = 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); + + if (_buffering) { + _screenQueue.push_back(q); + } else { + q->drawSelf(true, false); + delete q; + } +} + void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha) { Common::Rect area = r; @@ -890,7 +1122,35 @@ 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; + area.clip(_screen.w, _screen.h); + + ThemeItemBitmapClip *q = new ThemeItemBitmapClip(this, area, clip, bitmap, alpha); + + if (_buffering) { + _screenQueue.push_back(q); + } else { + q->drawSelf(true, false); + delete q; + } +} /********************************************************** * Widget drawing functions @@ -914,6 +1174,25 @@ void ThemeEngine::drawButton(const Common::Rect &r, const Common::String &str, W queueDDText(getTextData(dd), getTextColor(dd), r, str, false, true, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV); } +void ThemeEngine::drawButtonClip(const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &str, WidgetStateInfo state, uint16 hints) { + if (!ready()) + return; + + DrawData dd = kDDButtonIdle; + + if (state == kStateEnabled) + dd = kDDButtonIdle; + else if (state == kStateHighlight) + dd = kDDButtonHover; + else if (state == kStateDisabled) + dd = kDDButtonDisabled; + else if (state == kStatePressed) + dd = kDDButtonPressed; + + queueDDClip(dd, r, clippingRect, 0, hints & WIDGET_CLEARBG); + queueDDTextClip(getTextData(dd), getTextColor(dd), r, clippingRect, str, false, true, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV); +} + void ThemeEngine::drawLineSeparator(const Common::Rect &r, WidgetStateInfo state) { if (!ready()) return; @@ -921,6 +1200,13 @@ void ThemeEngine::drawLineSeparator(const Common::Rect &r, WidgetStateInfo state queueDD(kDDSeparator, r); } +void ThemeEngine::drawLineSeparatorClip(const Common::Rect &r, const Common::Rect &clippingRect, WidgetStateInfo state) { + if (!ready()) + return; + + queueDDClip(kDDSeparator, r, clippingRect); +} + void ThemeEngine::drawCheckbox(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) { if (!ready()) return; @@ -947,6 +1233,32 @@ void ThemeEngine::drawCheckbox(const Common::Rect &r, const Common::String &str, queueDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDCheckboxDefault]->_textAlignH, _widgets[dd]->_textAlignV); } +void ThemeEngine::drawCheckboxClip(const Common::Rect &r, const Common::Rect &clip, const Common::String &str, bool checked, WidgetStateInfo state) { + if (!ready()) + return; + + Common::Rect r2 = r; + DrawData dd = kDDCheckboxDefault; + + if (checked) + dd = kDDCheckboxSelected; + + if (state == kStateDisabled) + dd = kDDCheckboxDisabled; + + const int checkBoxSize = MIN((int)r.height(), getFontHeight()); + + r2.bottom = r2.top + checkBoxSize; + r2.right = r2.left + checkBoxSize; + + queueDDClip(dd, r2, clip); + + r2.left = r2.right + checkBoxSize; + r2.right = r.right; + + queueDDTextClip(getTextData(dd), getTextColor(dd), r2, clip, str, true, false, _widgets[kDDCheckboxDefault]->_textAlignH, _widgets[dd]->_textAlignV); +} + void ThemeEngine::drawRadiobutton(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) { if (!ready()) return; @@ -973,6 +1285,32 @@ void ThemeEngine::drawRadiobutton(const Common::Rect &r, const Common::String &s queueDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDRadiobuttonDefault]->_textAlignH, _widgets[dd]->_textAlignV); } +void ThemeEngine::drawRadiobuttonClip(const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &str, bool checked, WidgetStateInfo state) { + if (!ready()) + return; + + Common::Rect r2 = r; + DrawData dd = kDDRadiobuttonDefault; + + if (checked) + dd = kDDRadiobuttonSelected; + + if (state == kStateDisabled) + dd = kDDRadiobuttonDisabled; + + const int checkBoxSize = MIN((int)r.height(), getFontHeight()); + + r2.bottom = r2.top + checkBoxSize; + r2.right = r2.left + checkBoxSize; + + queueDDClip(dd, r2, clippingRect); + + r2.left = r2.right + checkBoxSize; + r2.right = r.right; + + queueDDTextClip(getTextData(dd), getTextColor(dd), r2, clippingRect, str, true, false, _widgets[kDDRadiobuttonDefault]->_textAlignH, _widgets[dd]->_textAlignV); +} + void ThemeEngine::drawSlider(const Common::Rect &r, int width, WidgetStateInfo state) { if (!ready()) return; @@ -993,6 +1331,26 @@ void ThemeEngine::drawSlider(const Common::Rect &r, int width, WidgetStateInfo s queueDD(dd, r2); } +void ThemeEngine::drawSliderClip(const Common::Rect &r, const Common::Rect &clip, int width, WidgetStateInfo state) { + if (!ready()) + return; + + DrawData dd = kDDSliderFull; + + if (state == kStateHighlight) + dd = kDDSliderHover; + else if (state == kStateDisabled) + dd = kDDSliderDisabled; + + Common::Rect r2 = r; + r2.setWidth(MIN((int16)width, r.width())); + // r2.top++; r2.bottom--; r2.left++; r2.right--; + + drawWidgetBackgroundClip(r, clip, 0, kWidgetBackgroundSlider, kStateEnabled); + + queueDDClip(dd, r2, clip); +} + void ThemeEngine::drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState scrollState, WidgetStateInfo state) { if (!ready()) return; @@ -1014,11 +1372,34 @@ void ThemeEngine::drawScrollbar(const Common::Rect &r, int sliderY, int sliderHe r2.top += sliderY; r2.bottom = r2.top + sliderHeight; - r2.top += r.width() / 5; - r2.bottom -= r.width() / 5; + //r2.top += r.width() / 5; + //r2.bottom -= r.width() / 5; queueDD(scrollState == kScrollbarStateSlider ? kDDScrollbarHandleHover : kDDScrollbarHandleIdle, r2); } +void ThemeEngine::drawScrollbarClip(const Common::Rect &r, const Common::Rect &clippingRect, int sliderY, int sliderHeight, ScrollbarState scrollState, WidgetStateInfo state) { + if (!ready()) + return; + + queueDDClip(kDDScrollbarBase, r, clippingRect); + + Common::Rect r2 = r; + const int buttonExtra = (r.width() * 120) / 100; + + r2.bottom = r2.top + buttonExtra; + queueDDClip(scrollState == kScrollbarStateUp ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2, clippingRect, Graphics::VectorRenderer::kTriangleUp); + + r2.translate(0, r.height() - r2.height()); + queueDDClip(scrollState == kScrollbarStateDown ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2, clippingRect, Graphics::VectorRenderer::kTriangleDown); + + r2 = r; + r2.left += 1; + r2.right -= 1; + r2.top += sliderY; + r2.bottom = r2.top + sliderHeight; + queueDDClip(scrollState == kScrollbarStateSlider ? kDDScrollbarHandleHover : kDDScrollbarHandleIdle, r2, clippingRect); +} + void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground bgtype, WidgetStateInfo state) { if (!ready()) return; @@ -1043,6 +1424,38 @@ void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground b case kDialogBackgroundDefault: queueDD(kDDDefaultBackground, r); break; + case kDialogBackgroundNone: + break; + } +} + +void ThemeEngine::drawDialogBackgroundClip(const Common::Rect &r, const Common::Rect &clip, DialogBackground bgtype, WidgetStateInfo state) { + if (!ready()) + return; + + switch (bgtype) { + case kDialogBackgroundMain: + queueDDClip(kDDMainDialogBackground, r, clip); + break; + + case kDialogBackgroundSpecial: + queueDDClip(kDDSpecialColorBackground, r, clip); + break; + + case kDialogBackgroundPlain: + queueDDClip(kDDPlainColorBackground, r, clip); + break; + + case kDialogBackgroundTooltip: + queueDDClip(kDDTooltipBackground, r, clip); + break; + + case kDialogBackgroundDefault: + queueDDClip(kDDDefaultBackground, r, clip); + break; + case kDialogBackgroundNone: + // no op + break; } } @@ -1057,6 +1470,17 @@ void ThemeEngine::drawCaret(const Common::Rect &r, bool erase, WidgetStateInfo s queueDD(kDDCaret, r); } +void ThemeEngine::drawCaretClip(const Common::Rect &r, const Common::Rect &clip, bool erase, WidgetStateInfo state) { + if (!ready()) + return; + + if (erase) { + restoreBackground(r); + addDirtyRect(r); + } else + queueDDClip(kDDCaret, r, clip); +} + void ThemeEngine::drawPopUpWidget(const Common::Rect &r, const Common::String &sel, int deltax, WidgetStateInfo state, Graphics::TextAlign align) { if (!ready()) return; @@ -1078,6 +1502,27 @@ void ThemeEngine::drawPopUpWidget(const Common::Rect &r, const Common::String &s } } +void ThemeEngine::drawPopUpWidgetClip(const Common::Rect &r, const Common::Rect &clip, const Common::String &sel, int deltax, WidgetStateInfo state, Graphics::TextAlign align) { + if (!ready()) + return; + + DrawData dd = kDDPopUpIdle; + + if (state == kStateEnabled) + dd = kDDPopUpIdle; + else if (state == kStateHighlight) + dd = kDDPopUpHover; + else if (state == kStateDisabled) + dd = kDDPopUpDisabled; + + queueDDClip(dd, r, clip); + + if (!sel.empty()) { + Common::Rect text(r.left + 3, r.top + 1, r.right - 10, r.bottom); + queueDDTextClip(getTextData(dd), getTextColor(dd), text, clip, sel, true, false, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV, deltax); + } +} + void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &surface, WidgetStateInfo state, int alpha, bool themeTrans) { if (!ready()) return; @@ -1085,6 +1530,20 @@ 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; + + queueBitmapClip(&surface, r, clip, themeTrans); +} + void ThemeEngine::drawWidgetBackground(const Common::Rect &r, uint16 hints, WidgetBackground background, WidgetStateInfo state) { if (!ready()) return; @@ -1108,6 +1567,29 @@ void ThemeEngine::drawWidgetBackground(const Common::Rect &r, uint16 hints, Widg } } +void ThemeEngine::drawWidgetBackgroundClip(const Common::Rect &r, const Common::Rect &clip, uint16 hints, WidgetBackground background, WidgetStateInfo state) { + if (!ready()) + return; + + switch (background) { + case kWidgetBackgroundBorderSmall: + queueDDClip(kDDWidgetBackgroundSmall, r, clip); + break; + + case kWidgetBackgroundEditText: + queueDDClip(kDDWidgetBackgroundEditText, r, clip); + break; + + case kWidgetBackgroundSlider: + queueDDClip(kDDWidgetBackgroundSlider, r, clip); + break; + + default: + queueDDClip(kDDWidgetBackgroundDefault, r, clip); + break; + } +} + void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, int tabWidth, const Common::Array<Common::String> &tabs, int active, uint16 hints, int titleVPad, WidgetStateInfo state) { if (!ready()) return; @@ -1136,6 +1618,34 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, int tabWidth, co } } +void ThemeEngine::drawTabClip(const Common::Rect &r, const Common::Rect &clip, int tabHeight, int tabWidth, const Common::Array<Common::String> &tabs, int active, uint16 hints, int titleVPad, WidgetStateInfo state) { + if (!ready()) + return; + + queueDDClip(kDDTabBackground, Common::Rect(r.left, r.top, r.right, r.top + tabHeight), clip); + + for (int i = 0; i < (int)tabs.size(); ++i) { + if (i == active) + continue; + + if (r.left + i * tabWidth > r.right || r.left + (i + 1) * tabWidth > r.right) + continue; + + Common::Rect tabRect(r.left + i * tabWidth, r.top, r.left + (i + 1) * tabWidth, r.top + tabHeight); + queueDDClip(kDDTabInactive, tabRect, clip); + queueDDTextClip(getTextData(kDDTabInactive), getTextColor(kDDTabInactive), tabRect, clip, tabs[i], false, false, _widgets[kDDTabInactive]->_textAlignH, _widgets[kDDTabInactive]->_textAlignV); + } + + if (active >= 0 && + (r.left + active * tabWidth < r.right) && (r.left + (active + 1) * tabWidth < r.right)) { + Common::Rect tabRect(r.left + active * tabWidth, r.top, r.left + (active + 1) * tabWidth, r.top + tabHeight); + const uint16 tabLeft = active * tabWidth; + const uint16 tabRight = MAX(r.right - tabRect.right, 0); + queueDDClip(kDDTabActive, tabRect, clip, (tabLeft << 16) | (tabRight & 0xFFFF)); + queueDDTextClip(getTextData(kDDTabActive), getTextColor(kDDTabActive), tabRect, clip, tabs[active], false, false, _widgets[kDDTabActive]->_textAlignH, _widgets[kDDTabActive]->_textAlignV); + } +} + void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, Graphics::TextAlign align, TextInversionState inverted, int deltax, bool useEllipsis, FontStyle font, FontColor color, bool restore, const Common::Rect &drawableTextArea) { if (!ready()) return; @@ -1209,6 +1719,79 @@ void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, Wid queueDDText(textId, colorId, r, str, restore, useEllipsis, align, kTextAlignVCenter, deltax, drawableTextArea); } +void ThemeEngine::drawTextClip(const Common::Rect &r, const Common::Rect &clippingArea, const Common::String &str, WidgetStateInfo state, Graphics::TextAlign align, TextInversionState inverted, int deltax, bool useEllipsis, FontStyle font, FontColor color, bool restore, const Common::Rect &drawableTextArea) { + if (!ready()) + return; + + TextColor colorId = kTextColorMAX; + + switch (color) { + case kFontColorNormal: + if (inverted) { + colorId = kTextColorNormalInverted; + } else { + switch (state) { + case kStateDisabled: + colorId = kTextColorNormalDisabled; + break; + + case kStateHighlight: + colorId = kTextColorNormalHover; + break; + + case kStateEnabled: + case kStatePressed: + colorId = kTextColorNormal; + break; + } + } + break; + + case kFontColorAlternate: + if (inverted) { + colorId = kTextColorAlternativeInverted; + } else { + switch (state) { + case kStateDisabled: + colorId = kTextColorAlternativeDisabled; + break; + + case kStateHighlight: + colorId = kTextColorAlternativeHover; + break; + + case kStateEnabled: + case kStatePressed: + colorId = kTextColorAlternative; + break; + } + } + break; + + default: + return; + } + + TextData textId = fontStyleToData(font); + + switch (inverted) { + case kTextInversion: + queueDDClip(kDDTextSelectionBackground, r, clippingArea); + restore = false; + break; + + case kTextInversionFocus: + queueDDClip(kDDTextSelectionFocusBackground, r, clippingArea); + restore = false; + break; + + default: + break; + } + + queueDDTextClip(textId, colorId, r, clippingArea, str, restore, useEllipsis, align, kTextAlignVCenter, deltax, drawableTextArea); +} + void ThemeEngine::drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, WidgetStateInfo state, FontColor color) { if (!ready()) return; @@ -1223,6 +1806,21 @@ void ThemeEngine::drawChar(const Common::Rect &r, byte ch, const Graphics::Font addDirtyRect(charArea); } +void ThemeEngine::drawCharClip(const Common::Rect &r, const Common::Rect &clip, byte ch, const Graphics::Font *font, WidgetStateInfo state, FontColor color) { + if (!ready()) + return; + + Common::Rect charArea = r; + charArea.clip(_screen.w, _screen.h); + if (!clip.isEmpty()) charArea.clip(clip); + + uint32 rgbColor = _overlayFormat.RGBToColor(_textColors[color]->r, _textColors[color]->g, _textColors[color]->b); + + restoreBackground(charArea); + font->drawChar(&_screen, ch, charArea.left, charArea.top, rgbColor); + addDirtyRect(charArea); +} + void ThemeEngine::debugWidgetPosition(const char *name, const Common::Rect &r) { _font->drawString(&_screen, name, r.left, r.top, r.width(), 0xFFFF, Graphics::kTextAlignRight, 0, true); _screen.hLine(r.left, r.top, r.right, 0xFFFF); @@ -1259,8 +1857,15 @@ void ThemeEngine::updateScreen(bool render) { _screenQueue.clear(); } - if (render) + if (render) { +#ifdef LAYOUT_DEBUG_DIALOG + _vectorRenderer->fillSurface(); + _themeEval->debugDraw(&_screen, _font); + _vectorRenderer->copyWholeFrame(_system); +#else renderDirtyScreen(); +#endif + } } void ThemeEngine::addDirtyRect(Common::Rect r) { diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index a5ef49c78b..8a6db17dfd 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -32,11 +32,12 @@ #include "common/rect.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" #include "graphics/font.h" #include "graphics/pixelformat.h" -#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.21" +#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.23" class OSystem; @@ -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. @@ -340,42 +355,69 @@ public: void drawWidgetBackground(const Common::Rect &r, uint16 hints, WidgetBackground background = kWidgetBackgroundPlain, WidgetStateInfo state = kStateEnabled); + void drawWidgetBackgroundClip(const Common::Rect &r, const Common::Rect &clippingArea, uint16 hints, + WidgetBackground background = kWidgetBackgroundPlain, WidgetStateInfo state = kStateEnabled); void drawButton(const Common::Rect &r, const Common::String &str, WidgetStateInfo state = kStateEnabled, uint16 hints = 0); + void drawButtonClip(const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &str, + WidgetStateInfo state = kStateEnabled, uint16 hints = 0); void drawSurface(const Common::Rect &r, const Graphics::Surface &surface, - WidgetStateInfo state = kStateEnabled, int alpha = 256, bool themeTrans = false); + WidgetStateInfo state = kStateEnabled, int alpha = 255, bool themeTrans = false); + 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, + WidgetStateInfo state = kStateEnabled); void drawCheckbox(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state = kStateEnabled); + void drawCheckboxClip(const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &str, + bool checked, WidgetStateInfo state = kStateEnabled); void drawRadiobutton(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state = kStateEnabled); + void drawRadiobuttonClip(const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &str, + bool checked, WidgetStateInfo state = kStateEnabled); void drawTab(const Common::Rect &r, int tabHeight, int tabWidth, const Common::Array<Common::String> &tabs, int active, uint16 hints, int titleVPad, WidgetStateInfo state = kStateEnabled); + void drawTabClip(const Common::Rect &r, const Common::Rect &clippingRect, int tabHeight, int tabWidth, + const Common::Array<Common::String> &tabs, int active, uint16 hints, + int titleVPad, WidgetStateInfo state = kStateEnabled); void drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState, WidgetStateInfo state = kStateEnabled); + void drawScrollbarClip(const Common::Rect &r, const Common::Rect &clippingRect, int sliderY, int sliderHeight, + ScrollbarState scrollState, WidgetStateInfo state = kStateEnabled); void drawPopUpWidget(const Common::Rect &r, const Common::String &sel, int deltax, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignLeft); + void drawPopUpWidgetClip(const Common::Rect &r, const Common::Rect &clippingArea, const Common::String &sel, + int deltax, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignLeft); void drawCaret(const Common::Rect &r, bool erase, WidgetStateInfo state = kStateEnabled); + void drawCaretClip(const Common::Rect &r, const Common::Rect &clip, bool erase, + WidgetStateInfo state = kStateEnabled); void drawLineSeparator(const Common::Rect &r, WidgetStateInfo state = kStateEnabled); + void drawLineSeparatorClip(const Common::Rect &r, const Common::Rect &clippingArea, WidgetStateInfo state = kStateEnabled); void drawDialogBackground(const Common::Rect &r, DialogBackground type, WidgetStateInfo state = kStateEnabled); + void drawDialogBackgroundClip(const Common::Rect &r, const Common::Rect &clip, DialogBackground type, WidgetStateInfo state = kStateEnabled); void drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignCenter, TextInversionState inverted = kTextInversionNone, int deltax = 0, bool useEllipsis = true, FontStyle font = kFontStyleBold, FontColor color = kFontColorNormal, bool restore = true, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); + void drawTextClip(const Common::Rect &r, const Common::Rect &clippingArea, const Common::String &str, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignCenter, TextInversionState inverted = kTextInversionNone, int deltax = 0, bool useEllipsis = true, FontStyle font = kFontStyleBold, FontColor color = kFontColorNormal, bool restore = true, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); void drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, WidgetStateInfo state = kStateEnabled, FontColor color = kFontColorNormal); + void drawCharClip(const Common::Rect &r, const Common::Rect &clippingArea, byte ch, const Graphics::Font *font, WidgetStateInfo state = kStateEnabled, FontColor color = kFontColorNormal); //@} @@ -458,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 ??? */ @@ -501,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. @@ -584,9 +642,14 @@ protected: * This function is called from all the Widget Drawing methods. */ void queueDD(DrawData type, const Common::Rect &r, uint32 dynamic = 0, bool restore = false); + void queueDDClip(DrawData type, const Common::Rect &r, const Common::Rect &clippingRect, uint32 dynamic = 0, bool restore = false); void queueDDText(TextData type, TextColor color, const Common::Rect &r, const Common::String &text, bool restoreBg, bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); + void queueDDTextClip(TextData type, TextColor color, const Common::Rect &r, const Common::Rect &clippingRect, const Common::String &text, bool restoreBg, + 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. @@ -627,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. */ @@ -659,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/ThemeEval.cpp b/gui/ThemeEval.cpp index 9d57d2408b..5255587089 100644 --- a/gui/ThemeEval.cpp +++ b/gui/ThemeEval.cpp @@ -91,10 +91,18 @@ void ThemeEval::addWidget(const Common::String &name, int w, int h, const Common typeAlign = (Graphics::TextAlign)getVar("Globals." + type + ".Align", Graphics::kTextAlignInvalid); } - ThemeLayoutWidget *widget = new ThemeLayoutWidget(_curLayout.top(), name, - typeW == -1 ? w : typeW, - typeH == -1 ? h : typeH, - typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign); + ThemeLayoutWidget *widget; + if (type == "TabWidget") + widget = new ThemeLayoutTabWidget(_curLayout.top(), name, + typeW == -1 ? w : typeW, + typeH == -1 ? h : typeH, + typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign, + getVar("Globals.TabWidget.Tab.Height", 0)); + else + widget = new ThemeLayoutWidget(_curLayout.top(), name, + typeW == -1 ? w : typeW, + typeH == -1 ? h : typeH, + typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign); _curLayout.top()->addChild(widget); setVar(_curDialog + "." + name + ".Enabled", enabled ? 1 : 0); diff --git a/gui/ThemeLayout.cpp b/gui/ThemeLayout.cpp index 6a6fd9e343..71e4b2c9fd 100644 --- a/gui/ThemeLayout.cpp +++ b/gui/ThemeLayout.cpp @@ -123,7 +123,7 @@ int16 ThemeLayoutStacked::getParentHeight() { #ifdef LAYOUT_DEBUG_DIALOG void ThemeLayout::debugDraw(Graphics::Surface *screen, const Graphics::Font *font) { - uint16 color = 0xFFFF; + uint32 color = 0xFFFFFFFF; font->drawString(screen, getName(), _x, _y, _w, color, Graphics::kTextAlignRight, 0, true); screen->hLine(_x, _y, _x + _w, color); screen->hLine(_x, _y + _h, _x + _w , color); diff --git a/gui/ThemeLayout.h b/gui/ThemeLayout.h index ba28fae1ac..e738002aa6 100644 --- a/gui/ThemeLayout.h +++ b/gui/ThemeLayout.h @@ -29,7 +29,7 @@ #ifdef LAYOUT_DEBUG_DIALOG namespace Graphics { -class Surface; +struct Surface; } #endif @@ -45,7 +45,8 @@ public: kLayoutMain, kLayoutVertical, kLayoutHorizontal, - kLayoutWidget + kLayoutWidget, + kLayoutTabWidget }; ThemeLayout(ThemeLayout *p) : @@ -223,6 +224,41 @@ protected: Common::String _name; }; +class ThemeLayoutTabWidget : public ThemeLayoutWidget { + int _tabHeight; + +public: + ThemeLayoutTabWidget(ThemeLayout *p, const Common::String &name, int16 w, int16 h, Graphics::TextAlign align, int tabHeight): + ThemeLayoutWidget(p, name, w, h, align) { + _tabHeight = tabHeight; + } + + void reflowLayout() { + for (uint i = 0; i < _children.size(); ++i) { + _children[i]->resetLayout(); + _children[i]->reflowLayout(); + } + } + + virtual bool getWidgetData(const Common::String &name, int16 &x, int16 &y, uint16 &w, uint16 &h) { + if (ThemeLayoutWidget::getWidgetData(name, x, y, w, h)) { + h -= _tabHeight; + return true; + } + + return false; + } + +protected: + LayoutType getLayoutType() { return kLayoutTabWidget; } + + ThemeLayout *makeClone(ThemeLayout *newParent) { + ThemeLayoutTabWidget *n = new ThemeLayoutTabWidget(*this); + n->_parent = newParent; + return n; + } +}; + class ThemeLayoutSpacing : public ThemeLayout { public: ThemeLayoutSpacing(ThemeLayout *p, int size) : ThemeLayout(p) { 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/Tooltip.cpp b/gui/Tooltip.cpp index ba313ee34f..09ad7ce5ca 100644 --- a/gui/Tooltip.cpp +++ b/gui/Tooltip.cpp @@ -32,7 +32,7 @@ namespace GUI { Tooltip::Tooltip() : - Dialog(-1, -1, -1, -1), _maxWidth(-1) { + Dialog(-1, -1, -1, -1), _maxWidth(-1), _parent(NULL), _xdelta(0), _ydelta(0) { _backgroundType = GUI::ThemeEngine::kDialogBackgroundTooltip; } diff --git a/gui/Tooltip.h b/gui/Tooltip.h index 58b6d8a429..60688412e6 100644 --- a/gui/Tooltip.h +++ b/gui/Tooltip.h @@ -41,6 +41,8 @@ public: void setup(Dialog *parent, Widget *widget, int x, int y); void drawDialog(); + + virtual void receivedFocus(int x = -1, int y = -1) {} protected: virtual void handleMouseDown(int x, int y, int button, int clickCount) { close(); @@ -64,7 +66,6 @@ protected: } virtual void handleMouseMoved(int x, int y, int button) { close(); - _parent->handleMouseMoved(x + (getAbsX() - _parent->getAbsX()), y + (getAbsY() - _parent->getAbsY()), button); } int _maxWidth; 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/browser_osx.mm b/gui/browser_osx.mm index 18cbd134f3..cfb70a3f65 100644 --- a/gui/browser_osx.mm +++ b/gui/browser_osx.mm @@ -52,7 +52,7 @@ - (id) init { self = [super init]; _panel = 0; - + return self; } diff --git a/gui/credits.h b/gui/credits.h index cda523bb79..b5d0b5da34 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -12,11 +12,6 @@ static const char *credits[] = { "C0""Eugene Sandulenko", "C2""Project Leader", "", -"C1""Core Team", -"C0""Willem Jan Palenstijn", -"C0""Eugene Sandulenko", -"C0""Johannes Schickel", -"", "C1""Retired Project Leaders", "C0""James Brown", "C0""Vincent Hamm", @@ -58,6 +53,7 @@ static const char *credits[] = { "C1""AGI", "C0""Stuart George", "C0""Matthew Hoops", +"C2""(retired)", "C0""Filippos Karapetis", "C0""Martin Kiewitz", "C0""Pawel Kolodziejski", @@ -127,6 +123,7 @@ static const char *credits[] = { "C1""Drascula", "C0""Filippos Karapetis", "C0""Pawel Kolodziejski", +"C0""Thierry Crozat", "", "C1""DreamWeb", "A0""Torbjorn Andersson", @@ -137,6 +134,11 @@ static const char *credits[] = { "C2""(retired)", "C0""Willem Jan Palenstijn", "", +"C1""Gnap", +"A0""Arnaud Boutonne", +"C0""Arnaud Boutonn\351", +"C0""Benjamin Haisch", +"", "C1""Gob", "A0""Torbjorn Andersson", "C0""Torbj\366rn Andersson", @@ -170,9 +172,11 @@ static const char *credits[] = { "C0""Gregory Montoir", "C2""(retired)", "C0""Johannes Schickel", +"C2""(retired)", "", "C1""Lastexpress", "C0""Matthew Hoops", +"C2""(retired)", "C0""Jordi Vilalta Prat", "C0""Julien Templier", "", @@ -192,6 +196,7 @@ static const char *credits[] = { "C1""Mohawk", "C0""Bastien Bouclet", "C0""Matthew Hoops", +"C2""(retired)", "C0""Filippos Karapetis", "C0""Alyssa Milburn", "C0""Eugene Sandulenko", @@ -211,6 +216,7 @@ static const char *credits[] = { "", "C1""Pegasus", "C0""Matthew Hoops", +"C2""(retired)", "", "C1""Queen", "C0""David Eriksson", @@ -343,10 +349,14 @@ static const char *credits[] = { "C1""Android", "C0""Andre Heider", "C0""Angus Lees", +"C0""Lubomyr Lisen", "", "C1""Dreamcast", "C0""Marcus Comstedt", "", +"C1""GCW0", +"C0""Eugene Sandulenko", +"", "C1""GPH Devices (GP2X, GP2XWiz & Caanoo)", "C0""John Willis", "", @@ -363,6 +373,9 @@ static const char *credits[] = { "C2""(retired)", "C0""Tarek Soliman", "", +"C1""Nintendo 3DS", +"C0""Thomas Edvalson", +"", "C1""Nintendo 64", "C0""Fabio Battaglia", "", @@ -421,11 +434,13 @@ static const char *credits[] = { "C2""Backend & Engine APIs, file API, sound mixer, audiostreams, data structures, etc. (retired)", "C0""Eugene Sandulenko", "C0""Johannes Schickel", +"C2""(retired)", "", "C1""GUI", "C0""Vicent Marti", "C0""Eugene Sandulenko", "C0""Johannes Schickel", +"C2""(retired)", "", "C1""Miscellaneous", "C0""David Corrales-Lopez", @@ -474,7 +489,7 @@ static const char *credits[] = { "C0""Joachim Eberhard", "C2""Numerous contributions to documentation (retired)", "C0""Matthew Hoops", -"C2""Wiki editor", +"C2""Numerous contributions to documentation (retired)", "", "C1""Retired Team Members", "C0""Chris Apers", @@ -532,6 +547,7 @@ static const char *credits[] = { "C0""Max Horn", "C2""(retired)", "C0""Oystein Eftevaag", +"C0""Thierry Crozat", "", "C1""Mandriva", "C0""Dominik Scherer", @@ -562,6 +578,7 @@ static const char *credits[] = { "C0""Chris Gray", "C2""(retired)", "C0""Johannes Schickel", +"C2""(retired)", "", "", "C1""GUI Translations", @@ -827,7 +844,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", @@ -845,18 +862,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 c9b435963d..7595efcfbc 100644 --- a/gui/debugger.cpp +++ b/gui/debugger.cpp @@ -42,6 +42,7 @@ #elif defined(USE_READLINE) #include <readline/readline.h> #include <readline/history.h> + #include "common/events.h" #endif @@ -191,6 +192,15 @@ char *readline_completionFunction(const char *text, int state) { return g_readline_debugger->readlineComplete(text, state); } +void readline_eventFunction() { + Common::EventManager *eventMan = g_system->getEventManager(); + + Common::Event event; + while (eventMan->pollEvent(event)) { + // drop all events + } +} + #ifdef USE_READLINE_INT_COMPLETION typedef int RLCompFunc_t(const char *, int); #else @@ -228,6 +238,7 @@ void Debugger::enter() { g_readline_debugger = this; rl_completion_entry_function = (RLCompFunc_t *)&readline_completionFunction; + rl_event_hook = (rl_hook_func_t *)&readline_eventFunction; char *line_read = 0; do { @@ -573,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++) { @@ -613,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/dialog.cpp b/gui/dialog.cpp index 315c24e9bf..523227a237 100644 --- a/gui/dialog.cpp +++ b/gui/dialog.cpp @@ -51,6 +51,8 @@ Dialog::Dialog(int x, int y, int w, int h) // will for example crash after returning to the launcher when the user // started a 640x480 game with a non 1x scaler. g_gui.checkScreenChange(); + + _result = -1; } Dialog::Dialog(const Common::String &name) @@ -66,6 +68,8 @@ Dialog::Dialog(const Common::String &name) // Fixes bug #1590596: "HE: When 3x graphics are choosen, F5 crashes game" // and bug #1595627: "SCUMM: F5 crashes game (640x480)" g_gui.checkScreenChange(); + + _result = -1; } int Dialog::runModal() { @@ -109,16 +113,18 @@ void Dialog::reflowLayout() { // changed, so any cached image may be invalid. The subsequent redraw // should be treated as the very first draw. + GuiObject::reflowLayout(); + Widget *w = _firstWidget; while (w) { w->reflowLayout(); w = w->_next; } - - GuiObject::reflowLayout(); } void Dialog::lostFocus() { + _dragWidget = NULL; + if (_tickleWidget) { _tickleWidget->lostFocus(); } diff --git a/gui/dialog.h b/gui/dialog.h index 593ee13458..0e06effabd 100644 --- a/gui/dialog.h +++ b/gui/dialog.h @@ -82,7 +82,7 @@ public: virtual void reflowLayout(); virtual void lostFocus(); - virtual void receivedFocus() {} + virtual void receivedFocus(int x = -1, int y = -1) { if (x >= 0 && y >= 0) handleMouseMoved(x, y, 0); } protected: virtual void open(); 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..85b37929dc --- /dev/null +++ b/gui/editgamedialog.cpp @@ -0,0 +1,548 @@ +/* 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" + +#if defined(USE_CLOUD) && defined(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::apply() { + 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::apply(); +} + +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()); +#if defined(USE_CLOUD) && defined(USE_LIBCURL) + MessageDialog warningMessage(_("Saved games sync feature doesn't work with non-default directories. If you want your saved games 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..a317e364c6 --- /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(); + virtual void apply(); + 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 ef37990dfc..d28a0df8c2 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) { @@ -281,14 +281,9 @@ void GuiManager::runLoop() { redraw(); } - _lastMousePosition.x = _lastMousePosition.y = -1; - _lastMousePosition.time = 0; - Common::EventManager *eventMan = _system->getEventManager(); uint32 lastRedraw = 0; - const uint32 waitTime = 1000 / 45; - - bool tooltipCheck = false; + const uint32 waitTime = 1000 / 60; while (!_dialogStack.empty() && activeDialog == getTopDialog() && !eventMan->shouldQuit()) { redraw(); @@ -304,9 +299,9 @@ void GuiManager::runLoop() { // _system->updateScreen(); if (lastRedraw + waitTime < _system->getMillis(true)) { + lastRedraw = _system->getMillis(true); _theme->updateScreen(); _system->updateScreen(); - lastRedraw = _system->getMillis(true); } Common::Event event; @@ -339,19 +334,14 @@ void GuiManager::runLoop() { processEvent(event, activeDialog); - if (event.type == Common::EVENT_MOUSEMOVE) { - tooltipCheck = true; - } - - if (lastRedraw + waitTime < _system->getMillis(true)) { + lastRedraw = _system->getMillis(true); _theme->updateScreen(); _system->updateScreen(); - lastRedraw = _system->getMillis(true); } } - if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis(true)) { + if (_lastMousePosition.time + kTooltipDelay < _system->getMillis(true)) { Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y); if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)) { Tooltip *tooltip = new Tooltip(); @@ -418,7 +408,7 @@ void GuiManager::restoreState() { } void GuiManager::openDialog(Dialog *dialog) { - dialog->receivedFocus(); + giveFocusToDialog(dialog); if (!_dialogStack.empty()) getTopDialog()->lostFocus(); @@ -442,8 +432,10 @@ void GuiManager::closeTopDialog() { // Remove the dialog from the stack _dialogStack.pop()->lostFocus(); - if (!_dialogStack.empty()) - getTopDialog()->receivedFocus(); + if (!_dialogStack.empty()) { + Dialog *dialog = getTopDialog(); + giveFocusToDialog(dialog); + } if (_redrawStatus != kRedrawFull) _redrawStatus = kRedrawCloseDialog; @@ -520,6 +512,7 @@ void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDi int button; uint32 time; Common::Point mouse(event.mouse.x - activeDialog->_x, event.mouse.y - activeDialog->_y); + switch (event.type) { case Common::EVENT_KEYDOWN: activeDialog->handleKeyDown(event.kbd); @@ -528,12 +521,12 @@ void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDi activeDialog->handleKeyUp(event.kbd); break; case Common::EVENT_MOUSEMOVE: + _globalMousePosition.x = event.mouse.x; + _globalMousePosition.y = event.mouse.y; activeDialog->handleMouseMoved(mouse.x, mouse.y, 0); if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) { - _lastMousePosition.x = mouse.x; - _lastMousePosition.y = mouse.y; - _lastMousePosition.time = _system->getMillis(true); + setLastMousePos(mouse.x, mouse.y); } break; @@ -576,4 +569,23 @@ void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDi } } +void GuiManager::doFullRedraw() { + _redrawStatus = kRedrawFull; + redraw(); + _system->updateScreen(); +} + +void GuiManager::giveFocusToDialog(Dialog *dialog) { + int16 dialogX = _globalMousePosition.x - dialog->_x; + int16 dialogY = _globalMousePosition.y - dialog->_y; + dialog->receivedFocus(dialogX, dialogY); + setLastMousePos(dialogX, dialogY); +} + +void GuiManager::setLastMousePos(int16 x, int16 y) { + _lastMousePosition.x = x; + _lastMousePosition.y = y; + _lastMousePosition.time = _system->getMillis(true); +} + } // End of namespace GUI diff --git a/gui/gui-manager.h b/gui/gui-manager.h index 26c8d6def9..4dc9af95fb 100644 --- a/gui/gui-manager.h +++ b/gui/gui-manager.h @@ -73,6 +73,7 @@ public: void runLoop(); void processEvent(const Common::Event &event, Dialog *const activeDialog); + void doFullRedraw(); bool isActive() const { return ! _dialogStack.empty(); } @@ -124,11 +125,12 @@ protected: bool _useStdCursor; // position and time of last mouse click (used to detect double clicks) - struct { + struct MousePos { + MousePos() : x(-1), y(-1), count(0) { time = 0; } int16 x, y; // Position of mouse when the click occurred uint32 time; // Time int count; // How often was it already pressed? - } _lastClick, _lastMousePosition; + } _lastClick, _lastMousePosition, _globalMousePosition; // mouse cursor state int _cursorAnimateCounter; @@ -155,6 +157,9 @@ protected: Dialog *getTopDialog() const; void screenChange(); + + void giveFocusToDialog(Dialog *dialog); + void setLastMousePos(int16 x, int16 y); }; } // End of namespace GUI diff --git a/gui/launcher.cpp b/gui/launcher.cpp index bae894cba1..f96dd17d46 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" +#if defined(USE_CLOUD) && defined(USE_LIBCURL) +#include "backends/cloud/cloudmanager.h" +#endif using Common::ConfigManager; @@ -84,530 +88,41 @@ 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 - // - 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 - // - 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); - } +#pragma mark - - // 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); - } +LauncherDialog::LauncherDialog() + : Dialog(0, 0, 320, 200) { + _backgroundType = GUI::ThemeEngine::kDialogBackgroundMain; + const int screenW = g_system->getOverlayWidth(); + const int screenH = g_system->getOverlayHeight(); - 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); -} + _w = screenW; + _h = screenH; + build(); -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(); + GUI::GuiManager::instance()._launched = true; } -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; +void LauncherDialog::selectTarget(const String &target) { + if (!target.empty()) { + int itemToSelect = 0; + StringArray::const_iterator iter; + for (iter = _domains.begin(); iter != _domains.end(); ++iter, ++itemToSelect) { + if (target == *iter) { + _list->setSelected(itemToSelect); + break; } - ConfMan.renameGameDomain(_domain, newDomain); - _domain = newDomain; - } } - // FALL THROUGH to default case - default: - OptionsDialog::handleCommand(sender, cmd, data); } } -#pragma mark - - -LauncherDialog::LauncherDialog() - : Dialog(0, 0, 320, 200) { - _backgroundType = GUI::ThemeEngine::kDialogBackgroundMain; - const int screenW = g_system->getOverlayWidth(); - const int screenH = g_system->getOverlayHeight(); - - _w = screenW; - _h = screenH; +LauncherDialog::~LauncherDialog() { + delete _browser; + delete _loadDialog; +} +void LauncherDialog::build() { #ifndef DISABLE_FANCY_THEMES _logo = 0; if (g_gui.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui.theme()->supportsImages()) { @@ -683,28 +198,25 @@ LauncherDialog::LauncherDialog() // Create Load dialog _loadDialog = new SaveLoadChooser(_("Load game:"), _("Load"), false); - - GUI::GuiManager::instance()._launched = true; } -void LauncherDialog::selectTarget(const String &target) { - if (!target.empty()) { - int itemToSelect = 0; - StringArray::const_iterator iter; - for (iter = _domains.begin(); iter != _domains.end(); ++iter, ++itemToSelect) { - if (target == *iter) { - _list->setSelected(itemToSelect); - break; - } - } +void LauncherDialog::clean() { + while (_firstWidget) { + Widget* w = _firstWidget; + removeWidget(w); + delete w; } -} - -LauncherDialog::~LauncherDialog() { delete _browser; delete _loadDialog; } +void LauncherDialog::rebuild() { + clean(); + build(); + reflowLayout(); + setFocusWidget(_firstWidget); +} + void LauncherDialog::open() { // Clear the active domain, in case we return to the dialog from a // failure to launch a game. Otherwise, pressing ESC will attempt to @@ -837,65 +349,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(); +#if defined(USE_CLOUD) && defined(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); } @@ -1074,6 +546,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(); @@ -1091,7 +638,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..08413fe3d2 100644 --- a/gui/launcher.h +++ b/gui/launcher.h @@ -47,11 +47,13 @@ public: LauncherDialog(); ~LauncherDialog(); + void rebuild(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); virtual void handleKeyDown(Common::KeyState state); virtual void handleKeyUp(Common::KeyState state); - + bool doGameDetection(const Common::String &path); protected: EditTextWidget *_searchWidget; ListWidget *_list; @@ -83,6 +85,9 @@ protected: void updateButtons(); void switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText); + void build(); + void clean(); + void open(); void close(); diff --git a/gui/module.mk b/gui/module.mk index bbb3def96d..5b32689a82 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,12 +25,16 @@ 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 \ widgets/list.o \ widgets/popup.o \ widgets/scrollbar.o \ + widgets/scrollcontainer.o \ widgets/tab.o # HACK: create_project's XCode generator relies on the following ifdef @@ -53,6 +58,15 @@ MODULE_OBJS += \ endif endif +ifdef USE_CLOUD +ifdef USE_LIBCURL +MODULE_OBJS += \ + downloaddialog.o \ + remotebrowser.o \ + storagewizarddialog.o +endif +endif + ifdef ENABLE_EVENTRECORDER MODULE_OBJS += \ editrecorddialog.o \ @@ -65,5 +79,10 @@ MODULE_OBJS += \ fluidsynth-dialog.o endif +ifdef USE_UPDATES +MODULE_OBJS += \ + updates-dialog.o +endif + # Include common rules include $(srcdir)/rules.mk diff --git a/gui/object.cpp b/gui/object.cpp index ef2cc9d6e0..de66d95492 100644 --- a/gui/object.cpp +++ b/gui/object.cpp @@ -44,19 +44,6 @@ void GuiObject::reflowLayout() { if (!g_gui.xmlEval()->getWidgetData(_name, _x, _y, _w, _h)) { error("Could not load widget position for '%s'", _name.c_str()); } - - if (_x < 0) - error("Widget <%s> has x < 0 (%d)", _name.c_str(), _x); - if (_x >= g_gui.getWidth()) - error("Widget <%s> has x > %d (%d)", _name.c_str(), g_gui.getWidth(), _x); - if (_x + _w > g_gui.getWidth()) - error("Widget <%s> has x + w > %d (%d)", _name.c_str(), g_gui.getWidth(), _x + _w); - if (_y < 0) - error("Widget <%s> has y < 0 (%d)", _name.c_str(), _y); - if (_y >= g_gui.getHeight()) - error("Widget <%s> has y > %d (%d)", _name.c_str(), g_gui.getHeight(), _y); - if (_y + _h > g_gui.getHeight()) - error("Widget <%s> has y + h > %d (%d)", _name.c_str(), g_gui.getHeight(), _y + _h); } } 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 ba247e5f15..3f6fc5aa2c 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -28,6 +28,7 @@ #include "gui/widgets/popup.h" #include "gui/widgets/tab.h" #include "gui/ThemeEval.h" +#include "gui/launcher.h" #include "common/fs.h" #include "common/config-manager.h" @@ -36,11 +37,26 @@ #include "common/system.h" #include "common/textconsole.h" #include "common/translation.h" +#include "common/updates.h" #include "audio/mididrv.h" #include "audio/musicplugin.h" #include "audio/mixer.h" #include "audio/fmopl.h" +#include "widgets/scrollcontainer.h" +#include "widgets/edittext.h" + +#ifdef USE_CLOUD +#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 +#endif namespace GUI { @@ -61,7 +77,8 @@ enum { kChooseExtraDirCmd = 'chex', kExtraPathClearCmd = 'clex', kChoosePluginsDirCmd = 'chpl', - kChooseThemeCmd = 'chtf' + kChooseThemeCmd = 'chtf', + kUpdatesCheckCmd = 'updc' }; enum { @@ -70,7 +87,7 @@ enum { kSubtitlesBoth }; -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG enum { kChooseKeyMappingCmd = 'chma' }; @@ -82,6 +99,23 @@ enum { }; #endif +#ifdef USE_CLOUD +enum { + kConfigureStorageCmd = 'cfst', + kRefreshStorageCmd = 'rfst', + kDownloadStorageCmd = 'dlst', + kRunServerCmd = 'rnsv', + kCloudTabContainerReflowCmd = 'ctcr', + kServerPortClearCmd = 'spcl', + kChooseRootDirCmd = 'chrp', + kRootPathClearCmd = 'clrp' +}; +#endif + +enum { + kApplyCmd = 'appl' +}; + 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 }; @@ -93,7 +127,7 @@ OptionsDialog::OptionsDialog(const Common::String &domain, int x, int y, int w, } OptionsDialog::OptionsDialog(const Common::String &domain, const Common::String &name) - : Dialog(name), _domain(domain), _graphicsTabId(-1), _tabWidget(0) { + : Dialog(name), _domain(domain), _graphicsTabId(-1), _midiTabId(-1), _pathsTabId(-1), _tabWidget(0) { init(); } @@ -108,6 +142,7 @@ void OptionsDialog::init() { _renderModePopUp = 0; _renderModePopUpDesc = 0; _fullscreenCheckbox = 0; + _filteringCheckbox = 0; _aspectCheckbox = 0; _enableAudioSettings = false; _midiPopUp = 0; @@ -142,6 +177,7 @@ void OptionsDialog::init() { _speechVolumeSlider = 0; _speechVolumeLabel = 0; _muteCheckbox = 0; + _enableSubtitleSettings = false; _subToggleDesc = 0; _subToggleGroup = 0; _subToggleSubOnly = 0; @@ -150,7 +186,6 @@ void OptionsDialog::init() { _subSpeedDesc = 0; _subSpeedSlider = 0; _subSpeedLabel = 0; - _oldTheme = g_gui.theme()->getThemeId(); // Retrieve game GUI options _guioptions.clear(); @@ -159,13 +194,8 @@ void OptionsDialog::init() { _guioptions = parseGameGUIOptions(_guioptionsString); } } - -void OptionsDialog::open() { - Dialog::open(); - - // Reset result value - setResult(0); - + +void OptionsDialog::build() { // Retrieve game GUI options _guioptions.clear(); if (ConfMan.hasKey("guioptions", _domain)) { @@ -204,14 +234,19 @@ void OptionsDialog::open() { _renderModePopUp->setSelectedTag(sel); } -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ONLY_FULLSCREEN _fullscreenCheckbox->setState(true); _fullscreenCheckbox->setEnabled(false); - _aspectCheckbox->setState(false); - _aspectCheckbox->setEnabled(false); -#else // !SMALL_SCREEN_DEVICE +#else // !GUI_ONLY_FULLSCREEN // Fullscreen setting _fullscreenCheckbox->setState(ConfMan.getBool("fullscreen", _domain)); +#endif // GUI_ONLY_FULLSCREEN + + // Filtering setting + if (g_system->hasFeature(OSystem::kFeatureFilteringMode)) + _filteringCheckbox->setState(ConfMan.getBool("filtering", _domain)); + else + _filteringCheckbox->setVisible(false); // Aspect ratio setting if (_guioptions.contains(GUIO_NOASPECT)) { @@ -221,7 +256,6 @@ void OptionsDialog::open() { _aspectCheckbox->setEnabled(true); _aspectCheckbox->setState(ConfMan.getBool("aspect_ratio", _domain)); } -#endif // SMALL_SCREEN_DEVICE } @@ -316,199 +350,238 @@ void OptionsDialog::open() { _subSpeedLabel->setValue(speed); } } + +void OptionsDialog::clean() { + delete _subToggleGroup; + while (_firstWidget) { + Widget* w = _firstWidget; + removeWidget(w); + delete w; + } + init(); +} + +void OptionsDialog::rebuild() { + int currentTab = _tabWidget->getActiveTab(); + clean(); + build(); + reflowLayout(); + _tabWidget->setActiveTab(currentTab); + setFocusWidget(_firstWidget); +} -void OptionsDialog::close() { - if (getResult()) { - - // Graphic options - bool graphicsModeChanged = false; - if (_fullscreenCheckbox) { - if (_enableGraphicSettings) { - if (ConfMan.getBool("fullscreen", _domain) != _fullscreenCheckbox->getState()) - graphicsModeChanged = true; - if (ConfMan.getBool("aspect_ratio", _domain) != _aspectCheckbox->getState()) - graphicsModeChanged = true; - - ConfMan.setBool("fullscreen", _fullscreenCheckbox->getState(), _domain); - ConfMan.setBool("aspect_ratio", _aspectCheckbox->getState(), _domain); - - bool isSet = false; - - if ((int32)_gfxPopUp->getSelectedTag() >= 0) { - const OSystem::GraphicsMode *gm = g_system->getSupportedGraphicsModes(); - - while (gm->name) { - if (gm->id == (int)_gfxPopUp->getSelectedTag()) { - if (ConfMan.get("gfx_mode", _domain) != gm->name) - graphicsModeChanged = true; - ConfMan.set("gfx_mode", gm->name, _domain); - isSet = true; - break; - } - gm++; +void OptionsDialog::open() { + build(); + + Dialog::open(); + + // Reset result value + setResult(0); +} + +void OptionsDialog::apply() { + // Graphic options + bool graphicsModeChanged = false; + if (_fullscreenCheckbox) { + if (_enableGraphicSettings) { + if (ConfMan.getBool("filtering", _domain) != _filteringCheckbox->getState()) + graphicsModeChanged = true; + if (ConfMan.getBool("fullscreen", _domain) != _fullscreenCheckbox->getState()) + graphicsModeChanged = true; + if (ConfMan.getBool("aspect_ratio", _domain) != _aspectCheckbox->getState()) + graphicsModeChanged = true; + + ConfMan.setBool("filtering", _filteringCheckbox->getState(), _domain); + ConfMan.setBool("fullscreen", _fullscreenCheckbox->getState(), _domain); + ConfMan.setBool("aspect_ratio", _aspectCheckbox->getState(), _domain); + + bool isSet = false; + + if ((int32)_gfxPopUp->getSelectedTag() >= 0) { + const OSystem::GraphicsMode *gm = g_system->getSupportedGraphicsModes(); + + while (gm->name) { + if (gm->id == (int)_gfxPopUp->getSelectedTag()) { + if (ConfMan.get("gfx_mode", _domain) != gm->name) + graphicsModeChanged = true; + ConfMan.set("gfx_mode", gm->name, _domain); + isSet = true; + break; } + gm++; } - if (!isSet) - ConfMan.removeKey("gfx_mode", _domain); - - if ((int32)_renderModePopUp->getSelectedTag() >= 0) - ConfMan.set("render_mode", Common::getRenderModeCode((Common::RenderMode)_renderModePopUp->getSelectedTag()), _domain); - } else { - ConfMan.removeKey("fullscreen", _domain); - ConfMan.removeKey("aspect_ratio", _domain); - ConfMan.removeKey("gfx_mode", _domain); - ConfMan.removeKey("render_mode", _domain); } + if (!isSet) + ConfMan.removeKey("gfx_mode", _domain); + + if ((int32)_renderModePopUp->getSelectedTag() >= 0) + ConfMan.set("render_mode", Common::getRenderModeCode((Common::RenderMode)_renderModePopUp->getSelectedTag()), _domain); + } else { + ConfMan.removeKey("fullscreen", _domain); + ConfMan.removeKey("filtering", _domain); + ConfMan.removeKey("aspect_ratio", _domain); + ConfMan.removeKey("gfx_mode", _domain); + ConfMan.removeKey("render_mode", _domain); } - - // Setup graphics again if needed - if (_domain == Common::ConfigManager::kApplicationDomain && graphicsModeChanged) { - g_system->beginGFXTransaction(); - g_system->setGraphicsMode(ConfMan.get("gfx_mode", _domain).c_str()); - - if (ConfMan.hasKey("aspect_ratio")) - g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio", _domain)); - if (ConfMan.hasKey("fullscreen")) - g_system->setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen", _domain)); - OSystem::TransactionError gfxError = g_system->endGFXTransaction(); - - // Since this might change the screen resolution we need to give - // the GUI a chance to update it's internal state. Otherwise we might - // get a crash when the GUI tries to grab the overlay. - // - // This fixes bug #3303501 "Switching from HQ2x->HQ3x crashes ScummVM" - // - // It is important that this is called *before* any of the current - // dialog's widgets are destroyed (for example before - // Dialog::close) is called, to prevent crashes caused by invalid - // widgets being referenced or similar errors. - g_gui.checkScreenChange(); - - if (gfxError != OSystem::kTransactionSuccess) { - // Revert ConfMan to what OSystem is using. - Common::String message = _("Failed to apply some of the graphic options changes:"); - - if (gfxError & OSystem::kTransactionModeSwitchFailed) { - const OSystem::GraphicsMode *gm = g_system->getSupportedGraphicsModes(); - while (gm->name) { - if (gm->id == g_system->getGraphicsMode()) { - ConfMan.set("gfx_mode", gm->name, _domain); - break; - } - gm++; + } + + // Setup graphics again if needed + if (_domain == Common::ConfigManager::kApplicationDomain && graphicsModeChanged) { + g_system->beginGFXTransaction(); + g_system->setGraphicsMode(ConfMan.get("gfx_mode", _domain).c_str()); + + if (ConfMan.hasKey("aspect_ratio")) + g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio", _domain)); + if (ConfMan.hasKey("fullscreen")) + g_system->setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen", _domain)); + if (ConfMan.hasKey("filtering")) + g_system->setFeatureState(OSystem::kFeatureFilteringMode, ConfMan.getBool("filtering", _domain)); + + OSystem::TransactionError gfxError = g_system->endGFXTransaction(); + + // Since this might change the screen resolution we need to give + // the GUI a chance to update it's internal state. Otherwise we might + // get a crash when the GUI tries to grab the overlay. + // + // This fixes bug #3303501 "Switching from HQ2x->HQ3x crashes ScummVM" + // + // It is important that this is called *before* any of the current + // dialog's widgets are destroyed (for example before + // Dialog::close) is called, to prevent crashes caused by invalid + // widgets being referenced or similar errors. + g_gui.checkScreenChange(); + + if (gfxError != OSystem::kTransactionSuccess) { + // Revert ConfMan to what OSystem is using. + Common::String message = _("Failed to apply some of the graphic options changes:"); + + if (gfxError & OSystem::kTransactionModeSwitchFailed) { + const OSystem::GraphicsMode *gm = g_system->getSupportedGraphicsModes(); + while (gm->name) { + if (gm->id == g_system->getGraphicsMode()) { + ConfMan.set("gfx_mode", gm->name, _domain); + break; } - message += "\n"; - message += _("the video mode could not be changed."); + gm++; } - - if (gfxError & OSystem::kTransactionAspectRatioFailed) { - ConfMan.setBool("aspect_ratio", g_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection), _domain); - message += "\n"; - message += _("the fullscreen setting could not be changed"); - } - - if (gfxError & OSystem::kTransactionFullscreenFailed) { - ConfMan.setBool("fullscreen", g_system->getFeatureState(OSystem::kFeatureFullscreenMode), _domain); - message += "\n"; - message += _("the aspect ratio setting could not be changed"); - } - - // And display the error - GUI::MessageDialog dialog(message); - dialog.runModal(); + message += "\n"; + message += _("the video mode could not be changed."); } - } - - // Volume options - if (_musicVolumeSlider) { - if (_enableVolumeSettings) { - ConfMan.setInt("music_volume", _musicVolumeSlider->getValue(), _domain); - ConfMan.setInt("sfx_volume", _sfxVolumeSlider->getValue(), _domain); - ConfMan.setInt("speech_volume", _speechVolumeSlider->getValue(), _domain); - ConfMan.setBool("mute", _muteCheckbox->getState(), _domain); - } else { - ConfMan.removeKey("music_volume", _domain); - ConfMan.removeKey("sfx_volume", _domain); - ConfMan.removeKey("speech_volume", _domain); - ConfMan.removeKey("mute", _domain); + + if (gfxError & OSystem::kTransactionAspectRatioFailed) { + ConfMan.setBool("aspect_ratio", g_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection), _domain); + message += "\n"; + message += _("the aspect ratio setting could not be changed"); } - } - - // Audio options - if (_midiPopUp) { - if (_enableAudioSettings) { - saveMusicDeviceSetting(_midiPopUp, "music_driver"); - } else { - ConfMan.removeKey("music_driver", _domain); + + if (gfxError & OSystem::kTransactionFullscreenFailed) { + ConfMan.setBool("fullscreen", g_system->getFeatureState(OSystem::kFeatureFullscreenMode), _domain); + message += "\n"; + message += _("the fullscreen setting could not be changed"); + } + + if (gfxError & OSystem::kTransactionFilteringFailed) { + ConfMan.setBool("filtering", g_system->getFeatureState(OSystem::kFeatureFilteringMode), _domain); + message += "\n"; + message += _("the filtering setting could not be changed"); } + + // And display the error + GUI::MessageDialog dialog(message); + dialog.runModal(); } - - if (_oplPopUp) { - if (_enableAudioSettings) { - const OPL::Config::EmulatorDescription *ed = OPL::Config::findDriver(_oplPopUp->getSelectedTag()); - - if (ed) - ConfMan.set("opl_driver", ed->name, _domain); - else - ConfMan.removeKey("opl_driver", _domain); - } else { + } + + // Volume options + if (_musicVolumeSlider) { + if (_enableVolumeSettings) { + ConfMan.setInt("music_volume", _musicVolumeSlider->getValue(), _domain); + ConfMan.setInt("sfx_volume", _sfxVolumeSlider->getValue(), _domain); + ConfMan.setInt("speech_volume", _speechVolumeSlider->getValue(), _domain); + ConfMan.setBool("mute", _muteCheckbox->getState(), _domain); + } else { + ConfMan.removeKey("music_volume", _domain); + ConfMan.removeKey("sfx_volume", _domain); + ConfMan.removeKey("speech_volume", _domain); + ConfMan.removeKey("mute", _domain); + } + } + + // Audio options + if (_midiPopUp) { + if (_enableAudioSettings) { + saveMusicDeviceSetting(_midiPopUp, "music_driver"); + } else { + ConfMan.removeKey("music_driver", _domain); + } + } + + if (_oplPopUp) { + if (_enableAudioSettings) { + const OPL::Config::EmulatorDescription *ed = OPL::Config::findDriver(_oplPopUp->getSelectedTag()); + + if (ed) + ConfMan.set("opl_driver", ed->name, _domain); + else ConfMan.removeKey("opl_driver", _domain); - } + } else { + ConfMan.removeKey("opl_driver", _domain); } - - if (_outputRatePopUp) { - if (_enableAudioSettings) { - if (_outputRatePopUp->getSelectedTag() != 0) - ConfMan.setInt("output_rate", _outputRatePopUp->getSelectedTag(), _domain); - else - ConfMan.removeKey("output_rate", _domain); - } else { + } + + if (_outputRatePopUp) { + if (_enableAudioSettings) { + if (_outputRatePopUp->getSelectedTag() != 0) + ConfMan.setInt("output_rate", _outputRatePopUp->getSelectedTag(), _domain); + else ConfMan.removeKey("output_rate", _domain); - } + } else { + ConfMan.removeKey("output_rate", _domain); } - - // MIDI options - if (_multiMidiCheckbox) { - if (_enableMIDISettings) { - saveMusicDeviceSetting(_gmDevicePopUp, "gm_device"); - - ConfMan.setBool("multi_midi", _multiMidiCheckbox->getState(), _domain); - ConfMan.setInt("midi_gain", _midiGainSlider->getValue(), _domain); - - Common::String soundFont(_soundFont->getLabel()); - if (!soundFont.empty() && (soundFont != _c("None", "soundfont"))) - ConfMan.set("soundfont", soundFont, _domain); - else - ConfMan.removeKey("soundfont", _domain); - } else { - ConfMan.removeKey("gm_device", _domain); - ConfMan.removeKey("multi_midi", _domain); - ConfMan.removeKey("midi_gain", _domain); + } + + // MIDI options + if (_multiMidiCheckbox) { + if (_enableMIDISettings) { + saveMusicDeviceSetting(_gmDevicePopUp, "gm_device"); + + ConfMan.setBool("multi_midi", _multiMidiCheckbox->getState(), _domain); + ConfMan.setInt("midi_gain", _midiGainSlider->getValue(), _domain); + + Common::String soundFont(_soundFont->getLabel()); + if (!soundFont.empty() && (soundFont != _c("None", "soundfont"))) + ConfMan.set("soundfont", soundFont, _domain); + else ConfMan.removeKey("soundfont", _domain); - } + } else { + ConfMan.removeKey("gm_device", _domain); + ConfMan.removeKey("multi_midi", _domain); + ConfMan.removeKey("midi_gain", _domain); + ConfMan.removeKey("soundfont", _domain); } - - // MT-32 options - if (_mt32DevicePopUp) { - if (_enableMT32Settings) { - saveMusicDeviceSetting(_mt32DevicePopUp, "mt32_device"); - ConfMan.setBool("native_mt32", _mt32Checkbox->getState(), _domain); - ConfMan.setBool("enable_gs", _enableGSCheckbox->getState(), _domain); - } else { - ConfMan.removeKey("mt32_device", _domain); - ConfMan.removeKey("native_mt32", _domain); - ConfMan.removeKey("enable_gs", _domain); - } + } + + // MT-32 options + if (_mt32DevicePopUp) { + if (_enableMT32Settings) { + saveMusicDeviceSetting(_mt32DevicePopUp, "mt32_device"); + ConfMan.setBool("native_mt32", _mt32Checkbox->getState(), _domain); + ConfMan.setBool("enable_gs", _enableGSCheckbox->getState(), _domain); + } else { + ConfMan.removeKey("mt32_device", _domain); + ConfMan.removeKey("native_mt32", _domain); + ConfMan.removeKey("enable_gs", _domain); } - - // Subtitle options - if (_subToggleGroup) { - if (_enableSubtitleSettings) { - bool subtitles, speech_mute; - int talkspeed; - int sliderMaxValue = _subSpeedSlider->getMaxValue(); - - switch (_subToggleGroup->getValue()) { + } + + // Subtitle options + if (_subToggleGroup) { + if (_enableSubtitleSettings) { + bool subtitles, speech_mute; + int talkspeed; + int sliderMaxValue = _subSpeedSlider->getMaxValue(); + + switch (_subToggleGroup->getValue()) { case kSubtitlesSpeech: subtitles = speech_mute = false; break; @@ -520,26 +593,30 @@ void OptionsDialog::close() { default: subtitles = speech_mute = true; break; - } - - ConfMan.setBool("subtitles", subtitles, _domain); - ConfMan.setBool("speech_mute", speech_mute, _domain); - - // Engines that reuse the subtitle speed widget set their own max value. - // Scale the config value accordingly (see addSubtitleControls) - talkspeed = (_subSpeedSlider->getValue() * 255 + sliderMaxValue / 2) / sliderMaxValue; - ConfMan.setInt("talkspeed", talkspeed, _domain); - - } else { - ConfMan.removeKey("subtitles", _domain); - ConfMan.removeKey("talkspeed", _domain); - ConfMan.removeKey("speech_mute", _domain); } + + ConfMan.setBool("subtitles", subtitles, _domain); + ConfMan.setBool("speech_mute", speech_mute, _domain); + + // Engines that reuse the subtitle speed widget set their own max value. + // Scale the config value accordingly (see addSubtitleControls) + talkspeed = (_subSpeedSlider->getValue() * 255 + sliderMaxValue / 2) / sliderMaxValue; + ConfMan.setInt("talkspeed", talkspeed, _domain); + + } else { + ConfMan.removeKey("subtitles", _domain); + ConfMan.removeKey("talkspeed", _domain); + ConfMan.removeKey("speech_mute", _domain); } - - // Save config file - ConfMan.flushToDisk(); } + + // Save config file + ConfMan.flushToDisk(); +} + +void OptionsDialog::close() { + if (getResult()) + apply(); Dialog::close(); } @@ -581,15 +658,14 @@ void OptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data _soundFontClearButton->setEnabled(false); draw(); break; + case kApplyCmd: + apply(); + break; case kOKCmd: setResult(1); close(); break; case kCloseCmd: - if (g_gui.theme()->getThemeId() != _oldTheme) { - g_gui.loadNewTheme(_oldTheme); - ConfMan.set("gui_theme", _oldTheme); - } close(); break; default: @@ -604,7 +680,8 @@ void OptionsDialog::setGraphicSettingsState(bool enabled) { _gfxPopUp->setEnabled(enabled); _renderModePopUpDesc->setEnabled(enabled); _renderModePopUp->setEnabled(enabled); -#ifndef SMALL_SCREEN_DEVICE + _filteringCheckbox->setEnabled(enabled); +#ifndef GUI_ENABLE_KEYSDIALOG _fullscreenCheckbox->setEnabled(enabled); if (_guioptions.contains(GUIO_NOASPECT)) _aspectCheckbox->setEnabled(false); @@ -757,6 +834,9 @@ void OptionsDialog::addGraphicControls(GuiObject *boss, const Common::String &pr // Fullscreen checkbox _fullscreenCheckbox = new CheckboxWidget(boss, prefix + "grFullscreenCheckbox", _("Fullscreen mode")); + // Filtering checkbox + _filteringCheckbox = new CheckboxWidget(boss, prefix + "grFilteringCheckbox", _("Filter graphics"), _("Use linear filtering when scaling graphics")); + // Aspect ratio checkbox _aspectCheckbox = new CheckboxWidget(boss, prefix + "grAspectCheckbox", _("Aspect ratio correction"), _("Correct aspect ratio for 320x200 games")); @@ -868,10 +948,6 @@ void OptionsDialog::addMIDIControls(GuiObject *boss, const Common::String &prefi _midiGainSlider->setMaxValue(1000); _midiGainLabel = new StaticTextWidget(boss, prefix + "mcMidiGainLabel", "1.00"); -#ifdef USE_FLUIDSYNTH - new ButtonWidget(boss, prefix + "mcFluidSynthSettings", _("FluidSynth Settings"), 0, kFluidSynthSettingsCmd); -#endif - _enableMIDISettings = true; } @@ -1076,9 +1152,77 @@ void OptionsDialog::reflowLayout() { #pragma mark - -GlobalOptionsDialog::GlobalOptionsDialog() - : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions") { +GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher) + : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions"), _launcher(launcher) { +#ifdef GUI_ENABLE_KEYSDIALOG + _keysDialog = 0; +#endif +#ifdef USE_FLUIDSYNTH + _fluidSynthSettingsDialog = 0; +#endif + _savePath = 0; + _savePathClearButton = 0; + _themePath = 0; + _themePathClearButton = 0; + _extraPath = 0; + _extraPathClearButton = 0; +#ifdef DYNAMIC_MODULES + _pluginsPath = 0; +#endif + _curTheme = 0; + _rendererPopUpDesc = 0; + _rendererPopUp = 0; + _autosavePeriodPopUpDesc = 0; + _autosavePeriodPopUp = 0; + _guiLanguagePopUpDesc = 0; + _guiLanguagePopUp = 0; +#ifdef USE_UPDATES + _updatesPopUpDesc = 0; + _updatesPopUp = 0; +#endif +#ifdef USE_CLOUD +#ifdef USE_LIBCURL + _selectedStorageIndex = CloudMan.getStorageIndex(); +#else + _selectedStorageIndex = 0; +#endif + _storagePopUpDesc = 0; + _storagePopUp = 0; + _storageUsernameDesc = 0; + _storageUsername = 0; + _storageUsedSpaceDesc = 0; + _storageUsedSpace = 0; + _storageLastSyncDesc = 0; + _storageLastSync = 0; + _storageConnectButton = 0; + _storageRefreshButton = 0; + _storageDownloadButton = 0; + _runServerButton = 0; + _serverInfoLabel = 0; + _rootPathButton = 0; + _rootPath = 0; + _rootPathClearButton = 0; + _serverPortDesc = 0; + _serverPort = 0; + _serverPortClearButton = 0; + _redrawCloudTab = false; +#ifdef USE_SDL_NET + _serverWasRunning = false; +#endif +#endif +} +GlobalOptionsDialog::~GlobalOptionsDialog() { +#ifdef GUI_ENABLE_KEYSDIALOG + delete _keysDialog; +#endif + +#ifdef USE_FLUIDSYNTH + delete _fluidSynthSettingsDialog; +#endif +} + +void GlobalOptionsDialog::build() { // The tab widget TabWidget *tab = new TabWidget(this, "GlobalOptions.TabWidget"); @@ -1109,6 +1253,10 @@ GlobalOptionsDialog::GlobalOptionsDialog() _midiTabId = tab->addTab(_("MIDI")); addMIDIControls(tab, "GlobalOptions_MIDI."); +#ifdef USE_FLUIDSYNTH + new ButtonWidget(tab, "GlobalOptions_MIDI.mcFluidSynthSettings", _("FluidSynth Settings"), 0, kFluidSynthSettingsCmd); +#endif + // // 4) The MT-32 tab // @@ -1194,7 +1342,7 @@ GlobalOptionsDialog::GlobalOptionsDialog() _autosavePeriodPopUp->appendEntry(_(savePeriodLabels[i]), savePeriodValues[i]); } -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG new ButtonWidget(tab, "GlobalOptions_Misc.KeysButton", _("Keys"), 0, kChooseKeyMappingCmd); #endif @@ -1229,35 +1377,100 @@ GlobalOptionsDialog::GlobalOptionsDialog() #endif // USE_TRANSLATION +#ifdef USE_UPDATES + _updatesPopUpDesc = new StaticTextWidget(tab, "GlobalOptions_Misc.UpdatesPopupDesc", _("Update check:"), _("How often to check ScummVM updates")); + _updatesPopUp = new PopUpWidget(tab, "GlobalOptions_Misc.UpdatesPopup"); + + const int *vals = Common::UpdateManager::getUpdateIntervals(); + + while (*vals != -1) { + _updatesPopUp->appendEntry(Common::UpdateManager::updateIntervalToString(*vals), *vals); + vals++; + } + + _updatesPopUp->setSelectedTag(Common::UpdateManager::normalizeInterval(ConfMan.getInt("updates_check"))); + + 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); + + _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 saved games 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 saved games 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(); +#endif // USE_CLOUD + // Activate the first tab tab->setActiveTab(0); _tabWidget = tab; // Add OK & Cancel buttons new ButtonWidget(this, "GlobalOptions.Cancel", _("Cancel"), 0, kCloseCmd); + new ButtonWidget(this, "GlobalOptions.Apply", _("Apply"), 0, kApplyCmd); new ButtonWidget(this, "GlobalOptions.Ok", _("OK"), 0, kOKCmd); -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG _keysDialog = new KeysDialog(); #endif #ifdef USE_FLUIDSYNTH _fluidSynthSettingsDialog = new FluidSynthSettingsDialog(); #endif -} - -GlobalOptionsDialog::~GlobalOptionsDialog() { -#ifdef SMALL_SCREEN_DEVICE - delete _keysDialog; -#endif - -#ifdef USE_FLUIDSYNTH - delete _fluidSynthSettingsDialog; -#endif -} -void GlobalOptionsDialog::open() { - OptionsDialog::open(); + OptionsDialog::build(); #if !( defined(__DC__) || defined(__GP32__) ) // Set _savePath to the current save path @@ -1305,69 +1518,174 @@ 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() { - if (getResult()) { - Common::String savePath(_savePath->getLabel()); - if (!savePath.empty() && (savePath != _("Default"))) - ConfMan.set("savepath", savePath, _domain); - else - ConfMan.removeKey("savepath", _domain); +void GlobalOptionsDialog::clean() { +#ifdef GUI_ENABLE_KEYSDIALOG + delete _keysDialog; + _keysDialog = 0; +#endif - Common::String themePath(_themePath->getLabel()); - if (!themePath.empty() && (themePath != _c("None", "path"))) - ConfMan.set("themepath", themePath, _domain); - else - ConfMan.removeKey("themepath", _domain); +#ifdef USE_FLUIDSYNTH + delete _fluidSynthSettingsDialog; + _fluidSynthSettingsDialog = 0; +#endif - Common::String extraPath(_extraPath->getLabel()); - if (!extraPath.empty() && (extraPath != _c("None", "path"))) - ConfMan.set("extrapath", extraPath, _domain); - else - ConfMan.removeKey("extrapath", _domain); + OptionsDialog::clean(); +} + +void GlobalOptionsDialog::apply() { + Common::String savePath(_savePath->getLabel()); + if (!savePath.empty() && (savePath != _("Default"))) + ConfMan.set("savepath", savePath, _domain); + else + ConfMan.removeKey("savepath", _domain); + + Common::String themePath(_themePath->getLabel()); + if (!themePath.empty() && (themePath != _c("None", "path"))) + ConfMan.set("themepath", themePath, _domain); + else + ConfMan.removeKey("themepath", _domain); + + Common::String extraPath(_extraPath->getLabel()); + if (!extraPath.empty() && (extraPath != _c("None", "path"))) + ConfMan.set("extrapath", extraPath, _domain); + else + ConfMan.removeKey("extrapath", _domain); #ifdef DYNAMIC_MODULES - Common::String pluginsPath(_pluginsPath->getLabel()); - if (!pluginsPath.empty() && (pluginsPath != _c("None", "path"))) - ConfMan.set("pluginspath", pluginsPath, _domain); - else - ConfMan.removeKey("pluginspath", _domain); + Common::String pluginsPath(_pluginsPath->getLabel()); + if (!pluginsPath.empty() && (pluginsPath != _c("None", "path"))) + ConfMan.set("pluginspath", pluginsPath, _domain); + else + ConfMan.removeKey("pluginspath", _domain); #endif - ConfMan.setInt("autosave_period", _autosavePeriodPopUp->getSelectedTag(), _domain); +#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 - GUI::ThemeEngine::GraphicsMode selected = (GUI::ThemeEngine::GraphicsMode)_rendererPopUp->getSelectedTag(); - const char *cfg = GUI::ThemeEngine::findModeConfigName(selected); - if (!ConfMan.get("gui_renderer").equalsIgnoreCase(cfg)) { - // FIXME: Actually, any changes (including the theme change) should - // only become active *after* the options dialog has closed. - g_gui.loadNewTheme(g_gui.theme()->getThemeId(), selected); - ConfMan.set("gui_renderer", cfg, _domain); - } + ConfMan.setInt("autosave_period", _autosavePeriodPopUp->getSelectedTag(), _domain); + + GUI::ThemeEngine::GraphicsMode selected = (GUI::ThemeEngine::GraphicsMode)_rendererPopUp->getSelectedTag(); + const char *cfg = GUI::ThemeEngine::findModeConfigName(selected); + if (!ConfMan.get("gui_renderer").equalsIgnoreCase(cfg)) { + // FIXME: Actually, any changes (including the theme change) should + // only become active *after* the options dialog has closed. + g_gui.loadNewTheme(g_gui.theme()->getThemeId(), selected); + ConfMan.set("gui_renderer", cfg, _domain); + } #ifdef USE_TRANSLATION - Common::String oldLang = ConfMan.get("gui_language"); - int selLang = _guiLanguagePopUp->getSelectedTag(); + Common::String oldLang = ConfMan.get("gui_language"); + int selLang = _guiLanguagePopUp->getSelectedTag(); - ConfMan.set("gui_language", TransMan.getLangById(selLang)); + ConfMan.set("gui_language", TransMan.getLangById(selLang)); - Common::String newLang = ConfMan.get("gui_language").c_str(); - if (newLang != oldLang) { -#if 0 - // Activate the selected language - TransMan.setLanguage(selLang); + Common::String newLang = ConfMan.get("gui_language").c_str(); + if (newLang != oldLang) { + // Activate the selected language + TransMan.setLanguage(selLang); - // FIXME: Actually, any changes (including the theme change) should - // only become active *after* the options dialog has closed. - g_gui.loadNewTheme(g_gui.theme()->getThemeId(), ThemeEngine::kGfxDisabled, true); -#else - MessageDialog error(_("You have to restart ScummVM before your changes will take effect.")); - error.runModal(); + // Rebuild the Launcher and Options dialogs + g_gui.loadNewTheme(g_gui.theme()->getThemeId(), ThemeEngine::kGfxDisabled, true); + rebuild(); + if (_launcher != 0) + _launcher->rebuild(); + } +#endif // USE_TRANSLATION + +#ifdef USE_UPDATES + ConfMan.setInt("updates_check", _updatesPopUp->getSelectedTag()); + + if (g_system->getUpdateManager()) { + if (_updatesPopUp->getSelectedTag() == Common::UpdateManager::kUpdateIntervalNotSupported) { + g_system->getUpdateManager()->setAutomaticallyChecksForUpdates(Common::UpdateManager::kUpdateStateDisabled); + } else { + g_system->getUpdateManager()->setAutomaticallyChecksForUpdates(Common::UpdateManager::kUpdateStateEnabled); + g_system->getUpdateManager()->setUpdateCheckInterval(_updatesPopUp->getSelectedTag()); + } + } #endif + +#ifdef USE_CLOUD +#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 // USE_TRANSLATION + } +#endif // USE_LIBCURL + +#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 // NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE +#endif // USE_SDL_NET +#endif // USE_CLOUD + + if (!_newTheme.empty()) { +#ifdef USE_TRANSLATION + Common::String lang = TransMan.getCurrentLanguage(); +#endif + Common::String oldTheme = g_gui.theme()->getThemeId(); + if (g_gui.loadNewTheme(_newTheme)) { +#ifdef USE_TRANSLATION + // If the charset has changed, it means the font were not found for the + // new theme. Since for the moment we do not support change of translation + // language without restarting, we let the user know about this. + if (lang != TransMan.getCurrentLanguage()) { + TransMan.setLanguage(lang.c_str()); + g_gui.loadNewTheme(oldTheme); + _curTheme->setLabel(g_gui.theme()->getThemeName()); + MessageDialog error(_("The theme you selected does not support your current language. If you want to use this theme you need to switch to another language first.")); + error.runModal(); + } else { +#endif + ConfMan.set("gui_theme", _newTheme); +#ifdef USE_TRANSLATION + } +#endif + } + draw(); + _newTheme.clear(); + } + + OptionsDialog::apply(); +} +void GlobalOptionsDialog::close() { +#if defined(USE_CLOUD) && defined(USE_SDL_NET) + if (LocalServer.isRunning()) { + LocalServer.stop(); } +#endif OptionsDialog::close(); } @@ -1421,6 +1739,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; @@ -1430,6 +1763,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) { @@ -1446,39 +1784,102 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 } break; } - case kChooseThemeCmd: { + case kChooseThemeCmd: + { ThemeBrowser browser; if (browser.runModal() > 0) { // User made his choice... - Common::String theme = browser.getSelected(); - // FIXME: Actually, any changes (including the theme change) should - // only become active *after* the options dialog has closed. -#ifdef USE_TRANSLATION - Common::String lang = TransMan.getCurrentLanguage(); -#endif - if (g_gui.loadNewTheme(theme)) { -#ifdef USE_TRANSLATION - // If the charset has changed, it means the font were not found for the - // new theme. Since for the moment we do not support change of translation - // language without restarting, we let the user know about this. - if (lang != TransMan.getCurrentLanguage()) { - TransMan.setLanguage(lang.c_str()); - g_gui.loadNewTheme(_oldTheme); - MessageDialog error(_("The theme you selected does not support your current language. If you want to use this theme you need to switch to another language first.")); - error.runModal(); - } else { -#endif - _curTheme->setLabel(g_gui.theme()->getThemeName()); - ConfMan.set("gui_theme", theme); -#ifdef USE_TRANSLATION - } -#endif + _newTheme = browser.getSelected(); + _curTheme->setLabel(browser.getSelectedName()); + } + break; + } +#ifdef USE_CLOUD + case kCloudTabContainerReflowCmd: + setupCloudTab(); + break; +#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 // NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + 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 // USE_LIBCURL +#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; } - draw(); + ConfMan.setInt("local_server_port", port); + ConfMan.flushToDisk(); +#endif // NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + + 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; } -#ifdef SMALL_SCREEN_DEVICE +#endif // USE_SDL_NET +#endif // USE_CLOUD +#ifdef GUI_ENABLE_KEYSDIALOG case kChooseKeyMappingCmd: _keysDialog->runModal(); break; @@ -1488,11 +1889,34 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 _fluidSynthSettingsDialog->runModal(); break; #endif +#ifdef USE_UPDATES + case kUpdatesCheckCmd: + if (g_system->getUpdateManager()) + g_system->getUpdateManager()->checkForUpdates(); + break; +#endif default: OptionsDialog::handleCommand(sender, cmd, data); } } +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(); @@ -1526,6 +1950,219 @@ 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 // USE_LIBCURL + _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 // USE_LIBCURL +#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 // NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + if (_serverPortDesc) + _serverPortDesc->setVisible(false); + if (_serverPort) + _serverPort->setVisible(false); + if (_serverPortClearButton) + _serverPortClearButton->setVisible(false); +#endif // NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE +#else // USE_SDL_NET + 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 // USE_SDL_NET +} + +#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 // USE_LIBCURL +#endif // USE_CLOUD } // End of namespace GUI diff --git a/gui/options.h b/gui/options.h index 1e65bfd134..a6eebe5748 100644 --- a/gui/options.h +++ b/gui/options.h @@ -29,7 +29,7 @@ #include "common/str.h" #include "audio/mididrv.h" -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG #include "gui/KeysDialog.h" #endif @@ -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; @@ -61,6 +67,7 @@ public: void init(); void open(); + virtual void apply(); void close(); void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); const Common::String& getDomain() const { return _domain; } @@ -74,6 +81,10 @@ protected: ButtonWidget *_soundFontButton; StaticTextWidget *_soundFont; ButtonWidget *_soundFontClearButton; + + virtual void build(); + virtual void clean(); + void rebuild(); void addGraphicControls(GuiObject *boss, const Common::String &prefix); void addAudioControls(GuiObject *boss, const Common::String &prefix); @@ -108,6 +119,7 @@ private: StaticTextWidget *_gfxPopUpDesc; PopUpWidget *_gfxPopUp; CheckboxWidget *_fullscreenCheckbox; + CheckboxWidget *_filteringCheckbox; CheckboxWidget *_aspectCheckbox; StaticTextWidget *_renderModePopUpDesc; PopUpWidget *_renderModePopUp; @@ -187,11 +199,6 @@ protected: Common::String _guioptionsString; // - //Theme Options - // - Common::String _oldTheme; - - // // Engine-specific controls // CheckboxWidgetList _engineCheckboxes; @@ -200,17 +207,23 @@ protected: class GlobalOptionsDialog : public OptionsDialog { public: - GlobalOptionsDialog(); + GlobalOptionsDialog(LauncherDialog *launcher); ~GlobalOptionsDialog(); - void open(); + virtual void apply(); void close(); void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + void handleTickle(); virtual void reflowLayout(); protected: -#ifdef SMALL_SCREEN_DEVICE + virtual void build(); + virtual void clean(); + + Common::String _newTheme; + LauncherDialog *_launcher; +#ifdef GUI_ENABLE_KEYSDIALOG KeysDialog *_keysDialog; #endif #ifdef USE_FLUIDSYNTH @@ -236,6 +249,49 @@ protected: PopUpWidget *_autosavePeriodPopUp; StaticTextWidget *_guiLanguagePopUpDesc; PopUpWidget *_guiLanguagePopUp; + +#ifdef USE_UPDATES + 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(); + +#ifdef USE_LIBCURL + void storageInfoCallback(Cloud::Storage::StorageInfoResponse response); + void storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response); + void storageErrorCallback(Networking::ErrorResponse response); +#endif +#endif // USE_CLOUD }; } // End of namespace GUI diff --git a/gui/predictivedialog.cpp b/gui/predictivedialog.cpp index 6ff10267db..933667186e 100644 --- a/gui/predictivedialog.cpp +++ b/gui/predictivedialog.cpp @@ -142,6 +142,9 @@ PredictiveDialog::PredictiveDialog() : Dialog("Predictive") { _numMemory = 0; _navigationWithKeys = false; + + _curPressedButton = kNoAct; + _needRefresh = true; } PredictiveDialog::~PredictiveDialog() { @@ -190,7 +193,7 @@ void PredictiveDialog::saveUserDictToFile() { void PredictiveDialog::handleKeyUp(Common::KeyState state) { if (_curPressedButton != kNoAct && !_needRefresh) { - _button[_curPressedButton]->startAnimatePressedState(); + _button[_curPressedButton]->setUnpressedState(); processButton(_curPressedButton); } } @@ -352,7 +355,7 @@ void PredictiveDialog::handleKeyDown(Common::KeyState state) { } if (_lastButton != _curPressedButton) - _button[_lastButton]->stopAnimatePressedState(); + _button[_lastButton]->setUnpressedState(); if (_curPressedButton != kNoAct && !_needRefresh) _button[_curPressedButton]->setPressedState(); @@ -604,18 +607,6 @@ void PredictiveDialog::processButton(ButtonId button) { } } -void PredictiveDialog::handleTickle() { - if (_lastTime) { - if ((_curTime - _lastTime) > kRepeatDelay) { - _lastTime = 0; - } - } - - if (getTickleWidget()) { - getTickleWidget()->handleTickle(); - } -} - void PredictiveDialog::mergeDicts() { _unitedDict.dictLineCount = _predictiveDict.dictLineCount + _userDict.dictLineCount; _unitedDict.dictLine = (char **)calloc(_unitedDict.dictLineCount, sizeof(char *)); @@ -724,6 +715,10 @@ int PredictiveDialog::binarySearch(const char *const *const dictLine, const Comm } bool PredictiveDialog::matchWord() { + // If there is no dictionary, then there is no match. + if (_unitedDict.dictLineCount <= 0) + return false; + // If no text has been entered, then there is no match. if (_currentCode.empty()) return false; @@ -981,6 +976,7 @@ void PredictiveDialog::loadAllDictionary(Dict &dict) { Common::File *inFile = new Common::File(); if (!inFile->open(ConfMan.get(dict.nameDict))) { warning("Predictive Dialog: cannot read file: %s", dict.defaultFilename.c_str()); + delete inFile; return; } loadDictionary(inFile, dict); diff --git a/gui/predictivedialog.h b/gui/predictivedialog.h index 37c80a2a14..1f6bdf84e0 100644 --- a/gui/predictivedialog.h +++ b/gui/predictivedialog.h @@ -43,7 +43,6 @@ public: virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); virtual void handleKeyUp(Common::KeyState state); virtual void handleKeyDown(Common::KeyState state); - virtual void handleTickle(); const char *getResult() const { return _predictiveResult; } @@ -85,6 +84,7 @@ private: struct Dict { Dict() : dictLine(nullptr), dictText(nullptr), dictActLine(nullptr), dictLineCount(0), dictTextSize(0) {} + ~Dict() { free(dictText); } char **dictLine; char *dictText; char *dictActLine; // using only for united dict... diff --git a/gui/recorderdialog.cpp b/gui/recorderdialog.cpp index 2d74cebbb6..c08b3e149a 100644 --- a/gui/recorderdialog.cpp +++ b/gui/recorderdialog.cpp @@ -33,7 +33,6 @@ #include "gui/editrecorddialog.h" #include "gui/EventRecorder.h" #include "gui/message.h" -#include "gui/saveload.h" #include "common/system.h" #include "gui/ThemeEval.h" #include "gui/gui-manager.h" 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 a333c5fe57..af02e36c13 100644 --- a/gui/saveload-dialog.cpp +++ b/gui/saveload-dialog.cpp @@ -21,6 +21,13 @@ */ #include "gui/saveload-dialog.h" + +#if defined(USE_CLOUD) && defined(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 { +#if defined(USE_CLOUD) && defined(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() { +#if defined(USE_CLOUD) && defined(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() { +#if defined(USE_CLOUD) && defined(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 +#if defined(USE_CLOUD) && defined(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); } +#if defined(USE_CLOUD) && defined(USE_LIBCURL) +void SaveLoadChooserDialog::runSaveSync(bool hasSavepathOverride) { + if (!CloudMan.isSyncing()) { + if (hasSavepathOverride) { + CloudMan.showCloudDisabledIcon(); + } else { + Cloud::SavesSyncRequest *request = CloudMan.syncSaves(); + if (request) + request->setTarget(this); + } + } +} +#endif + +void SaveLoadChooserDialog::handleTickle() { +#if defined(USE_CLOUD) && defined(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() { +#if defined(USE_CLOUD) && defined(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()); + +#if defined(USE_CLOUD) && defined(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. @@ -745,7 +921,7 @@ void SaveLoadChooserGrid::reflowLayout() { // In the save mode we will always create a new save button as the first button. if (_saveMode && curLine == 0 && curColumn == 0) { _newSaveContainer = new ContainerWidget(this, curX, y, containerWidth, containerHeight); - ButtonWidget *newSave = new ButtonWidget(_newSaveContainer, dstX, dstY, buttonWidth, buttonHeight, _("New Save"), _("Create a new save game"), kNewSaveCmd); + ButtonWidget *newSave = new ButtonWidget(_newSaveContainer, dstX, dstY, buttonWidth, buttonHeight, _("New Save"), _("Create a new saved game"), kNewSaveCmd); // In case no more slots are free, we will disable the new save button if (_nextFreeSaveSlot == -1) { newSave->setEnabled(false); @@ -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; @@ -932,6 +1112,8 @@ SavenameDialog::SavenameDialog() new ButtonWidget(this, "SavenameDialog.Ok", _("OK"), 0, kOKCmd); _description = new EditTextWidget(this, "SavenameDialog.Description", Common::String(), 0, 0, kOKCmd); + + _targetSlot = 0; } void SavenameDialog::setDescription(const Common::String &desc) { diff --git a/gui/saveload-dialog.h b/gui/saveload-dialog.h index 31f28f6452..43721aa270 100644 --- a/gui/saveload-dialog.h +++ b/gui/saveload-dialog.h @@ -30,6 +30,25 @@ namespace GUI { +#if defined(USE_CLOUD) && defined(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); +#if defined(USE_CLOUD) && defined(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 c1c1d12ec5..2ac68dd8f9 100644 --- a/gui/saveload.cpp +++ b/gui/saveload.cpp @@ -25,7 +25,6 @@ #include "gui/saveload.h" #include "gui/saveload-dialog.h" -#include "gui/gui-manager.h" #include "engines/metaengine.h" @@ -88,6 +87,10 @@ int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, con if (!_impl) return -1; +#if defined(USE_CLOUD) && defined(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/saveload.h b/gui/saveload.h index 22c26d4c5e..01a78c4924 100644 --- a/gui/saveload.h +++ b/gui/saveload.h @@ -23,7 +23,7 @@ #ifndef GUI_SAVELOAD_H #define GUI_SAVELOAD_H -#include "gui/dialog.h" +#include "common/str.h" #include "engines/metaengine.h" namespace GUI { diff --git a/gui/storagewizarddialog.cpp b/gui/storagewizarddialog.cpp new file mode 100644 index 0000000000..085f901fc4 --- /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/themebrowser.cpp b/gui/themebrowser.cpp index d8bd5d6fe8..75be555e0a 100644 --- a/gui/themebrowser.cpp +++ b/gui/themebrowser.cpp @@ -84,6 +84,7 @@ void ThemeBrowser::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) ++sel; _select = sel->id; + _selectName = sel->name; setResult(1); close(); break; diff --git a/gui/themebrowser.h b/gui/themebrowser.h index 2d94a7f423..bb0bec8c1a 100644 --- a/gui/themebrowser.h +++ b/gui/themebrowser.h @@ -42,9 +42,11 @@ public: void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); const Common::String &getSelected() const { return _select; } + const Common::String &getSelectedName() const { return _selectName; } private: ListWidget *_fileList; Common::String _select; + Common::String _selectName; typedef Common::List<ThemeEngine::ThemeDescriptor> ThemeDescList; ThemeDescList _themes; diff --git a/gui/themes/default.inc b/gui/themes/default.inc index e367bcb3c7..a83fd788ad 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -798,12 +798,15 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='GlobalOptions' overlays='Dialog.Launcher.GameList' shading='dim'>" "<layout type='vertical' padding='0,0,0,0'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='16,16,16,16'>" "<space/>" "<widget name='Cancel' " "type='Button' " "/>" +"<widget name='Apply' " +"type='Button' " +"/>" "<widget name='Ok' " "type='Button' " "/>" @@ -834,6 +837,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "<widget name='grFullscreenCheckbox' " "type='Checkbox' " "/>" +"<widget name='grFilteringCheckbox' " +"type='Checkbox' " +"/>" "</layout>" "</dialog>" "<dialog name='GlobalOptions_Audio' overlays='Dialog.GlobalOptions.TabWidget'>" @@ -1076,11 +1082,232 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "type='PopUp' " "/>" "</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='UpdatesPopupDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='UpdatesPopup' " +"type='PopUp' " +"/>" +"<widget name='UpdatesCheckManuallyButton' " +"type='Button' " +"/>" +"</layout>" "<widget name='KeysButton' " "type='Button' " "/>" "</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' " @@ -1107,7 +1334,7 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='GameOptions' overlays='Dialog.Launcher.GameList' shading='dim'>" "<layout type='vertical' padding='0,0,0,0' spacing='16'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='16,16,16,4'>" "<space/>" "<widget name='Cancel' " @@ -1394,7 +1621,7 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='FluidSynthSettings' overlays='GlobalOptions' shading='dim'>" "<layout type='vertical' padding='0,0,0,0'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='16,16,16,16'>" "<space/>" "<widget name='ResetSettings' " @@ -1581,6 +1808,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' " @@ -1964,7 +2222,7 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "padding='0,0,2,0' " "/>" "<widget name='TabWidget.Body' " -"padding='0,0,0,-8' " +"padding='0,0,0,0' " "/>" "<widget name='TabWidget.NavButton' " "size='32,18' " @@ -2082,12 +2340,15 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='GlobalOptions' overlays='screen' inset='16' shading='dim'>" "<layout type='vertical' padding='0,0,0,0'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='8,8,8,8'>" "<space/>" "<widget name='Cancel' " "type='Button' " "/>" +"<widget name='Apply' " +"type='Button' " +"/>" "<widget name='Ok' " "type='Button' " "/>" @@ -2118,6 +2379,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "<widget name='grFullscreenCheckbox' " "type='Checkbox' " "/>" +"<widget name='grFilteringCheckbox' " +"type='Checkbox' " +"/>" "</layout>" "</dialog>" "<dialog name='GlobalOptions_Audio' overlays='Dialog.GlobalOptions.TabWidget'>" @@ -2365,11 +2629,239 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "type='PopUp' " "/>" "</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='UpdatesPopupDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='UpdatesPopup' " +"type='PopUp' " +"/>" +"<widget name='UpdatesCheckManuallyButton' " +"type='Button' " +"/>" +"</layout>" "<widget name='KeysButton' " "type='Button' " "/>" "</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='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='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' " @@ -2396,7 +2888,7 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='GameOptions' overlays='screen' inset='16' shading='dim'>" "<layout type='vertical' padding='0,0,0,0' spacing='16'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='8,8,8,8'>" "<space/>" "<widget name='Cancel' " @@ -2692,7 +3184,7 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</dialog>" "<dialog name='FluidSynthSettings' overlays='GlobalOptions' shading='dim'>" "<layout type='vertical' padding='0,0,0,0'>" -"<widget name='TabWidget'/>" +"<widget name='TabWidget' type='TabWidget'/>" "<layout type='horizontal' padding='8,8,8,8'>" "<space/>" "<widget name='ResetSettings' " @@ -2864,6 +3356,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 Binary files differindex 1d8b8dad05..400b997b93 100644 --- a/gui/themes/scummclassic.zip +++ b/gui/themes/scummclassic.zip diff --git a/gui/themes/scummclassic/THEMERC b/gui/themes/scummclassic/THEMERC index 7e58285d08..25d8531376 100644 --- a/gui/themes/scummclassic/THEMERC +++ b/gui/themes/scummclassic/THEMERC @@ -1 +1 @@ -[SCUMMVM_STX0.8.21:ScummVM Classic Theme:No Author] +[SCUMMVM_STX0.8.23:ScummVM Classic Theme:No Author] 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 18c2a1f889..b3100d4b92 100644 --- a/gui/themes/scummclassic/classic_layout.stx +++ b/gui/themes/scummclassic/classic_layout.stx @@ -222,12 +222,15 @@ <dialog name = 'GlobalOptions' overlays = 'Dialog.Launcher.GameList' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 16'> <space/> <widget name = 'Cancel' type = 'Button' /> + <widget name = 'Apply' + type = 'Button' + /> <widget name = 'Ok' type = 'Button' /> @@ -259,6 +262,9 @@ <widget name = 'grFullscreenCheckbox' type = 'Checkbox' /> + <widget name = 'grFilteringCheckbox' + type = 'Checkbox' + /> </layout> </dialog> @@ -507,12 +513,238 @@ type = 'PopUp' /> </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'UpdatesPopupDesc' + type = 'OptionsLabel' + /> + <widget name = 'UpdatesPopup' + type = 'PopUp' + /> + <widget name = 'UpdatesCheckManuallyButton' + type = 'Button' + /> + </layout> <widget name='KeysButton' type='Button' /> </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' @@ -540,7 +772,7 @@ <dialog name = 'GameOptions' overlays = 'Dialog.Launcher.GameList' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '16'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 4'> <space/> <widget name = 'Cancel' @@ -839,7 +1071,7 @@ <dialog name = 'FluidSynthSettings' overlays = 'GlobalOptions' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 16'> <space/> <widget name = 'ResetSettings' @@ -1031,6 +1263,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 6cc9eed6b3..7879e05a97 100644 --- a/gui/themes/scummclassic/classic_layout_lowres.stx +++ b/gui/themes/scummclassic/classic_layout_lowres.stx @@ -97,7 +97,7 @@ padding = '0, 0, 2, 0' /> <widget name = 'TabWidget.Body' - padding = '0, 0, 0, -8' + padding = '0, 0, 0, 0' /> <widget name = 'TabWidget.NavButton' size = '32, 18' @@ -219,13 +219,15 @@ <dialog name = 'GlobalOptions' overlays = 'screen' inset = '16' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'Cancel' type = 'Button' /> - + <widget name = 'Apply' + type = 'Button' + /> <widget name = 'Ok' type = 'Button' /> @@ -257,6 +259,9 @@ <widget name = 'grFullscreenCheckbox' type = 'Checkbox' /> + <widget name = 'grFilteringCheckbox' + type = 'Checkbox' + /> </layout> </dialog> @@ -510,12 +515,245 @@ type = 'PopUp' /> </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'UpdatesPopupDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'UpdatesPopup' + type = 'PopUp' + /> + <widget name = 'UpdatesCheckManuallyButton' + type = 'Button' + /> + </layout> <widget name='KeysButton' type='Button' /> </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 = '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 = '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' @@ -543,7 +781,7 @@ <dialog name = 'GameOptions' overlays = 'screen' inset = '16' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '16'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'Cancel' @@ -850,7 +1088,7 @@ <dialog name = 'FluidSynthSettings' overlays = 'GlobalOptions' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'ResetSettings' @@ -1027,6 +1265,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 Binary files differindex 43826abf5e..673d67ed87 100644 --- a/gui/themes/scummmodern.zip +++ b/gui/themes/scummmodern.zip diff --git a/gui/themes/scummmodern/THEMERC b/gui/themes/scummmodern/THEMERC index dc98bdc00e..3dbd640865 100644 --- a/gui/themes/scummmodern/THEMERC +++ b/gui/themes/scummmodern/THEMERC @@ -1 +1 @@ -[SCUMMVM_STX0.8.21:ScummVM Modern Theme:No Author] +[SCUMMVM_STX0.8.23:ScummVM Modern Theme:No Author] diff --git a/gui/themes/scummmodern/box.bmp b/gui/themes/scummmodern/box.bmp Binary files differnew file mode 100644 index 0000000000..21fb650f02 --- /dev/null +++ b/gui/themes/scummmodern/box.bmp diff --git a/gui/themes/scummmodern/dropbox.bmp b/gui/themes/scummmodern/dropbox.bmp Binary files differnew file mode 100644 index 0000000000..4ed95f0009 --- /dev/null +++ b/gui/themes/scummmodern/dropbox.bmp diff --git a/gui/themes/scummmodern/googledrive.bmp b/gui/themes/scummmodern/googledrive.bmp Binary files differnew file mode 100644 index 0000000000..30377a5f74 --- /dev/null +++ b/gui/themes/scummmodern/googledrive.bmp diff --git a/gui/themes/scummmodern/onedrive.bmp b/gui/themes/scummmodern/onedrive.bmp Binary files differnew file mode 100644 index 0000000000..cd26d71d3c --- /dev/null +++ b/gui/themes/scummmodern/onedrive.bmp 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 fd6a3c5cd2..9cadc11e13 100644 --- a/gui/themes/scummmodern/scummmodern_layout.stx +++ b/gui/themes/scummmodern/scummmodern_layout.stx @@ -236,12 +236,15 @@ <dialog name = 'GlobalOptions' overlays = 'Dialog.Launcher.GameList' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 16'> <space/> <widget name = 'Cancel' type = 'Button' /> + <widget name = 'Apply' + type = 'Button' + /> <widget name = 'Ok' type = 'Button' /> @@ -273,6 +276,9 @@ <widget name = 'grFullscreenCheckbox' type = 'Checkbox' /> + <widget name = 'grFilteringCheckbox' + type = 'Checkbox' + /> </layout> </dialog> @@ -521,12 +527,238 @@ type = 'PopUp' /> </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'UpdatesPopupDesc' + type = 'OptionsLabel' + /> + <widget name = 'UpdatesPopup' + type = 'PopUp' + /> + <widget name = 'UpdatesCheckManuallyButton' + type = 'Button' + /> + </layout> <widget name='KeysButton' type='Button' /> </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' @@ -554,7 +786,7 @@ <dialog name = 'GameOptions' overlays = 'Dialog.Launcher.GameList' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '16'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 4'> <space/> <widget name = 'Cancel' @@ -853,7 +1085,7 @@ <dialog name = 'FluidSynthSettings' overlays = 'GlobalOptions' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '16, 16, 16, 16'> <space/> <widget name = 'ResetSettings' @@ -1045,6 +1277,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 9d8f79795c..7ef5fc5ee1 100644 --- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx +++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx @@ -95,7 +95,7 @@ padding = '0, 0, 2, 0' /> <widget name = 'TabWidget.Body' - padding = '0, 0, 0, -8' + padding = '0, 0, 0, 0' /> <widget name = 'TabWidget.NavButton' size = '32, 18' @@ -217,13 +217,15 @@ <dialog name = 'GlobalOptions' overlays = 'screen' inset = '16' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'Cancel' type = 'Button' /> - + <widget name = 'Apply' + type = 'Button' + /> <widget name = 'Ok' type = 'Button' /> @@ -255,6 +257,9 @@ <widget name = 'grFullscreenCheckbox' type = 'Checkbox' /> + <widget name = 'grFilteringCheckbox' + type = 'Checkbox' + /> </layout> </dialog> @@ -508,12 +513,245 @@ type = 'PopUp' /> </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'UpdatesPopupDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'UpdatesPopup' + type = 'PopUp' + /> + <widget name = 'UpdatesCheckManuallyButton' + type = 'Button' + /> + </layout> <widget name='KeysButton' type='Button' /> </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 = '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 = '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' @@ -541,7 +779,7 @@ <dialog name = 'GameOptions' overlays = 'screen' inset = '16' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'Cancel' @@ -848,7 +1086,7 @@ <dialog name = 'FluidSynthSettings' overlays = 'GlobalOptions' shading = 'dim'> <layout type = 'vertical' padding = '0, 0, 0, 0'> - <widget name = 'TabWidget'/> + <widget name = 'TabWidget' type = 'TabWidget'/> <layout type = 'horizontal' padding = '8, 8, 8, 8'> <space/> <widget name = 'ResetSettings' @@ -1025,6 +1263,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 Binary files differindex 63617258e8..e57f42f383 100644 --- a/gui/themes/translations.dat +++ b/gui/themes/translations.dat diff --git a/gui/updates-dialog.cpp b/gui/updates-dialog.cpp new file mode 100644 index 0000000000..b82ecc0402 --- /dev/null +++ b/gui/updates-dialog.cpp @@ -0,0 +1,148 @@ +/* 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 "common/system.h" +#include "common/translation.h" +#include "common/updates.h" +#include "common/config-manager.h" + +#include "gui/updates-dialog.h" +#include "gui/gui-manager.h" +#include "gui/ThemeEval.h" +#include "gui/widgets/popup.h" + +namespace GUI { + +enum { + kYesCmd = 'YES ', + kNoCmd = 'NO ', + kCheckBoxCmd = 'CHK ' +}; + + +UpdatesDialog::UpdatesDialog() : Dialog(30, 20, 260, 124) { + + const int screenW = g_system->getOverlayWidth(); + const int screenH = g_system->getOverlayHeight(); + + int buttonWidth = g_gui.xmlEval()->getVar("Globals.Button.Width", 0); + int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0); + + const char *message = _( + "ScummVM now supports automatic check for updates\n" + "which requires access to the Internet.\n" + "\n" + "Would you like to enable this feature?"); + const char *message2 = _("(You can always enable it in the options dialog on the Misc tab)"); + + // First, determine the size the dialog needs. For this we have to break + // down the string into lines, and taking the maximum of their widths. + // Using this, and accounting for the space the button(s) need, we can set + // the real size of the dialog + Common::Array<Common::String> lines, lines2; + int maxlineWidth = g_gui.getFont().wordWrapText(message, screenW - 2 * 20, lines); + int maxlineWidth2 = g_gui.getFont(ThemeEngine::kFontStyleTooltip).wordWrapText(message2, screenW - 2 * 20, lines2); + + _w = MAX(MAX(maxlineWidth, maxlineWidth2), (2 * buttonWidth) + 10) + 20; + + int lineCount = lines.size() + 1 + lines2.size(); + + _h = 16 + 3 * kLineHeight; + _h += buttonHeight + 8; + + _h += lineCount * kLineHeight; + + // Center the dialog + _x = (screenW - _w) / 2; + _y = (screenH - _h) / 2; + + // Each line is represented by one static text item. + uint y = 10; + for (uint i = 0; i < lines.size(); i++) { + new StaticTextWidget(this, 10, y, maxlineWidth, kLineHeight, + lines[i], Graphics::kTextAlignCenter); + y += kLineHeight; + } + for (uint i = 0; i < lines2.size(); i++) { + new StaticTextWidget(this, 10, y, maxlineWidth2, kLineHeight, + lines2[i], Graphics::kTextAlignCenter, 0, ThemeEngine::kFontStyleTooltip); + y += kLineHeight; + } + + y += kLineHeight; + _updatesCheckbox = new CheckboxWidget(this, 10, y, _w, kLineHeight, _("Check for updates automatically"), 0, kCheckBoxCmd); + + y += kLineHeight + 3; + + _updatesPopUp = new PopUpWidget(this, 10, y, _w - 20, g_gui.xmlEval()->getVar("Globals.PopUp.Height", kLineHeight)); + + const int *vals = Common::UpdateManager::getUpdateIntervals(); + + while (*vals != -1) { + _updatesPopUp->appendEntry(Common::UpdateManager::updateIntervalToString(*vals), *vals); + vals++; + } + + _updatesPopUp->setSelectedTag(Common::UpdateManager::kUpdateIntervalOneWeek); + + int yesButtonPos = (_w - (buttonWidth * 2)) / 2; + int noButtonPos = ((_w - (buttonWidth * 2)) / 2) + buttonWidth + 10; + + _yesButton = new ButtonWidget(this, yesButtonPos, _h - buttonHeight - 8, buttonWidth, buttonHeight, + _("Proceed"), 0, kYesCmd, Common::ASCII_RETURN); + _noButton = new ButtonWidget(this, noButtonPos, _h - buttonHeight - 8, buttonWidth, buttonHeight, + _("Cancel"), 0, kNoCmd, Common::ASCII_ESCAPE); + + _updatesPopUp->setEnabled(false); + _yesButton->setEnabled(false); +} + +void UpdatesDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + if (cmd == kYesCmd) { + ConfMan.setInt("updates_check", _updatesPopUp->getSelectedTag()); + + if (g_system->getUpdateManager()) { + if (_updatesCheckbox->getState() == false || + _updatesPopUp->getSelectedTag() == Common::UpdateManager::kUpdateIntervalNotSupported) { + g_system->getUpdateManager()->setAutomaticallyChecksForUpdates(Common::UpdateManager::kUpdateStateDisabled); + } else { + g_system->getUpdateManager()->setAutomaticallyChecksForUpdates(Common::UpdateManager::kUpdateStateEnabled); + g_system->getUpdateManager()->setUpdateCheckInterval(_updatesPopUp->getSelectedTag()); + } + } + close(); + } else if (cmd == kNoCmd) { + ConfMan.setInt("updates_check", Common::UpdateManager::kUpdateIntervalNotSupported); + g_system->getUpdateManager()->setAutomaticallyChecksForUpdates(Common::UpdateManager::kUpdateStateDisabled); + + close(); + } else if (cmd == kCheckBoxCmd) { + _updatesPopUp->setEnabled(_updatesCheckbox->getState()); + _yesButton->setEnabled(_updatesCheckbox->getState()); + + draw(); + } else { + Dialog::handleCommand(sender, cmd, data); + } +} + +} // End of namespace GUI diff --git a/gui/updates-dialog.h b/gui/updates-dialog.h new file mode 100644 index 0000000000..9c429c960c --- /dev/null +++ b/gui/updates-dialog.h @@ -0,0 +1,54 @@ +/* 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_UPDATES_DIALOG_H +#define GUI_UPDATES_DIALOG_H + +#include "gui/dialog.h" + +namespace GUI { + +class CheckboxWidget; +class CommandSender; +class ButtonWidget; +class PopUpWidget; + +/** + * Wizard for updates opt-in + */ +class UpdatesDialog : public Dialog { +public: + UpdatesDialog(); + virtual ~UpdatesDialog() {} + + void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + +private: + PopUpWidget *_updatesPopUp; + ButtonWidget *_yesButton; + ButtonWidget *_noButton; + CheckboxWidget *_updatesCheckbox; +}; + +} // End of namespace GUI + +#endif diff --git a/gui/widget.cpp b/gui/widget.cpp index 851774fd70..898d5671c6 100644 --- a/gui/widget.cpp +++ b/gui/widget.cpp @@ -53,6 +53,31 @@ void Widget::init() { _boss->_firstWidget = this; } +Common::Rect Widget::getBossClipRect() const { + int bx = _boss->getAbsX(); + int by = _boss->getAbsY(); + Common::Rect result = Common::Rect(bx, by, bx + _boss->getWidth(), by + _boss->getHeight()); + bool needsClipping = false; + + //check whether clipping area is inside the screen + if (result.left < 0 && (needsClipping = true)) + warning("Widget <%s> has clipping area x < 0 (%d)", _name.c_str(), result.left); + if (result.left >= g_gui.getWidth() && (needsClipping = true)) + warning("Widget <%s> has clipping area x > %d (%d)", _name.c_str(), g_gui.getWidth(), result.left); + if (result.right > g_gui.getWidth() && (needsClipping = true)) + warning("Widget <%s> has clipping area x + w > %d (%d)", _name.c_str(), g_gui.getWidth(), result.right); + if (result.top < 0 && (needsClipping = true)) + warning("Widget <%s> has clipping area y < 0 (%d)", _name.c_str(), result.top); + if (result.top >= g_gui.getHeight() && (needsClipping = true)) + warning("Widget <%s> has clipping area y > %d (%d)", _name.c_str(), g_gui.getHeight(), result.top); + if (result.bottom > g_gui.getHeight() && (needsClipping = true)) + warning("Widget <%s> has clipping area y + h > %d (%d)", _name.c_str(), g_gui.getHeight(), result.bottom); + + if (needsClipping) + result.clip(g_gui.getWidth(), g_gui.getHeight()); + return result; +} + Widget::~Widget() { delete _next; _next = 0; @@ -99,7 +124,7 @@ void Widget::draw() { // Draw border if (_flags & WIDGET_BORDER) { - g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x+_w, _y+_h), 0, ThemeEngine::kWidgetBackgroundBorder); + g_gui.theme()->drawWidgetBackgroundClip(Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), 0, ThemeEngine::kWidgetBackgroundBorder); _x += 4; _y += 4; _w -= 8; @@ -131,7 +156,7 @@ void Widget::draw() { Widget *Widget::findWidgetInChain(Widget *w, int x, int y) { while (w) { // Stop as soon as we find a widget that contains the point (x,y) - if (x >= w->_x && x < w->_x + w->_w && y >= w->_y && y < w->_y + w->_h) + if (x >= w->_x && x < w->_x + w->_w && y >= w->_y && y < w->_y + w->getHeight()) break; w = w->_next; } @@ -229,20 +254,22 @@ Common::String Widget::cleanupHotkey(const Common::String &label) { #pragma mark - -StaticTextWidget::StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &text, Graphics::TextAlign align, const char *tooltip) +StaticTextWidget::StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &text, Graphics::TextAlign align, const char *tooltip, ThemeEngine::FontStyle font) : Widget(boss, x, y, w, h, tooltip), _align(align) { setFlags(WIDGET_ENABLED); _type = kStaticTextWidget; _label = text; + _font = font; } -StaticTextWidget::StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::String &text, const char *tooltip) +StaticTextWidget::StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::String &text, const char *tooltip, ThemeEngine::FontStyle font) : Widget(boss, name, tooltip) { setFlags(WIDGET_ENABLED); _type = kStaticTextWidget; _label = text; _align = g_gui.xmlEval()->getWidgetTextHAlign(name); + _font = font; } void StaticTextWidget::setValue(int value) { @@ -263,21 +290,32 @@ void StaticTextWidget::setLabel(const Common::String &label) { } void StaticTextWidget::setAlign(Graphics::TextAlign align) { - _align = align; - // TODO: We should automatically redraw when the alignment is changed. - // See setLabel() for more insights. + if (_align != align){ + _align = align; + + // same as setLabel() actually, the text + // would be redrawn on top of the old one so + // we add the CLEARBG flag + setFlags(WIDGET_CLEARBG); + draw(); + clearFlags(WIDGET_CLEARBG); + } + } void StaticTextWidget::drawWidget() { - g_gui.theme()->drawText(Common::Rect(_x, _y, _x+_w, _y+_h), _label, _state, _align); + g_gui.theme()->drawTextClip( + Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), + _label, _state, _align, ThemeEngine::kTextInversionNone, 0, true, _font + ); } #pragma mark - ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : StaticTextWidget(boss, x, y, w, h, cleanupHotkey(label), Graphics::kTextAlignCenter, tooltip), CommandSender(boss), - _cmd(cmd), _hotkey(hotkey), _lastTime(0) { + _cmd(cmd), _hotkey(hotkey), _lastTime(0), _duringPress(false) { if (hotkey == 0) _hotkey = parseHotkey(label); @@ -288,7 +326,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Co ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : StaticTextWidget(boss, name, cleanupHotkey(label), tooltip), CommandSender(boss), - _cmd(cmd), _hotkey(hotkey), _lastTime(0) { + _cmd(cmd), _hotkey(hotkey), _lastTime(0), _duringPress(false) { if (hotkey == 0) _hotkey = parseHotkey(label); setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG); @@ -296,18 +334,23 @@ ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Co } void ButtonWidget::handleMouseUp(int x, int y, int button, int clickCount) { - if (isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h) { - startAnimatePressedState(); + if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) { + setUnpressedState(); sendCommand(_cmd, 0); } + _duringPress = false; } void ButtonWidget::handleMouseDown(int x, int y, int button, int clickCount) { + _duringPress = true; setPressedState(); } void ButtonWidget::drawWidget() { - g_gui.theme()->drawButton(Common::Rect(_x, _y, _x+_w, _y+_h), _label, _state, getFlags()); + g_gui.theme()->drawButtonClip( + Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), + _label, _state, getFlags() + ); } void ButtonWidget::setLabel(const Common::String &label) { @@ -340,62 +383,43 @@ void ButtonWidget::setHighLighted(bool enable) { draw(); } -void ButtonWidget::handleTickle() { - if (_lastTime) { - uint32 curTime = g_system->getMillis(); - if (curTime - _lastTime > kPressedButtonTime) { - stopAnimatePressedState(); - } - } -} - void ButtonWidget::setPressedState() { - wantTickle(true); setFlags(WIDGET_PRESSED); + clearFlags(WIDGET_HILITED); draw(); } -void ButtonWidget::stopAnimatePressedState() { - wantTickle(false); - _lastTime = 0; +void ButtonWidget::setUnpressedState() { clearFlags(WIDGET_PRESSED); draw(); } -void ButtonWidget::startAnimatePressedState() { - _lastTime = g_system->getMillis(); -} - -void ButtonWidget::wantTickle(bool tickled) { - if (tickled) - ((GUI::Dialog *)_boss)->setTickleWidget(this); - else - ((GUI::Dialog *)_boss)->unSetTickleWidget(); -} - #pragma mark - PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey) : 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; @@ -411,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) @@ -422,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()->drawButton(Common::Rect(_x, _y, _x+_w, _y+_h), "", _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]; + + 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); - g_gui.theme()->drawSurface(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), _gfx, _state, _alpha, _transparency); + } else { + g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), *gfx, _mode, _alpha); + } + } } } @@ -460,9 +544,10 @@ CheckboxWidget::CheckboxWidget(GuiObject *boss, const Common::String &name, cons } void CheckboxWidget::handleMouseUp(int x, int y, int button, int clickCount) { - if (isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h) { + if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) { toggleState(); } + _duringPress = false; } void CheckboxWidget::setState(bool state) { @@ -475,7 +560,7 @@ void CheckboxWidget::setState(bool state) { } void CheckboxWidget::drawWidget() { - g_gui.theme()->drawCheckbox(Common::Rect(_x, _y, _x+_w, _y+_h), _label, _state, Widget::_state); + g_gui.theme()->drawCheckboxClip(Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), _label, _state, Widget::_state); } #pragma mark - @@ -523,9 +608,10 @@ RadiobuttonWidget::RadiobuttonWidget(GuiObject *boss, const Common::String &name } void RadiobuttonWidget::handleMouseUp(int x, int y, int button, int clickCount) { - if (isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h) { + if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) { toggleState(); } + _duringPress = false; } void RadiobuttonWidget::setState(bool state, bool setGroup) { @@ -543,21 +629,21 @@ void RadiobuttonWidget::setState(bool state, bool setGroup) { } void RadiobuttonWidget::drawWidget() { - g_gui.theme()->drawRadiobutton(Common::Rect(_x, _y, _x+_w, _y+_h), _label, _state, Widget::_state); + g_gui.theme()->drawRadiobuttonClip(Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), _label, _state, Widget::_state); } #pragma mark - SliderWidget::SliderWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd) : Widget(boss, x, y, w, h, tooltip), CommandSender(boss), - _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false) { + _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) { setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG); _type = kSliderWidget; } SliderWidget::SliderWidget(GuiObject *boss, const Common::String &name, const char *tooltip, uint32 cmd) : Widget(boss, name, tooltip), CommandSender(boss), - _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false) { + _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) { setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG); _type = kSliderWidget; } @@ -611,7 +697,7 @@ void SliderWidget::handleMouseWheel(int x, int y, int direction) { } void SliderWidget::drawWidget() { - g_gui.theme()->drawSlider(Common::Rect(_x, _y, _x + _w, _y + _h), valueToBarWidth(_value), _state); + g_gui.theme()->drawSliderClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), valueToBarWidth(_value), _state); } int SliderWidget::valueToBarWidth(int value) { @@ -629,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; } @@ -663,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; @@ -688,7 +794,24 @@ void GraphicsWidget::drawWidget() { const int x = _x + (_w - _gfx.w) / 2; const int y = _y + (_h - _gfx.h) / 2; - g_gui.theme()->drawSurface(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), _gfx, _state, _alpha, _transparency); + 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); + } } } @@ -725,7 +848,7 @@ void ContainerWidget::removeWidget(Widget *widget) { } void ContainerWidget::drawWidget() { - g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), 0, ThemeEngine::kWidgetBackgroundBorder); + g_gui.theme()->drawWidgetBackgroundClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), 0, ThemeEngine::kWidgetBackgroundBorder); } } // End of namespace GUI diff --git a/gui/widget.h b/gui/widget.h index 9891f32b36..e9343f264c 100644 --- a/gui/widget.h +++ b/gui/widget.h @@ -68,7 +68,8 @@ enum { kPopUpWidget = 'POPU', kTabWidget = 'TABW', kGraphicsWidget = 'GFXW', - kContainerWidget = 'CTNR' + kContainerWidget = 'CTNR', + kScrollContainerWidget = 'SCTR' }; enum { @@ -79,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; @@ -111,6 +121,7 @@ public: virtual int16 getAbsX() const { return _x + _boss->getChildX(); } virtual int16 getAbsY() const { return _y + _boss->getChildY(); } + virtual Common::Rect getBossClipRect() const; virtual void setPos(int x, int y) { _x = x; _y = y; } virtual void setSize(int w, int h) { _w = w; _h = h; } @@ -168,9 +179,10 @@ class StaticTextWidget : public Widget { protected: Common::String _label; Graphics::TextAlign _align; + ThemeEngine::FontStyle _font; public: - StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &text, Graphics::TextAlign align, const char *tooltip = 0); - StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::String &text, const char *tooltip = 0); + StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &text, Graphics::TextAlign align, const char *tooltip = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleBold); + StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::String &text, const char *tooltip = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleBold); void setValue(int value); void setLabel(const Common::String &label); const Common::String &getLabel() const { return _label; } @@ -198,19 +210,15 @@ public: void handleMouseUp(int x, int y, int button, int clickCount); void handleMouseDown(int x, int y, int button, int clickCount); - void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); draw(); } + void handleMouseEntered(int button) { if (_duringPress) { setFlags(WIDGET_PRESSED); } else { setFlags(WIDGET_HILITED); } draw(); } void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED | WIDGET_PRESSED); draw(); } - void handleTickle(); void setHighLighted(bool enable); void setPressedState(); - void startAnimatePressedState(); - void stopAnimatePressedState(); - - void lostFocusWidget() { stopAnimatePressedState(); } + void setUnpressedState(); protected: void drawWidget(); - void wantTickle(bool tickled); + bool _duringPress; private: uint32 _lastTime; }; @@ -222,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 */ @@ -352,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; } @@ -360,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 2d929113b1..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. // @@ -274,7 +289,7 @@ void EditableWidget::drawCaret(bool erase) { x += getAbsX(); y += getAbsY(); - g_gui.theme()->drawCaret(Common::Rect(x, y, x + 1, y + editRect.height()), erase); + g_gui.theme()->drawCaretClip(Common::Rect(x, y, x + 1, y + editRect.height()), getBossClipRect(), erase); if (erase) { GUI::EditableWidget::String character; @@ -303,7 +318,7 @@ void EditableWidget::drawCaret(bool erase) { // possible glitches due to different methods used. width = MIN(editRect.width() - caretOffset, width); if (width > 0) { - g_gui.theme()->drawText(Common::Rect(x, y, x + width, y + editRect.height()), character, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); + g_gui.theme()->drawTextClip(Common::Rect(x, y, x + width, y + editRect.height()), getBossClipRect(), character, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); } } diff --git a/gui/widgets/edittext.cpp b/gui/widgets/edittext.cpp index 1481bebae3..0a8725ac9e 100644 --- a/gui/widgets/edittext.cpp +++ b/gui/widgets/edittext.cpp @@ -36,6 +36,8 @@ EditTextWidget::EditTextWidget(GuiObject *boss, int x, int y, int w, int h, cons setEditString(text); setFontStyle(ThemeEngine::kFontStyleNormal); + + _leftPadding = _rightPadding = 0; } EditTextWidget::EditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip, uint32 cmd, uint32 finishCmd) @@ -46,6 +48,8 @@ EditTextWidget::EditTextWidget(GuiObject *boss, const String &name, const String setEditString(text); setFontStyle(ThemeEngine::kFontStyleNormal); + + _leftPadding = _rightPadding = 0; } void EditTextWidget::setEditString(const String &str) { @@ -93,7 +97,7 @@ void EditTextWidget::handleMouseDown(int x, int y, int button, int clickCount) { } void EditTextWidget::drawWidget() { - g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x+_w, _y+_h), 0, ThemeEngine::kWidgetBackgroundEditText); + g_gui.theme()->drawWidgetBackgroundClip(Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), 0, ThemeEngine::kWidgetBackgroundEditText); // Draw the text adjustOffset(); @@ -101,7 +105,7 @@ void EditTextWidget::drawWidget() { const Common::Rect &r = Common::Rect(_x + 2 + _leftPadding, _y + 2, _x + _leftPadding + getEditRect().width() + 8, _y + _h); setTextDrawableArea(r); - g_gui.theme()->drawText(Common::Rect(_x + 2 + _leftPadding, _y + 2, _x + _leftPadding + getEditRect().width() + 2, _y + _h), _editString, _state, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, -_editScrollOffset, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); + g_gui.theme()->drawTextClip(Common::Rect(_x + 2 + _leftPadding, _y + 2, _x + _leftPadding + getEditRect().width() + 2, _y + _h), getBossClipRect(), _editString, _state, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, -_editScrollOffset, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); } Common::Rect EditTextWidget::getEditRect() const { diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp index 4b69202fdc..f6e5c67510 100644 --- a/gui/widgets/list.cpp +++ b/gui/widgets/list.cpp @@ -488,7 +488,7 @@ void ListWidget::drawWidget() { Common::String buffer; // Draw a thin frame around the list. - g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), 0, ThemeEngine::kWidgetBackgroundBorder); + g_gui.theme()->drawWidgetBackgroundClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), 0, ThemeEngine::kWidgetBackgroundBorder); const int scrollbarW = (_scrollBar && _scrollBar->isVisible()) ? _scrollBarWidth : 0; // Draw the list items @@ -507,7 +507,7 @@ void ListWidget::drawWidget() { // If in numbering mode, we first print a number prefix if (_numberingMode != kListNumberingOff) { buffer = Common::String::format("%2d. ", (pos + _numberingMode)); - g_gui.theme()->drawText(Common::Rect(_x, y, _x + r.left + _leftPadding, y + fontHeight - 2), + g_gui.theme()->drawTextClip(Common::Rect(_x, y, _x + r.left + _leftPadding, y + fontHeight - 2), getBossClipRect(), buffer, _state, Graphics::kTextAlignLeft, inverted, _leftPadding, true); pad = 0; } @@ -528,12 +528,12 @@ void ListWidget::drawWidget() { color = _editColor; adjustOffset(); width = _w - r.left - _hlRightPadding - _leftPadding - scrollbarW; - g_gui.theme()->drawText(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), buffer, _state, + g_gui.theme()->drawTextClip(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), getBossClipRect(), buffer, _state, Graphics::kTextAlignLeft, inverted, pad, true, ThemeEngine::kFontStyleBold, color); } else { buffer = _list[pos]; width = _w - r.left - scrollbarW; - g_gui.theme()->drawText(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), buffer, _state, + g_gui.theme()->drawTextClip(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), getBossClipRect(), buffer, _state, Graphics::kTextAlignLeft, inverted, pad, true, ThemeEngine::kFontStyleBold, color); } diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp index 6186492339..82f4112a97 100644 --- a/gui/widgets/popup.cpp +++ b/gui/widgets/popup.cpp @@ -71,6 +71,10 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) : Dialog(0, 0, 16, 16), _popUpBoss(boss) { + _openTime = 0; + _buffer = nullptr; + _entriesPerColumn = 1; + // Copy the selection index _selection = _popUpBoss->_selectedItem; @@ -142,8 +146,6 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) // Remember original mouse position _clickX = clickX - _x; _clickY = clickY - _y; - - _openTime = 0; } void PopUpDialog::drawDialog() { @@ -362,8 +364,11 @@ void PopUpDialog::drawMenuEntry(int entry, bool hilite) { // Draw a separator g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x+w, y+kLineHeight)); } else { - g_gui.theme()->drawText(Common::Rect(x+1, y+2, x+w, y+2+kLineHeight), name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled, - Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding); + g_gui.theme()->drawText( + Common::Rect(x+1, y+2, x+w, y+2+kLineHeight), + name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled, + Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding + ); } } @@ -380,6 +385,17 @@ PopUpWidget::PopUpWidget(GuiObject *boss, const String &name, const char *toolti _type = kPopUpWidget; _selectedItem = -1; + _leftPadding = _rightPadding = 0; +} + +PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip) + : Widget(boss, x, y, w, h, tooltip), CommandSender(boss) { + setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG); + _type = kPopUpWidget; + + _selectedItem = -1; + + _leftPadding = _rightPadding = 0; } void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) { @@ -457,7 +473,10 @@ void PopUpWidget::drawWidget() { Common::String sel; if (_selectedItem >= 0) sel = _entries[_selectedItem].name; - g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, _leftPadding, _state, Graphics::kTextAlignLeft); + g_gui.theme()->drawPopUpWidgetClip( + Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), + sel, _leftPadding, _state, Graphics::kTextAlignLeft + ); } } // End of namespace GUI diff --git a/gui/widgets/popup.h b/gui/widgets/popup.h index 102c7fd258..37ddc276ad 100644 --- a/gui/widgets/popup.h +++ b/gui/widgets/popup.h @@ -58,6 +58,7 @@ protected: public: PopUpWidget(GuiObject *boss, const String &name, const char *tooltip = 0); + PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip = 0); void handleMouseDown(int x, int y, int button, int clickCount); void handleMouseWheel(int x, int y, int direction); diff --git a/gui/widgets/scrollbar.cpp b/gui/widgets/scrollbar.cpp index f1306b9c4a..d8bcb18336 100644 --- a/gui/widgets/scrollbar.cpp +++ b/gui/widgets/scrollbar.cpp @@ -26,6 +26,7 @@ #include "gui/widgets/scrollbar.h" #include "gui/gui-manager.h" #include "gui/ThemeEngine.h" +#include "gui/widgets/scrollcontainer.h" namespace GUI { @@ -202,7 +203,11 @@ void ScrollBarWidget::drawWidget() { state = ThemeEngine::kScrollbarStateSlider; } - g_gui.theme()->drawScrollbar(Common::Rect(_x, _y, _x+_w, _y+_h), _sliderPos, _sliderHeight, state, _state); + Common::Rect clipRect = getBossClipRect(); + //scrollbar is not a usual child of ScrollContainerWidget, so it gets this special treatment + if (dynamic_cast<ScrollContainerWidget *>(_boss)) + clipRect.right += _w; + g_gui.theme()->drawScrollbarClip(Common::Rect(_x, _y, _x+_w, _y+_h), clipRect, _sliderPos, _sliderHeight, state, _state); } } // End of namespace GUI diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp new file mode 100644 index 0000000000..9a7792730d --- /dev/null +++ b/gui/widgets/scrollcontainer.cpp @@ -0,0 +1,153 @@ +/* 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 "common/util.h" +#include "gui/widgets/scrollcontainer.h" +#include "gui/gui-manager.h" + +#include "gui/ThemeEval.h" + +namespace GUI { + +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, uint32 reflowCmd) + : Widget(boss, name), CommandSender(nullptr), _reflowCmd(reflowCmd) { + init(); +} + +void ScrollContainerWidget::init() { + setFlags(WIDGET_ENABLED); + _type = kScrollContainerWidget; + _verticalScroll = new ScrollBarWidget(this, _w-16, 0, 16, _h); + _verticalScroll->setTarget(this); + _scrolledX = 0; + _scrolledY = 0; + _limitH = 140; + recalc(); +} + +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 && ptr->isVisible()) { + int y = ptr->getAbsY() - getChildY(); + min = MIN(min, y - spacing); + max = MAX(max, y + ptr->getHeight() + spacing); + } + ptr = ptr->next(); + } + h = max - min; + + if (h <= _limitH) _scrolledY = 0; + + _verticalScroll->_numEntries = h; + _verticalScroll->_currentPos = _scrolledY; + _verticalScroll->_entriesPerPage = _limitH; + _verticalScroll->setPos(_w - scrollbarWidth, _scrolledY+1); + _verticalScroll->setSize(scrollbarWidth, _limitH -2); +} + + +ScrollContainerWidget::~ScrollContainerWidget() {} + +int16 ScrollContainerWidget::getChildX() const { + return getAbsX() - _scrolledX; +} + +int16 ScrollContainerWidget::getChildY() const { + return getAbsY() - _scrolledY; +} + +uint16 ScrollContainerWidget::getWidth() const { + return _w - (_verticalScroll->isVisible() ? _verticalScroll->getWidth() : 0); +} + +uint16 ScrollContainerWidget::getHeight() const { + return _limitH; +} + +void ScrollContainerWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + Widget::handleCommand(sender, cmd, data); + switch (cmd) { + case kSetPositionCmd: + _scrolledY = _verticalScroll->_currentPos; + reflowLayout(); + draw(); + g_gui.doFullRedraw(); + break; + } +} + +void ScrollContainerWidget::reflowLayout() { + Widget::reflowLayout(); + + //reflow layout of inner widgets + Widget *ptr = _firstWidget; + while (ptr) { + ptr->reflowLayout(); + ptr = ptr->next(); + } + + //hide and move widgets, if needed + sendCommand(_reflowCmd, 0); + + //recalculate height + recalc(); + + //hide those widgets which are out of visible area + ptr = _firstWidget; + while (ptr) { + int y = ptr->getAbsY() - getChildY(); + int h = ptr->getHeight(); + bool visible = ptr->isVisible(); + if (y + h - _scrolledY < 0) visible = false; + if (y - _scrolledY > _limitH) visible = false; + ptr->setVisible(visible); + ptr = ptr->next(); + } + + _verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll + _verticalScroll->recalc(); +} + +void ScrollContainerWidget::drawWidget() { + g_gui.theme()->drawDialogBackgroundClip(Common::Rect(_x, _y, _x + _w, _y + getHeight() - 1), getBossClipRect(), ThemeEngine::kDialogBackgroundDefault); +} + +Widget *ScrollContainerWidget::findWidget(int x, int y) { + if (_verticalScroll->isVisible() && x >= _w - _verticalScroll->getWidth()) + return _verticalScroll; + return Widget::findWidgetInChain(_firstWidget, x + _scrolledX, y + _scrolledY); +} + +} // End of namespace GUI diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h new file mode 100644 index 0000000000..c2d47370ee --- /dev/null +++ b/gui/widgets/scrollcontainer.h @@ -0,0 +1,64 @@ +/* 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_WIDGETS_SCROLLCONTAINER_H +#define GUI_WIDGETS_SCROLLCONTAINER_H + +#include "gui/widget.h" +#include "common/str.h" +#include "scrollbar.h" + +namespace GUI { + +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, uint32 reflowCmd = 0); + ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd = 0); + ~ScrollContainerWidget(); + + void init(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + virtual void reflowLayout(); + +protected: + // We overload getChildY to make sure child widgets are positioned correctly. + // Essentially this compensates for the space taken up by the tab title header. + virtual int16 getChildX() const; + virtual int16 getChildY() const; + virtual uint16 getWidth() const; + virtual uint16 getHeight() const; + + virtual void drawWidget(); + + virtual Widget *findWidget(int x, int y); +}; + +} // End of namespace GUI + +#endif diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp index 756781a04b..cf9dd5d962 100644 --- a/gui/widgets/tab.cpp +++ b/gui/widgets/tab.cpp @@ -70,6 +70,12 @@ void TabWidget::init() { } TabWidget::~TabWidget() { + // If widgets were added or removed in the current tab, without tabs + // having been switched using setActiveTab() afterward, then the + // firstWidget in the _tabs list for the active tab may not be up to + // date. So update it now. + if (_activeTab != -1) + _tabs[_activeTab].firstWidget = _firstWidget; _firstWidget = 0; for (uint i = 0; i < _tabs.size(); ++i) { delete _tabs[i].firstWidget; @@ -80,9 +86,19 @@ TabWidget::~TabWidget() { } int16 TabWidget::getChildY() const { + // NOTE: if you change that, make sure to do the same + // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp) return getAbsY() + _tabHeight; } +uint16 TabWidget::getHeight() const { + // NOTE: if you change that, make sure to do the same + // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp) + // NOTE: this height is used for clipping, so it *includes* + // tabs, because it starts from getAbsY(), not getChildY() + return _h + _tabHeight; +} + int TabWidget::addTab(const String &title) { // Add a new tab page Tab newTab; @@ -183,6 +199,12 @@ void TabWidget::setActiveTab(int tabID) { } _activeTab = tabID; _firstWidget = _tabs[tabID].firstWidget; + + // Also ensure the tab is visible in the tab bar + if (_firstVisibleTab > tabID) + _firstVisibleTab = tabID; + else if (_firstVisibleTab + _w / _tabWidth <= tabID) + _firstVisibleTab = tabID - _w / _tabWidth + 1; _boss->draw(); } @@ -258,6 +280,19 @@ void TabWidget::adjustTabs(int value) { void TabWidget::reflowLayout() { Widget::reflowLayout(); + // NOTE: if you change that, make sure to do the same + // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp) + _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height"); + _tabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width"); + _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top"); + + // If widgets were added or removed in the current tab, without tabs + // having been switched using setActiveTab() afterward, then the + // firstWidget in the _tabs list for the active tab may not be up to + // date. So update it now. + if (_activeTab != -1) + _tabs[_activeTab].firstWidget = _firstWidget; + for (uint i = 0; i < _tabs.size(); ++i) { Widget *w = _tabs[i].firstWidget; while (w) { @@ -266,10 +301,6 @@ void TabWidget::reflowLayout() { } } - _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height"); - _tabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width"); - _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top"); - if (_tabWidth == 0) { _tabWidth = 40; #ifdef __DS__ @@ -304,9 +335,9 @@ void TabWidget::drawWidget() { for (int i = _firstVisibleTab; i < (int)_tabs.size(); ++i) { tabs.push_back(_tabs[i].title); } - g_gui.theme()->drawDialogBackground(Common::Rect(_x + _bodyLP, _y + _bodyTP, _x+_w-_bodyRP, _y+_h-_bodyBP), _bodyBackgroundType); + g_gui.theme()->drawDialogBackgroundClip(Common::Rect(_x + _bodyLP, _y + _bodyTP, _x+_w-_bodyRP, _y+_h-_bodyBP+_tabHeight), getBossClipRect(), _bodyBackgroundType); - g_gui.theme()->drawTab(Common::Rect(_x, _y, _x+_w, _y+_h), _tabHeight, _tabWidth, tabs, _activeTab - _firstVisibleTab, 0, _titleVPad); + g_gui.theme()->drawTabClip(Common::Rect(_x, _y, _x+_w, _y+_h), getBossClipRect(), _tabHeight, _tabWidth, tabs, _activeTab - _firstVisibleTab, 0, _titleVPad); } void TabWidget::draw() { diff --git a/gui/widgets/tab.h b/gui/widgets/tab.h index 148f164fbb..17b85986b5 100644 --- a/gui/widgets/tab.h +++ b/gui/widgets/tab.h @@ -110,6 +110,7 @@ protected: // We overload getChildY to make sure child widgets are positioned correctly. // Essentially this compensates for the space taken up by the tab title header. virtual int16 getChildY() const; + virtual uint16 getHeight() const; virtual void drawWidget(); |