ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

ThemeEngine.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "common/system.h"
00024 #include "common/config-manager.h"
00025 #include "common/file.h"
00026 #include "common/fs.h"
00027 #include "common/unzip.h"
00028 #include "common/tokenizer.h"
00029 #include "common/translation.h"
00030 
00031 #include "graphics/cursorman.h"
00032 #include "graphics/fontman.h"
00033 #include "graphics/surface.h"
00034 #include "graphics/transparent_surface.h"
00035 #include "graphics/VectorRenderer.h"
00036 #include "graphics/fonts/bdf.h"
00037 #include "graphics/fonts/ttf.h"
00038 
00039 #include "image/bmp.h"
00040 #include "image/png.h"
00041 
00042 #include "gui/widget.h"
00043 #include "gui/ThemeEngine.h"
00044 #include "gui/ThemeEval.h"
00045 #include "gui/ThemeParser.h"
00046 
00047 namespace GUI {
00048 
00049 const char *const ThemeEngine::kImageLogo = "logo.bmp";
00050 const char *const ThemeEngine::kImageLogoSmall = "logo_small.bmp";
00051 const char *const ThemeEngine::kImageSearch = "search.bmp";
00052 const char *const ThemeEngine::kImageEraser = "eraser.bmp";
00053 const char *const ThemeEngine::kImageDelButton = "delbtn.bmp";
00054 const char *const ThemeEngine::kImageList = "list.bmp";
00055 const char *const ThemeEngine::kImageGrid = "grid.bmp";
00056 const char *const ThemeEngine::kImageStopButton = "stopbtn.bmp";
00057 const char *const ThemeEngine::kImageEditButton = "editbtn.bmp";
00058 const char *const ThemeEngine::kImageSwitchModeButton = "switchbtn.bmp";
00059 const char *const ThemeEngine::kImageFastReplayButton = "fastreplay.bmp";
00060 const char *const ThemeEngine::kImageStopSmallButton = "stopbtn_small.bmp";
00061 const char *const ThemeEngine::kImageEditSmallButton = "editbtn_small.bmp";
00062 const char *const ThemeEngine::kImageSwitchModeSmallButton = "switchbtn_small.bmp";
00063 const char *const ThemeEngine::kImageFastReplaySmallButton = "fastreplay_small.bmp";
00064 const char *const ThemeEngine::kImageDropboxLogo = "dropbox.bmp";
00065 const char *const ThemeEngine::kImageOneDriveLogo = "onedrive.bmp";
00066 const char *const ThemeEngine::kImageGoogleDriveLogo = "googledrive.bmp";
00067 const char *const ThemeEngine::kImageBoxLogo = "box.bmp";
00068 
00069 struct TextDrawData {
00070     const Graphics::Font *_fontPtr;
00071 };
00072 
00073 struct TextColorData {
00074     int r, g, b;
00075 };
00076 
00077 struct WidgetDrawData {
00079     Common::List<Graphics::DrawStep> _steps;
00080 
00081     TextData _textDataId;
00082     TextColor _textColorId;
00083     Graphics::TextAlign _textAlignH;
00084     GUI::ThemeEngine::TextAlignVertical _textAlignV;
00085 
00089     uint16 _backgroundOffset;
00090     uint16 _shadowOffset;
00091 
00092     DrawLayer _layer;
00093 
00094 
00102     void calcBackgroundOffset();
00103 };
00104 
00105 /**********************************************************
00106  *  Data definitions for theme engine elements
00107  *********************************************************/
00108 struct DrawDataInfo {
00109     DrawData id;        
00110     const char *name;   
00111     DrawLayer layer;    
00112     DrawData parent;    
00113 };
00114 
00118 static const DrawDataInfo kDrawDataDefaults[] = {
00119     {kDDMainDialogBackground,         "mainmenu_bg",          kDrawLayerBackground,   kDDNone},
00120     {kDDSpecialColorBackground,       "special_bg",           kDrawLayerBackground,   kDDNone},
00121     {kDDPlainColorBackground,         "plain_bg",             kDrawLayerBackground,   kDDNone},
00122     {kDDTooltipBackground,            "tooltip_bg",           kDrawLayerBackground,   kDDNone},
00123     {kDDDefaultBackground,            "default_bg",           kDrawLayerBackground,   kDDNone},
00124     {kDDTextSelectionBackground,      "text_selection",       kDrawLayerForeground,  kDDNone},
00125     {kDDTextSelectionFocusBackground, "text_selection_focus", kDrawLayerForeground,  kDDNone},
00126 
00127     {kDDWidgetBackgroundDefault,    "widget_default",   kDrawLayerBackground,   kDDNone},
00128     {kDDWidgetBackgroundSmall,      "widget_small",     kDrawLayerBackground,   kDDNone},
00129     {kDDWidgetBackgroundEditText,   "widget_textedit",  kDrawLayerBackground,   kDDNone},
00130     {kDDWidgetBackgroundSlider,     "widget_slider",    kDrawLayerBackground,   kDDNone},
00131 
00132     {kDDButtonIdle,                 "button_idle",      kDrawLayerBackground,   kDDNone},
00133     {kDDButtonHover,                "button_hover",     kDrawLayerForeground,  kDDButtonIdle},
00134     {kDDButtonDisabled,             "button_disabled",  kDrawLayerBackground,   kDDNone},
00135     {kDDButtonPressed,              "button_pressed",   kDrawLayerForeground,  kDDButtonIdle},
00136 
00137     {kDDSliderFull,                 "slider_full",      kDrawLayerForeground,  kDDNone},
00138     {kDDSliderHover,                "slider_hover",     kDrawLayerForeground,  kDDNone},
00139     {kDDSliderDisabled,             "slider_disabled",  kDrawLayerForeground,  kDDNone},
00140 
00141     {kDDCheckboxDefault,            "checkbox_default",         kDrawLayerBackground,   kDDNone},
00142     {kDDCheckboxDisabled,           "checkbox_disabled",        kDrawLayerBackground,   kDDNone},
00143     {kDDCheckboxSelected,           "checkbox_selected",        kDrawLayerForeground,  kDDCheckboxDefault},
00144 
00145     {kDDRadiobuttonDefault,         "radiobutton_default",      kDrawLayerBackground,   kDDNone},
00146     {kDDRadiobuttonDisabled,        "radiobutton_disabled",     kDrawLayerBackground,   kDDNone},
00147     {kDDRadiobuttonSelected,        "radiobutton_selected",     kDrawLayerForeground,  kDDRadiobuttonDefault},
00148 
00149     {kDDTabActive,                  "tab_active",               kDrawLayerForeground,  kDDTabInactive},
00150     {kDDTabInactive,                "tab_inactive",             kDrawLayerBackground,   kDDNone},
00151     {kDDTabBackground,              "tab_background",           kDrawLayerBackground,   kDDNone},
00152 
00153     {kDDScrollbarBase,              "scrollbar_base",           kDrawLayerBackground,   kDDNone},
00154 
00155     {kDDScrollbarButtonIdle,        "scrollbar_button_idle",    kDrawLayerBackground,   kDDNone},
00156     {kDDScrollbarButtonHover,       "scrollbar_button_hover",   kDrawLayerForeground,  kDDScrollbarButtonIdle},
00157 
00158     {kDDScrollbarHandleIdle,        "scrollbar_handle_idle",    kDrawLayerForeground,  kDDNone},
00159     {kDDScrollbarHandleHover,       "scrollbar_handle_hover",   kDrawLayerForeground,  kDDScrollbarBase},
00160 
00161     {kDDPopUpIdle,                  "popup_idle",       kDrawLayerBackground,   kDDNone},
00162     {kDDPopUpHover,                 "popup_hover",      kDrawLayerForeground,  kDDPopUpIdle},
00163     {kDDPopUpDisabled,              "popup_disabled",   kDrawLayerBackground,   kDDNone},
00164 
00165     {kDDCaret,                      "caret",        kDrawLayerForeground,  kDDNone},
00166     {kDDSeparator,                  "separator",    kDrawLayerBackground,   kDDNone},
00167 };
00168 
00169 /**********************************************************
00170  * ThemeEngine class
00171  *********************************************************/
00172 ThemeEngine::ThemeEngine(Common::String id, GraphicsMode mode) :
00173     _system(0), _vectorRenderer(0),
00174     _layerToDraw(kDrawLayerBackground), _bytesPerPixel(0),  _graphicsMode(kGfxDisabled),
00175     _font(0), _initOk(false), _themeOk(false), _enabled(false), _themeFiles(),
00176     _cursor(0) {
00177 
00178     _system = g_system;
00179     _parser = new ThemeParser(this);
00180     _themeEval = new GUI::ThemeEval();
00181 
00182     _useCursor = false;
00183 
00184     for (int i = 0; i < kDrawDataMAX; ++i) {
00185         _widgets[i] = 0;
00186     }
00187 
00188     for (int i = 0; i < kTextDataMAX; ++i) {
00189         _texts[i] = 0;
00190     }
00191 
00192     for (int i = 0; i < kTextColorMAX; ++i) {
00193         _textColors[i] = 0;
00194     }
00195 
00196     // We currently allow two different ways of theme selection in our config file:
00197     // 1) Via full path
00198     // 2) Via a basename, which will need to be translated into a full path
00199     // This function assures we have a correct path to pass to the ThemeEngine
00200     // constructor.
00201     _themeFile = getThemeFile(id);
00202     // We will use getThemeId to retrive the theme id from the given filename
00203     // here, since the user could have passed a fixed filename as 'id'.
00204     _themeId = getThemeId(_themeFile);
00205 
00206     _graphicsMode = mode;
00207     _themeArchive = 0;
00208     _initOk = false;
00209 
00210     _cursorHotspotX = _cursorHotspotY = 0;
00211     _cursorWidth = _cursorHeight = 0;
00212     _cursorPalSize = 0;
00213 
00214     // We prefer files in archive bundles over the common search paths.
00215     _themeFiles.add("default", &SearchMan, 0, false);
00216 }
00217 
00218 ThemeEngine::~ThemeEngine() {
00219     delete _vectorRenderer;
00220     _vectorRenderer = 0;
00221     _screen.free();
00222     _backBuffer.free();
00223 
00224     unloadTheme();
00225 
00226     // Release all graphics surfaces
00227     for (ImagesMap::iterator i = _bitmaps.begin(); i != _bitmaps.end(); ++i) {
00228         Graphics::Surface *surf = i->_value;
00229         if (surf) {
00230             surf->free();
00231             delete surf;
00232         }
00233     }
00234     _bitmaps.clear();
00235 
00236     for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
00237         Graphics::TransparentSurface *surf = i->_value;
00238         if (surf) {
00239             surf->free();
00240             delete surf;
00241         }
00242     }
00243     _abitmaps.clear();
00244 
00245     delete _parser;
00246     delete _themeEval;
00247     delete[] _cursor;
00248 }
00249 
00250 
00251 
00252 /**********************************************************
00253  * Rendering mode management
00254  *********************************************************/
00255 const ThemeEngine::Renderer ThemeEngine::_rendererModes[] = {
00256     { _s("Disabled GFX"), _sc("Disabled GFX", "lowres"), "none", kGfxDisabled },
00257     { _s("Standard renderer"), _s("Standard"), "normal", kGfxStandard },
00258 #ifndef DISABLE_FANCY_THEMES
00259     { _s("Antialiased renderer"), _s("Antialiased"), "antialias", kGfxAntialias }
00260 #endif
00261 };
00262 
00263 const uint ThemeEngine::_rendererModesSize = ARRAYSIZE(ThemeEngine::_rendererModes);
00264 
00265 const ThemeEngine::GraphicsMode ThemeEngine::_defaultRendererMode =
00266 #ifndef DISABLE_FANCY_THEMES
00267     ThemeEngine::kGfxAntialias;
00268 #else
00269     ThemeEngine::kGfxStandard;
00270 #endif
00271 
00272 ThemeEngine::GraphicsMode ThemeEngine::findMode(const Common::String &cfg) {
00273     for (uint i = 0; i < _rendererModesSize; ++i) {
00274         if (cfg.equalsIgnoreCase(_rendererModes[i].cfg))
00275             return _rendererModes[i].mode;
00276     }
00277 
00278     return kGfxDisabled;
00279 }
00280 
00281 const char *ThemeEngine::findModeConfigName(GraphicsMode mode) {
00282     for (uint i = 0; i < _rendererModesSize; ++i) {
00283         if (mode == _rendererModes[i].mode)
00284             return _rendererModes[i].cfg;
00285     }
00286 
00287     return findModeConfigName(kGfxDisabled);
00288 }
00289 
00290 
00291 
00292 
00293 
00294 /**********************************************************
00295  * Theme setup/initialization
00296  *********************************************************/
00297 bool ThemeEngine::init() {
00298     // reset everything and reload the graphics
00299     _initOk = false;
00300     _overlayFormat = _system->getOverlayFormat();
00301     setGraphicsMode(_graphicsMode);
00302 
00303     if (_screen.getPixels() && _backBuffer.getPixels()) {
00304         _initOk = true;
00305     }
00306 
00307     // TODO: Instead of hard coding the font here, it should be possible
00308     // to specify the fonts to be used for each resolution in the theme XML.
00309     if (_screen.w >= 400 && _screen.h >= 300) {
00310         _font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
00311     } else {
00312         _font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
00313     }
00314 
00315     // Try to create a Common::Archive with the files of the theme.
00316     if (!_themeArchive && !_themeFile.empty()) {
00317         Common::FSNode node(_themeFile);
00318         if (node.isDirectory()) {
00319             _themeArchive = new Common::FSDirectory(node);
00320         } else if (_themeFile.matchString("*.zip", true)) {
00321             // TODO: Also use "node" directly?
00322             // Look for the zip file via SearchMan
00323             Common::ArchiveMemberPtr member = SearchMan.getMember(_themeFile);
00324             if (member) {
00325                 _themeArchive = Common::makeZipArchive(member->createReadStream());
00326                 if (!_themeArchive) {
00327                     warning("Failed to open Zip archive '%s'.", member->getDisplayName().c_str());
00328                 }
00329             } else {
00330                 _themeArchive = Common::makeZipArchive(node);
00331                 if (!_themeArchive) {
00332                     warning("Failed to open Zip archive '%s'.", node.getPath().c_str());
00333                 }
00334             }
00335         }
00336 
00337         if (_themeArchive)
00338             _themeFiles.add("theme_archive", _themeArchive, 1, true);
00339     }
00340 
00341     // Load the theme
00342     // We pass the theme file here by default, so the user will
00343     // have a descriptive error message. The only exception will
00344     // be the builtin theme which has no filename.
00345     loadTheme(_themeFile.empty() ? _themeId : _themeFile);
00346 
00347     return ready();
00348 }
00349 
00350 void ThemeEngine::clearAll() {
00351     if (_initOk) {
00352         _system->clearOverlay();
00353         _system->grabOverlay(_backBuffer.getPixels(), _backBuffer.pitch);
00354     }
00355 }
00356 
00357 void ThemeEngine::refresh() {
00358 
00359     // Flush all bitmaps if the overlay pixel format changed.
00360     if (_overlayFormat != _system->getOverlayFormat()) {
00361         for (ImagesMap::iterator i = _bitmaps.begin(); i != _bitmaps.end(); ++i) {
00362             Graphics::Surface *surf = i->_value;
00363             if (surf) {
00364                 surf->free();
00365                 delete surf;
00366             }
00367         }
00368         _bitmaps.clear();
00369 
00370         for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
00371             Graphics::TransparentSurface *surf = i->_value;
00372             if (surf) {
00373                 surf->free();
00374                 delete surf;
00375             }
00376         }
00377         _abitmaps.clear();
00378     }
00379 
00380     init();
00381 
00382     if (_enabled) {
00383         _system->showOverlay();
00384 
00385         if (_useCursor) {
00386             CursorMan.replaceCursorPalette(_cursorPal, 0, _cursorPalSize);
00387             CursorMan.replaceCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
00388         }
00389     }
00390 }
00391 
00392 void ThemeEngine::enable() {
00393     if (_enabled)
00394         return;
00395 
00396     showCursor();
00397 
00398     _system->showOverlay();
00399     clearAll();
00400     _enabled = true;
00401 }
00402 
00403 void ThemeEngine::disable() {
00404     if (!_enabled)
00405         return;
00406 
00407     _system->hideOverlay();
00408 
00409     hideCursor();
00410 
00411 
00412     _enabled = false;
00413 }
00414 
00415 void ThemeEngine::setGraphicsMode(GraphicsMode mode) {
00416     switch (mode) {
00417     case kGfxStandard:
00418 #ifndef DISABLE_FANCY_THEMES
00419     case kGfxAntialias:
00420 #endif
00421         if (g_system->getOverlayFormat().bytesPerPixel == 4) {
00422             _bytesPerPixel = sizeof(uint32);
00423             break;
00424         } else if (g_system->getOverlayFormat().bytesPerPixel == 2) {
00425             _bytesPerPixel = sizeof(uint16);
00426             break;
00427         }
00428         // fall through
00429     default:
00430         error("Invalid graphics mode");
00431     }
00432 
00433     uint32 width = _system->getOverlayWidth();
00434     uint32 height = _system->getOverlayHeight();
00435 
00436     _backBuffer.free();
00437     _backBuffer.create(width, height, _overlayFormat);
00438 
00439     _screen.free();
00440     _screen.create(width, height, _overlayFormat);
00441 
00442     delete _vectorRenderer;
00443     _vectorRenderer = Graphics::createRenderer(mode);
00444     _vectorRenderer->setSurface(&_screen);
00445 
00446     // Since we reinitialized our screen surfaces we know nothing has been
00447     // drawn so far. Sometimes we still end up with dirty screen bits in the
00448     // list. Clearing it avoids invalid overlay writes when the backend
00449     // resizes the overlay.
00450     _dirtyScreen.clear();
00451 }
00452 
00453 void WidgetDrawData::calcBackgroundOffset() {
00454     uint maxShadow = 0, maxBevel = 0;
00455     for (Common::List<Graphics::DrawStep>::const_iterator step = _steps.begin();
00456             step != _steps.end(); ++step) {
00457         if ((step->autoWidth || step->autoHeight) && step->shadow > maxShadow)
00458             maxShadow = step->shadow;
00459 
00460         if (step->drawingCall == &Graphics::VectorRenderer::drawCallback_BEVELSQ && step->bevel > maxBevel)
00461             maxBevel = step->bevel;
00462     }
00463 
00464     _backgroundOffset = maxBevel;
00465     _shadowOffset = maxShadow;
00466 }
00467 
00468 void ThemeEngine::restoreBackground(Common::Rect r) {
00469     if (_vectorRenderer->getActiveSurface() == &_backBuffer) {
00470         // Only restore the background when drawing to the screen surface
00471         return;
00472     }
00473 
00474     r.clip(_screen.w, _screen.h);
00475     _vectorRenderer->blitSurface(&_backBuffer, r);
00476 
00477     addDirtyRect(r);
00478 }
00479 
00480 
00481 
00482 /**********************************************************
00483  * Theme elements management
00484  *********************************************************/
00485 void ThemeEngine::addDrawStep(const Common::String &drawDataId, const Graphics::DrawStep &step) {
00486     DrawData id = parseDrawDataId(drawDataId);
00487 
00488     assert(id != kDDNone && _widgets[id] != 0);
00489     _widgets[id]->_steps.push_back(step);
00490 }
00491 
00492 bool ThemeEngine::addTextData(const Common::String &drawDataId, TextData textId, TextColor colorId, Graphics::TextAlign alignH, TextAlignVertical alignV) {
00493     DrawData id = parseDrawDataId(drawDataId);
00494 
00495     if (id == -1 || textId == -1 || colorId == kTextColorMAX || !_widgets[id])
00496         return false;
00497 
00498     _widgets[id]->_textDataId = textId;
00499     _widgets[id]->_textColorId = colorId;
00500     _widgets[id]->_textAlignH = alignH;
00501     _widgets[id]->_textAlignV = alignV;
00502 
00503     return true;
00504 }
00505 
00506 bool ThemeEngine::addFont(TextData textId, const Common::String &file, const Common::String &scalableFile, const int pointsize) {
00507     if (textId == -1)
00508         return false;
00509 
00510     if (_texts[textId] != 0)
00511         delete _texts[textId];
00512 
00513     _texts[textId] = new TextDrawData;
00514 
00515     if (file == "default") {
00516         _texts[textId]->_fontPtr = _font;
00517     } else {
00518         Common::String localized = FontMan.genLocalizedFontFilename(file);
00519         const Common::String charset
00520 #ifdef USE_TRANSLATION
00521                                     (TransMan.getCurrentCharset())
00522 #endif
00523                                     ;
00524 
00525         // Try localized fonts
00526         _texts[textId]->_fontPtr = loadFont(localized, scalableFile, charset, pointsize, textId == kTextDataDefault);
00527 
00528         if (!_texts[textId]->_fontPtr) {
00529             // Try standard fonts
00530             _texts[textId]->_fontPtr = loadFont(file, scalableFile, Common::String(), pointsize, textId == kTextDataDefault);
00531 
00532             if (!_texts[textId]->_fontPtr)
00533                 error("Couldn't load font '%s'/'%s'", file.c_str(), scalableFile.c_str());
00534 
00535 #ifdef USE_TRANSLATION
00536             TransMan.setLanguage("C");
00537 #endif
00538             warning("Failed to load localized font '%s'.", localized.c_str());
00539 
00540             return false;
00541         }
00542     }
00543 
00544     return true;
00545 
00546 }
00547 
00548 bool ThemeEngine::addTextColor(TextColor colorId, int r, int g, int b) {
00549     if (colorId >= kTextColorMAX)
00550         return false;
00551 
00552     if (_textColors[colorId] != 0)
00553         delete _textColors[colorId];
00554 
00555     _textColors[colorId] = new TextColorData;
00556 
00557     _textColors[colorId]->r = r;
00558     _textColors[colorId]->g = g;
00559     _textColors[colorId]->b = b;
00560 
00561     return true;
00562 }
00563 
00564 bool ThemeEngine::addBitmap(const Common::String &filename) {
00565     // Nothing has to be done if the bitmap already has been loaded.
00566     Graphics::Surface *surf = _bitmaps[filename];
00567     if (surf)
00568         return true;
00569 
00570     const Graphics::Surface *srcSurface = 0;
00571 
00572     if (filename.hasSuffix(".png")) {
00573         // Maybe it is PNG?
00574 #ifdef USE_PNG
00575         Image::PNGDecoder decoder;
00576         Common::ArchiveMemberList members;
00577         _themeFiles.listMatchingMembers(members, filename);
00578         for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
00579             Common::SeekableReadStream *stream = (*i)->createReadStream();
00580             if (stream) {
00581                 if (!decoder.loadStream(*stream))
00582                     error("Error decoding PNG");
00583 
00584                 srcSurface = decoder.getSurface();
00585                 delete stream;
00586                 if (srcSurface)
00587                     break;
00588             }
00589         }
00590 
00591         if (srcSurface && srcSurface->format.bytesPerPixel != 1)
00592             surf = srcSurface->convertTo(_overlayFormat);
00593 #else
00594         error("No PNG support compiled in");
00595 #endif
00596     } else {
00597         // If not, try to load the bitmap via the BitmapDecoder class.
00598         Image::BitmapDecoder bitmapDecoder;
00599         Common::ArchiveMemberList members;
00600         _themeFiles.listMatchingMembers(members, filename);
00601         for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
00602             Common::SeekableReadStream *stream = (*i)->createReadStream();
00603             if (stream) {
00604                 bitmapDecoder.loadStream(*stream);
00605                 srcSurface = bitmapDecoder.getSurface();
00606                 delete stream;
00607                 if (srcSurface)
00608                     break;
00609             }
00610         }
00611 
00612         if (srcSurface && srcSurface->format.bytesPerPixel != 1)
00613             surf = srcSurface->convertTo(_overlayFormat);
00614     }
00615 
00616     // Store the surface into our hashmap (attention, may store NULL entries!)
00617     _bitmaps[filename] = surf;
00618 
00619     return surf != 0;
00620 }
00621 
00622 bool ThemeEngine::addAlphaBitmap(const Common::String &filename) {
00623     // Nothing has to be done if the bitmap already has been loaded.
00624     Graphics::TransparentSurface *surf = _abitmaps[filename];
00625     if (surf)
00626         return true;
00627 
00628 #ifdef USE_PNG
00629     const Graphics::TransparentSurface *srcSurface = 0;
00630 #endif
00631 
00632     if (filename.hasSuffix(".png")) {
00633         // Maybe it is PNG?
00634 #ifdef USE_PNG
00635         Image::PNGDecoder decoder;
00636         Common::ArchiveMemberList members;
00637         _themeFiles.listMatchingMembers(members, filename);
00638         for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
00639             Common::SeekableReadStream *stream = (*i)->createReadStream();
00640             if (stream) {
00641                 if (!decoder.loadStream(*stream))
00642                     error("Error decoding PNG");
00643 
00644                 srcSurface = new Graphics::TransparentSurface(*decoder.getSurface(), true);
00645                 delete stream;
00646                 if (srcSurface)
00647                     break;
00648             }
00649         }
00650 
00651         if (srcSurface && srcSurface->format.bytesPerPixel != 1)
00652             surf = srcSurface->convertTo(_overlayFormat);
00653 #else
00654         error("No PNG support compiled in");
00655 #endif
00656     } else {
00657         error("Only PNG is supported as alphabitmap");
00658     }
00659 
00660     // Store the surface into our hashmap (attention, may store NULL entries!)
00661     _abitmaps[filename] = surf;
00662 
00663     return surf != 0;
00664 }
00665 
00666 bool ThemeEngine::addDrawData(const Common::String &data, bool cached) {
00667     DrawData id = parseDrawDataId(data);
00668 
00669     if (id == -1)
00670         return false;
00671 
00672     if (_widgets[id] != 0)
00673         delete _widgets[id];
00674 
00675     _widgets[id] = new WidgetDrawData;
00676     _widgets[id]->_layer = kDrawDataDefaults[id].layer;
00677     _widgets[id]->_textDataId = kTextDataNone;
00678 
00679     return true;
00680 }
00681 
00682 
00683 /**********************************************************
00684  * Theme XML loading
00685  *********************************************************/
00686 void ThemeEngine::loadTheme(const Common::String &themeId) {
00687     unloadTheme();
00688 
00689     debug(6, "Loading theme %s", themeId.c_str());
00690 
00691     if (themeId == "builtin") {
00692         _themeOk = loadDefaultXML();
00693     } else {
00694         // Load the archive containing image and XML data
00695         _themeOk = loadThemeXML(themeId);
00696     }
00697 
00698     if (!_themeOk) {
00699         warning("Failed to load theme '%s'", themeId.c_str());
00700         return;
00701     }
00702 
00703     for (int i = 0; i < kDrawDataMAX; ++i) {
00704         if (_widgets[i] == 0) {
00705             warning("Missing data asset: '%s'", kDrawDataDefaults[i].name);
00706         } else {
00707             _widgets[i]->calcBackgroundOffset();
00708         }
00709     }
00710 }
00711 
00712 void ThemeEngine::unloadTheme() {
00713     if (!_themeOk)
00714         return;
00715 
00716     for (int i = 0; i < kDrawDataMAX; ++i) {
00717         delete _widgets[i];
00718         _widgets[i] = 0;
00719     }
00720 
00721     for (int i = 0; i < kTextDataMAX; ++i) {
00722         delete _texts[i];
00723         _texts[i] = 0;
00724     }
00725 
00726     for (int i = 0; i < kTextColorMAX; ++i) {
00727         delete _textColors[i];
00728         _textColors[i] = 0;
00729     }
00730 
00731     _themeEval->reset();
00732     _themeOk = false;
00733 }
00734 
00735 bool ThemeEngine::loadDefaultXML() {
00736 
00737     // The default XML theme is included on runtime from a pregenerated
00738     // file inside the themes directory.
00739     // Use the Python script "makedeftheme.py" to convert a normal XML theme
00740     // into the "default.inc" file, which is ready to be included in the code.
00741 #ifndef DISABLE_GUI_BUILTIN_THEME
00742 #include "themes/default.inc"
00743     int xmllen = 0;
00744 
00745     for (int i = 0; i < ARRAYSIZE(defaultXML); i++)
00746         xmllen += strlen(defaultXML[i]);
00747 
00748     byte *tmpXML = (byte *)malloc(xmllen + 1);
00749     tmpXML[0] = '\0';
00750 
00751     for (int i = 0; i < ARRAYSIZE(defaultXML); i++)
00752         strncat((char *)tmpXML, defaultXML[i], xmllen);
00753 
00754     if (!_parser->loadBuffer(tmpXML, xmllen)) {
00755         free(tmpXML);
00756 
00757         return false;
00758     }
00759 
00760     _themeName = "ResidualVM Classic Theme (Builtin Version)";
00761     _themeId = "builtin";
00762     _themeFile.clear();
00763 
00764     bool result = _parser->parse();
00765     _parser->close();
00766 
00767     free(tmpXML);
00768 
00769     return result;
00770 #else
00771     warning("The built-in theme is not enabled in the current build. Please load an external theme");
00772     return false;
00773 #endif
00774 }
00775 
00776 bool ThemeEngine::loadThemeXML(const Common::String &themeId) {
00777     assert(_parser);
00778     assert(_themeArchive);
00779 
00780     _themeName.clear();
00781 
00782 
00783     //
00784     // Now that we have a Common::Archive, verify that it contains a valid THEMERC File
00785     //
00786     Common::File themercFile;
00787     themercFile.open("THEMERC", *_themeArchive);
00788     if (!themercFile.isOpen()) {
00789         warning("Theme '%s' contains no 'THEMERC' file.", themeId.c_str());
00790         return false;
00791     }
00792 
00793     Common::String stxHeader = themercFile.readLine();
00794     if (!themeConfigParseHeader(stxHeader, _themeName) || _themeName.empty()) {
00795         warning("Corrupted 'THEMERC' file in theme '%s'", themeId.c_str());
00796         return false;
00797     }
00798 
00799     Common::ArchiveMemberList members;
00800     if (0 == _themeArchive->listMatchingMembers(members, "*.stx")) {
00801         warning("Found no STX files for theme '%s'.", themeId.c_str());
00802         return false;
00803     }
00804 
00805     //
00806     // Loop over all STX files, load and parse them
00807     //
00808     for (Common::ArchiveMemberList::iterator i = members.begin(); i != members.end(); ++i) {
00809         assert((*i)->getName().hasSuffix(".stx"));
00810 
00811         if (_parser->loadStream((*i)->createReadStream()) == false) {
00812             warning("Failed to load STX file '%s'", (*i)->getDisplayName().c_str());
00813             _parser->close();
00814             return false;
00815         }
00816 
00817         if (_parser->parse() == false) {
00818             warning("Failed to parse STX file '%s'", (*i)->getDisplayName().c_str());
00819             _parser->close();
00820             return false;
00821         }
00822 
00823         _parser->close();
00824     }
00825 
00826     assert(!_themeName.empty());
00827     return true;
00828 }
00829 
00830 
00831 
00832 /**********************************************************
00833  * Draw Date descriptors drawing functions
00834  *********************************************************/
00835 void ThemeEngine::drawDD(DrawData type, const Common::Rect &r, uint32 dynamic, bool forceRestore) {
00836     WidgetDrawData *drawData = _widgets[type];
00837 
00838     if (!drawData)
00839         return;
00840 
00841     if (kDrawDataDefaults[type].parent != kDDNone && kDrawDataDefaults[type].parent != type)
00842         drawDD(kDrawDataDefaults[type].parent, r, dynamic);
00843 
00844     Common::Rect area = r;
00845     area.clip(_screen.w, _screen.h);
00846 
00847     Common::Rect extendedRect = area;
00848     extendedRect.grow(kDirtyRectangleThreshold + drawData->_backgroundOffset);
00849     if (drawData->_shadowOffset > drawData->_backgroundOffset) {
00850         extendedRect.right += drawData->_shadowOffset - drawData->_backgroundOffset;
00851         extendedRect.bottom += drawData->_shadowOffset - drawData->_backgroundOffset;
00852     }
00853 
00854     if (!_clip.isEmpty()) {
00855         extendedRect.clip(_clip);
00856     }
00857 
00858     if (forceRestore || drawData->_layer == kDrawLayerBackground)
00859         restoreBackground(extendedRect);
00860 
00861     if (drawData->_layer == _layerToDraw) {
00862         Common::List<Graphics::DrawStep>::const_iterator step;
00863         for (step = drawData->_steps.begin(); step != drawData->_steps.end(); ++step) {
00864             _vectorRenderer->drawStepClip(area, _clip, *step, dynamic);
00865         }
00866 
00867         addDirtyRect(extendedRect);
00868     }
00869 }
00870 
00871 void ThemeEngine::drawDDText(TextData type, TextColor color, const Common::Rect &r, const Common::String &text,
00872                              bool restoreBg, bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV,
00873                              int deltax, const Common::Rect &drawableTextArea) {
00874 
00875     if (type == kTextDataNone || !_texts[type] || _layerToDraw == kDrawLayerBackground)
00876         return;
00877 
00878     Common::Rect area = r;
00879     area.clip(_screen.w, _screen.h);
00880 
00881     Common::Rect dirty = drawableTextArea;
00882     if (dirty.isEmpty()) dirty = area;
00883     else dirty.clip(area);
00884 
00885     if (!_clip.isEmpty()) {
00886         dirty.clip(_clip);
00887     }
00888 
00889     // HACK: One small pixel should be invisible enough
00890     if (dirty.isEmpty()) dirty = Common::Rect(0, 0, 1, 1);
00891 
00892     if (restoreBg)
00893         restoreBackground(dirty);
00894 
00895     _vectorRenderer->setFgColor(_textColors[color]->r, _textColors[color]->g, _textColors[color]->b);
00896     _vectorRenderer->drawString(_texts[type]->_fontPtr, text, area, alignH, alignV, deltax, ellipsis, dirty);
00897 
00898     addDirtyRect(dirty);
00899 }
00900 
00901 void ThemeEngine::drawBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha) {
00902     if (_layerToDraw == kDrawLayerBackground)
00903         return;
00904 
00905     Common::Rect area = r;
00906     area.clip(_screen.w, _screen.h);
00907 
00908     if (alpha)
00909         _vectorRenderer->blitKeyBitmapClip(bitmap, area, _clip);
00910     else
00911         _vectorRenderer->blitSubSurfaceClip(bitmap, area, _clip);
00912 
00913     Common::Rect dirtyRect = area;
00914     dirtyRect.clip(_clip);
00915     addDirtyRect(dirtyRect);
00916 }
00917 
00918 /**********************************************************
00919  * Widget drawing functions
00920  *********************************************************/
00921 void ThemeEngine::drawButton(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, uint16 hints) {
00922     if (!ready())
00923         return;
00924 
00925     DrawData dd = kDDButtonIdle;
00926 
00927     if (state == kStateEnabled)
00928         dd = kDDButtonIdle;
00929     else if (state == kStateHighlight)
00930         dd = kDDButtonHover;
00931     else if (state == kStateDisabled)
00932         dd = kDDButtonDisabled;
00933     else if (state == kStatePressed)
00934         dd = kDDButtonPressed;
00935 
00936     drawDD(dd, r, 0, hints & WIDGET_CLEARBG);
00937     drawDDText(getTextData(dd), getTextColor(dd), r, str, false, true, _widgets[dd]->_textAlignH,
00938                _widgets[dd]->_textAlignV);
00939 }
00940 
00941 void ThemeEngine::drawLineSeparator(const Common::Rect &r) {
00942     if (!ready())
00943         return;
00944 
00945     drawDD(kDDSeparator, r);
00946 }
00947 
00948 void ThemeEngine::drawCheckbox(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) {
00949     if (!ready())
00950         return;
00951 
00952     Common::Rect r2 = r;
00953     DrawData dd = kDDCheckboxDefault;
00954 
00955     if (checked)
00956         dd = kDDCheckboxSelected;
00957 
00958     if (state == kStateDisabled)
00959         dd = kDDCheckboxDisabled;
00960 
00961     const int checkBoxSize = MIN((int)r.height(), getFontHeight());
00962 
00963     r2.bottom = r2.top + checkBoxSize;
00964     r2.right = r2.left + checkBoxSize;
00965 
00966     drawDD(dd, r2);
00967 
00968     r2.left = r2.right + checkBoxSize;
00969     r2.right = r.right;
00970 
00971     drawDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDCheckboxDefault]->_textAlignH,
00972                _widgets[dd]->_textAlignV);
00973 }
00974 
00975 void ThemeEngine::drawRadiobutton(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) {
00976     if (!ready())
00977         return;
00978 
00979     Common::Rect r2 = r;
00980     DrawData dd = kDDRadiobuttonDefault;
00981 
00982     if (checked)
00983         dd = kDDRadiobuttonSelected;
00984 
00985     if (state == kStateDisabled)
00986         dd = kDDRadiobuttonDisabled;
00987 
00988     const int checkBoxSize = MIN((int)r.height(), getFontHeight());
00989 
00990     r2.bottom = r2.top + checkBoxSize;
00991     r2.right = r2.left + checkBoxSize;
00992 
00993     drawDD(dd, r2);
00994 
00995     r2.left = r2.right + checkBoxSize;
00996     r2.right = MAX(r2.left, r.right);
00997 
00998     drawDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDRadiobuttonDefault]->_textAlignH,
00999                _widgets[dd]->_textAlignV);
01000 }
01001 
01002 void ThemeEngine::drawSlider(const Common::Rect &r, int width, WidgetStateInfo state) {
01003     if (!ready())
01004         return;
01005 
01006     DrawData dd = kDDSliderFull;
01007 
01008     if (state == kStateHighlight)
01009         dd = kDDSliderHover;
01010     else if (state == kStateDisabled)
01011         dd = kDDSliderDisabled;
01012 
01013     Common::Rect r2 = r;
01014     r2.setWidth(MIN((int16)width, r.width()));
01015     //  r2.top++; r2.bottom--; r2.left++; r2.right--;
01016 
01017     drawWidgetBackground(r, 0, kWidgetBackgroundSlider);
01018 
01019     drawDD(dd, r2);
01020 }
01021 
01022 void ThemeEngine::drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState scrollState) {
01023     if (!ready())
01024         return;
01025 
01026     drawDD(kDDScrollbarBase, r);
01027 
01028     Common::Rect r2 = r;
01029     const int buttonExtra = (r.width() * 120) / 100;
01030 
01031     r2.bottom = r2.top + buttonExtra;
01032     drawDD(scrollState == kScrollbarStateUp ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2,
01033            Graphics::VectorRenderer::kTriangleUp);
01034 
01035     r2.translate(0, r.height() - r2.height());
01036     drawDD(scrollState == kScrollbarStateDown ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2,
01037            Graphics::VectorRenderer::kTriangleDown);
01038 
01039     r2 = r;
01040     r2.left += 1;
01041     r2.right -= 1;
01042     r2.top += sliderY;
01043     r2.bottom = r2.top + sliderHeight;
01044     drawDD(scrollState == kScrollbarStateSlider ? kDDScrollbarHandleHover : kDDScrollbarHandleIdle, r2);
01045 }
01046 
01047 void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground bgtype) {
01048     if (!ready())
01049         return;
01050 
01051     switch (bgtype) {
01052     case kDialogBackgroundMain:
01053         drawDD(kDDMainDialogBackground, r);
01054         break;
01055 
01056     case kDialogBackgroundSpecial:
01057         drawDD(kDDSpecialColorBackground, r);
01058         break;
01059 
01060     case kDialogBackgroundPlain:
01061         drawDD(kDDPlainColorBackground, r);
01062         break;
01063 
01064     case kDialogBackgroundTooltip:
01065         drawDD(kDDTooltipBackground, r);
01066         break;
01067 
01068     case kDialogBackgroundDefault:
01069         drawDD(kDDDefaultBackground, r);
01070         break;
01071     case kDialogBackgroundNone:
01072         // no op
01073         break;
01074     }
01075 }
01076 
01077 void ThemeEngine::drawCaret(const Common::Rect &r, bool erase) {
01078     if (!ready())
01079         return;
01080 
01081     if (erase) {
01082         restoreBackground(r);
01083     } else
01084         drawDD(kDDCaret, r);
01085 }
01086 
01087 void ThemeEngine::drawPopUpWidget(const Common::Rect &r, const Common::String &sel, int deltax, WidgetStateInfo state) {
01088     if (!ready())
01089         return;
01090 
01091     DrawData dd = kDDPopUpIdle;
01092 
01093     if (state == kStateEnabled)
01094         dd = kDDPopUpIdle;
01095     else if (state == kStateHighlight)
01096         dd = kDDPopUpHover;
01097     else if (state == kStateDisabled)
01098         dd = kDDPopUpDisabled;
01099 
01100     drawDD(dd, r);
01101 
01102     if (!sel.empty() && r.width() >= 13 && r.height() >= 1) {
01103         Common::Rect text(r.left + 3, r.top + 1, r.right - 10, r.bottom);
01104         drawDDText(getTextData(dd), getTextColor(dd), text, sel, true, false, _widgets[dd]->_textAlignH,
01105                    _widgets[dd]->_textAlignV, deltax);
01106     }
01107 }
01108 
01109 void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &surface, bool themeTrans) {
01110     if (!ready())
01111         return;
01112 
01113     drawBitmap(&surface, r, themeTrans);
01114 }
01115 
01116 void ThemeEngine::drawWidgetBackground(const Common::Rect &r, uint16 hints, WidgetBackground background) {
01117     if (!ready())
01118         return;
01119 
01120     switch (background) {
01121     case kWidgetBackgroundBorderSmall:
01122         drawDD(kDDWidgetBackgroundSmall, r);
01123         break;
01124 
01125     case kWidgetBackgroundEditText:
01126         drawDD(kDDWidgetBackgroundEditText, r);
01127         break;
01128 
01129     case kWidgetBackgroundSlider:
01130         drawDD(kDDWidgetBackgroundSlider, r);
01131         break;
01132 
01133     default:
01134         drawDD(kDDWidgetBackgroundDefault, r);
01135         break;
01136     }
01137 }
01138 
01139 void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Array<int> &tabWidths,
01140                           const Common::Array<Common::String> &tabs, int active) {
01141     if (!ready())
01142         return;
01143 
01144     assert(tabs.size() == tabWidths.size());
01145 
01146     drawDD(kDDTabBackground, Common::Rect(r.left, r.top, r.right, r.top + tabHeight));
01147 
01148     int width = 0;
01149     int activePos = -1;
01150     for (int i = 0; i < (int)tabs.size(); width += tabWidths[i++]) {
01151         if (r.left + width > r.right || r.left + width + tabWidths[i] > r.right)
01152             continue;
01153 
01154         if (i == active) {
01155             activePos = width;
01156             continue;
01157         }
01158 
01159 
01160         Common::Rect tabRect(r.left + width, r.top, r.left + width + tabWidths[i], r.top + tabHeight);
01161         drawDD(kDDTabInactive, tabRect);
01162         drawDDText(getTextData(kDDTabInactive), getTextColor(kDDTabInactive), tabRect, tabs[i], false, false,
01163                    _widgets[kDDTabInactive]->_textAlignH, _widgets[kDDTabInactive]->_textAlignV);
01164     }
01165 
01166     if (activePos >= 0) {
01167         Common::Rect tabRect(r.left + activePos, r.top, r.left + activePos + tabWidths[active], r.top + tabHeight);
01168         const uint16 tabLeft = activePos;
01169         const uint16 tabRight = MAX(r.right - tabRect.right, 0);
01170         drawDD(kDDTabActive, tabRect, (tabLeft << 16) | (tabRight & 0xFFFF));
01171         drawDDText(getTextData(kDDTabActive), getTextColor(kDDTabActive), tabRect, tabs[active], false, false,
01172                    _widgets[kDDTabActive]->_textAlignH, _widgets[kDDTabActive]->_textAlignV);
01173     }
01174 }
01175 
01176 void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state,
01177                            Graphics::TextAlign align, TextInversionState inverted, int deltax, bool useEllipsis,
01178                            FontStyle font, FontColor color, bool restore, const Common::Rect &drawableTextArea) {
01179     if (!ready())
01180         return;
01181 
01182     TextColor colorId = kTextColorMAX;
01183 
01184     switch (color) {
01185     case kFontColorNormal:
01186         if (inverted) {
01187             colorId = kTextColorNormalInverted;
01188         } else {
01189             switch (state) {
01190             case kStateDisabled:
01191                 colorId = kTextColorNormalDisabled;
01192                 break;
01193 
01194             case kStateHighlight:
01195                 colorId = kTextColorNormalHover;
01196                 break;
01197 
01198             case kStateEnabled:
01199             case kStatePressed:
01200                 colorId = kTextColorNormal;
01201                 break;
01202             }
01203         }
01204         break;
01205 
01206     case kFontColorAlternate:
01207         if (inverted) {
01208             colorId = kTextColorAlternativeInverted;
01209         } else {
01210             switch (state) {
01211             case kStateDisabled:
01212                 colorId = kTextColorAlternativeDisabled;
01213                 break;
01214 
01215             case kStateHighlight:
01216                 colorId = kTextColorAlternativeHover;
01217                 break;
01218 
01219             case kStateEnabled:
01220             case kStatePressed:
01221                 colorId = kTextColorAlternative;
01222                 break;
01223             }
01224         }
01225         break;
01226 
01227     default:
01228         return;
01229     }
01230 
01231     TextData textId = fontStyleToData(font);
01232 
01233     switch (inverted) {
01234     case kTextInversion:
01235         drawDD(kDDTextSelectionBackground, r);
01236         restore = false;
01237         break;
01238 
01239     case kTextInversionFocus:
01240         drawDD(kDDTextSelectionFocusBackground, r);
01241         restore = false;
01242         break;
01243 
01244     default:
01245         break;
01246     }
01247 
01248     drawDDText(textId, colorId, r, str, restore, useEllipsis, align, kTextAlignVCenter, deltax, drawableTextArea);
01249 }
01250 
01251 void ThemeEngine::drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, FontColor color) {
01252     if (!ready())
01253         return;
01254 
01255     Common::Rect charArea = r;
01256     charArea.clip(_screen.w, _screen.h);
01257 
01258     uint32 rgbColor = _overlayFormat.RGBToColor(_textColors[color]->r, _textColors[color]->g, _textColors[color]->b);
01259 
01260     // TODO: Handle clipping when drawing chars
01261 
01262     restoreBackground(charArea);
01263     font->drawChar(&_screen, ch, charArea.left, charArea.top, rgbColor);
01264     addDirtyRect(charArea);
01265 }
01266 
01267 void ThemeEngine::debugWidgetPosition(const char *name, const Common::Rect &r) {
01268     _font->drawString(&_screen, name, r.left, r.top, r.width(), 0xFFFF, Graphics::kTextAlignRight, 0, true);
01269     _screen.hLine(r.left, r.top, r.right, 0xFFFF);
01270     _screen.hLine(r.left, r.bottom, r.right, 0xFFFF);
01271     _screen.vLine(r.left, r.top, r.bottom, 0xFFFF);
01272     _screen.vLine(r.right, r.top, r.bottom, 0xFFFF);
01273 }
01274 
01275 /**********************************************************
01276  * Screen/overlay management
01277  *********************************************************/
01278 void ThemeEngine::copyBackBufferToScreen() {
01279     memcpy(_screen.getPixels(), _backBuffer.getPixels(), _screen.pitch * _screen.h);
01280 }
01281 
01282 void ThemeEngine::updateScreen() {
01283 #ifdef LAYOUT_DEBUG_DIALOG
01284     _vectorRenderer->fillSurface();
01285     _themeEval->debugDraw(&_screen, _font);
01286     _vectorRenderer->copyWholeFrame(_system);
01287 #else
01288     updateDirtyScreen();
01289 #endif
01290 }
01291 
01292 void ThemeEngine::addDirtyRect(Common::Rect r) {
01293     // Clip the rect to screen coords
01294     r.clip(_screen.w, _screen.h);
01295 
01296     // If it is empty after clipping, we are done
01297     if (r.isEmpty())
01298         return;
01299 
01300     // Check if the new rectangle is contained within another in the list
01301     Common::List<Common::Rect>::iterator it;
01302     for (it = _dirtyScreen.begin(); it != _dirtyScreen.end();) {
01303         // If we find a rectangle which fully contains the new one,
01304         // we can abort the search.
01305         if (it->contains(r))
01306             return;
01307 
01308         // Conversely, if we find rectangles which are contained in
01309         // the new one, we can remove them
01310         if (r.contains(*it))
01311             it = _dirtyScreen.erase(it);
01312         else
01313             ++it;
01314     }
01315 
01316     // If we got here, we can safely add r to the list of dirty rects.
01317     _dirtyScreen.push_back(r);
01318 }
01319 
01320 void ThemeEngine::updateDirtyScreen() {
01321     if (_dirtyScreen.empty())
01322         return;
01323 
01324     Common::List<Common::Rect>::iterator i;
01325     for (i = _dirtyScreen.begin(); i != _dirtyScreen.end(); ++i) {
01326         _vectorRenderer->copyFrame(_system, *i);
01327     }
01328 
01329     _dirtyScreen.clear();
01330 }
01331 
01332 void ThemeEngine::applyScreenShading(ShadingStyle style) {
01333     if (style != kShadingNone) {
01334         _vectorRenderer->applyScreenShading(style);
01335         addDirtyRect(Common::Rect(0, 0, _screen.w, _screen.h));
01336     }
01337 }
01338 
01339 bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int hotspotY) {
01340     if (!_system->hasFeature(OSystem::kFeatureCursorPalette))
01341         return true;
01342 
01343     // Try to locate the specified file among all loaded bitmaps
01344     const Graphics::Surface *cursor = _bitmaps[filename];
01345     if (!cursor)
01346         return false;
01347 
01348 #ifdef USE_RGB_COLOR
01349     _cursorFormat.bytesPerPixel = 1;
01350     _cursorFormat.rLoss = _cursorFormat.gLoss = _cursorFormat.bLoss = _cursorFormat.aLoss = 8;
01351     _cursorFormat.rShift = _cursorFormat.gShift = _cursorFormat.bShift = _cursorFormat.aShift = 0;
01352 #endif
01353 
01354     // Set up the cursor parameters
01355     _cursorHotspotX = hotspotX;
01356     _cursorHotspotY = hotspotY;
01357 
01358     _cursorWidth = cursor->w;
01359     _cursorHeight = cursor->h;
01360 
01361     // Allocate a new buffer for the cursor
01362     delete[] _cursor;
01363     _cursor = new byte[_cursorWidth * _cursorHeight];
01364     assert(_cursor);
01365     memset(_cursor, 0xFF, sizeof(byte) * _cursorWidth * _cursorHeight);
01366 
01367     // the transparent color is 0xFF00FF
01368     const uint32 colTransparent = _overlayFormat.RGBToColor(0xFF, 0, 0xFF);
01369 
01370     // Now, scan the bitmap. We have to convert it from 16 bit color mode
01371     // to 8 bit mode, and have to create a suitable palette on the fly.
01372     uint colorsFound = 0;
01373     Common::HashMap<int, int> colorToIndex;
01374     const byte *src = (const byte *)cursor->getPixels();
01375     for (uint y = 0; y < _cursorHeight; ++y) {
01376         for (uint x = 0; x < _cursorWidth; ++x) {
01377             uint32 color = colTransparent;
01378             byte r, g, b;
01379 
01380             if (cursor->format.bytesPerPixel == 2) {
01381                 color = READ_UINT16(src);
01382             } else if (cursor->format.bytesPerPixel == 4) {
01383                 color = READ_UINT32(src);
01384             }
01385 
01386             src += cursor->format.bytesPerPixel;
01387 
01388             // Skip transparency
01389             if (color == colTransparent)
01390                 continue;
01391 
01392             cursor->format.colorToRGB(color, r, g, b);
01393             const int col = (r << 16) | (g << 8) | b;
01394 
01395             // If there is no entry yet for this color in the palette: Add one
01396             if (!colorToIndex.contains(col)) {
01397                 if (colorsFound >= MAX_CURS_COLORS) {
01398                     warning("Cursor contains too many colors (%d, but only %d are allowed)", colorsFound, MAX_CURS_COLORS);
01399                     return false;
01400                 }
01401 
01402                 const int index = colorsFound++;
01403                 colorToIndex[col] = index;
01404 
01405                 _cursorPal[index * 3 + 0] = r;
01406                 _cursorPal[index * 3 + 1] = g;
01407                 _cursorPal[index * 3 + 2] = b;
01408             }
01409 
01410             // Copy pixel from the 16 bit source surface to the 8bit target surface
01411             const int index = colorToIndex[col];
01412             _cursor[y * _cursorWidth + x] = index;
01413         }
01414     }
01415 
01416     _useCursor = true;
01417     _cursorPalSize = colorsFound;
01418 
01419     return true;
01420 }
01421 
01422 
01423 /**********************************************************
01424  * Legacy GUI::Theme support functions
01425  *********************************************************/
01426 
01427 const Graphics::Font *ThemeEngine::getFont(FontStyle font) const {
01428     return _texts[fontStyleToData(font)]->_fontPtr;
01429 }
01430 
01431 int ThemeEngine::getFontHeight(FontStyle font) const {
01432     return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getFontHeight() : 0;
01433 }
01434 
01435 int ThemeEngine::getStringWidth(const Common::String &str, FontStyle font) const {
01436     return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getStringWidth(str) : 0;
01437 }
01438 
01439 int ThemeEngine::getCharWidth(byte c, FontStyle font) const {
01440     return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getCharWidth(c) : 0;
01441 }
01442 
01443 int ThemeEngine::getKerningOffset(byte left, byte right, FontStyle font) const {
01444     return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getKerningOffset(left, right) : 0;
01445 }
01446 
01447 TextData ThemeEngine::getTextData(DrawData ddId) const {
01448     return _widgets[ddId] ? (TextData)_widgets[ddId]->_textDataId : kTextDataNone;
01449 }
01450 
01451 TextColor ThemeEngine::getTextColor(DrawData ddId) const {
01452     return _widgets[ddId] ? _widgets[ddId]->_textColorId : kTextColorMAX;
01453 }
01454 
01455 DrawData ThemeEngine::parseDrawDataId(const Common::String &name) const {
01456     for (int i = 0; i < kDrawDataMAX; ++i)
01457         if (name.compareToIgnoreCase(kDrawDataDefaults[i].name) == 0)
01458             return kDrawDataDefaults[i].id;
01459 
01460     return kDDNone;
01461 }
01462 
01463 /**********************************************************
01464  * External data loading
01465  *********************************************************/
01466 
01467 const Graphics::Font *ThemeEngine::loadScalableFont(const Common::String &filename, const Common::String &charset, const int pointsize, Common::String &name) {
01468 #ifdef USE_FREETYPE2
01469 #ifdef USE_TRANSLATION
01470     const uint32 *mapping = TransMan.getCharsetMapping();
01471 #else
01472     const uint32 *mapping = 0;
01473 #endif
01474     name = Common::String::format("%s-%s@%d", filename.c_str(), charset.c_str(), pointsize);
01475 
01476     // Try already loaded fonts.
01477     const Graphics::Font *font = FontMan.getFontByName(name);
01478     if (font)
01479         return font;
01480 
01481     Common::ArchiveMemberList members;
01482     _themeFiles.listMatchingMembers(members, filename);
01483 
01484     for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
01485         Common::SeekableReadStream *stream = (*i)->createReadStream();
01486         if (stream) {
01487             font = Graphics::loadTTFFont(*stream, pointsize, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeLight, mapping);
01488             delete stream;
01489 
01490             if (font)
01491                 return font;
01492         }
01493     }
01494 
01495     // Try loading the font from the common fonts archive.
01496     font = Graphics::loadTTFFontFromArchive(filename, pointsize, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeLight, mapping);
01497     if (font)
01498         return font;
01499 #endif
01500     return 0;
01501 }
01502 
01503 const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, Common::String &name) {
01504     name = filename;
01505 
01506     // Try already loaded fonts.
01507     const Graphics::Font *font = FontMan.getFontByName(name);
01508     if (font)
01509         return font;
01510 
01511     Common::ArchiveMemberList members;
01512     const Common::String cacheFilename(genCacheFilename(filename));
01513     _themeFiles.listMatchingMembers(members, cacheFilename);
01514     _themeFiles.listMatchingMembers(members, filename);
01515 
01516     for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
01517         Common::SeekableReadStream *stream = (*i)->createReadStream();
01518         if (stream) {
01519             if ((*i)->getName().equalsIgnoreCase(cacheFilename)) {
01520                 font = Graphics::BdfFont::loadFromCache(*stream);
01521             } else {
01522                 font = Graphics::BdfFont::loadFont(*stream);
01523                 if (font && !cacheFilename.empty()) {
01524                     if (!Graphics::BdfFont::cacheFontData(*(const Graphics::BdfFont *)font, cacheFilename))
01525                         warning("Couldn't create cache file for font '%s'", filename.c_str());
01526                 }
01527             }
01528             delete stream;
01529 
01530             if (font)
01531                 return font;
01532         }
01533     }
01534 
01535     return 0;
01536 }
01537 
01538 const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, const Common::String &scalableFilename, const Common::String &charset, const int pointsize, const bool makeLocalizedFont) {
01539     Common::String fontName;
01540 
01541     const Graphics::Font *font = 0;
01542 
01543     // Prefer scalable fonts over non-scalable fonts
01544     if (!scalableFilename.empty())
01545         font = loadScalableFont(scalableFilename, charset, pointsize, fontName);
01546 
01547     if (!font)
01548         font = loadFont(filename, fontName);
01549 
01550     // If the font is successfully loaded store it in the font manager.
01551     if (font) {
01552         FontMan.assignFontToName(fontName, font);
01553         // If this font should be the new default localized font, we set it up
01554         // for that.
01555         if (makeLocalizedFont)
01556             FontMan.setLocalizedFont(fontName);
01557     }
01558 
01559     return font;
01560 }
01561 
01562 Common::String ThemeEngine::genCacheFilename(const Common::String &filename) const {
01563     Common::String cacheName(filename);
01564     for (int i = cacheName.size() - 1; i >= 0; --i) {
01565         if (cacheName[i] == '.') {
01566             while ((uint)i < cacheName.size() - 1) {
01567                 cacheName.deleteLastChar();
01568             }
01569 
01570             cacheName += "fcc";
01571             return cacheName;
01572         }
01573     }
01574 
01575     return Common::String();
01576 }
01577 
01578 
01579 /**********************************************************
01580  * Static Theme XML functions
01581  *********************************************************/
01582 
01583 bool ThemeEngine::themeConfigParseHeader(Common::String header, Common::String &themeName) {
01584     // Check that header is not corrupted
01585     if ((byte)header[0] > 127) {
01586         warning("Corrupted theme header found");
01587         return false;
01588     }
01589 
01590     header.trim();
01591 
01592     if (header.empty())
01593         return false;
01594 
01595     if (header[0] != '[' || header.lastChar() != ']')
01596         return false;
01597 
01598     header.deleteChar(0);
01599     header.deleteLastChar();
01600 
01601     Common::StringTokenizer tok(header, ":");
01602 
01603     if (tok.nextToken() != SCUMMVM_THEME_VERSION_STR)
01604         return false;
01605 
01606     themeName = tok.nextToken();
01607     Common::String author = tok.nextToken();
01608 
01609     return tok.empty();
01610 }
01611 
01612 bool ThemeEngine::themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName) {
01613     Common::File stream;
01614     bool foundHeader = false;
01615 
01616     if (member.getName().matchString("*.zip", true)) {
01617         Common::Archive *zipArchive = Common::makeZipArchive(member.createReadStream());
01618 
01619         if (zipArchive && zipArchive->hasFile("THEMERC")) {
01620             stream.open("THEMERC", *zipArchive);
01621         }
01622 
01623         delete zipArchive;
01624     }
01625 
01626     if (stream.isOpen()) {
01627         Common::String stxHeader = stream.readLine();
01628         foundHeader = themeConfigParseHeader(stxHeader, themeName);
01629     }
01630 
01631     return foundHeader;
01632 }
01633 
01634 bool ThemeEngine::themeConfigUsable(const Common::FSNode &node, Common::String &themeName) {
01635     Common::File stream;
01636     bool foundHeader = false;
01637 
01638     if (node.getName().matchString("*.zip", true) && !node.isDirectory()) {
01639         Common::Archive *zipArchive = Common::makeZipArchive(node);
01640         if (zipArchive && zipArchive->hasFile("THEMERC")) {
01641             // Open THEMERC from the ZIP file.
01642             stream.open("THEMERC", *zipArchive);
01643         }
01644         // Delete the ZIP archive again. Note: This only works because
01645         // stream.open() only uses ZipArchive::createReadStreamForMember,
01646         // and that in turn happens to read all the data for a given
01647         // archive member into a memory block. So there will be no dangling
01648         // reference to zipArchive anywhere. This could change if we
01649         // ever modify ZipArchive::createReadStreamForMember.
01650         delete zipArchive;
01651     } else if (node.isDirectory()) {
01652         Common::FSNode headerfile = node.getChild("THEMERC");
01653         if (!headerfile.exists() || !headerfile.isReadable() || headerfile.isDirectory())
01654             return false;
01655         stream.open(headerfile);
01656     }
01657 
01658     if (stream.isOpen()) {
01659         Common::String stxHeader = stream.readLine();
01660         foundHeader = themeConfigParseHeader(stxHeader, themeName);
01661     }
01662 
01663     return foundHeader;
01664 }
01665 
01666 namespace {
01667 
01668 struct TDComparator {
01669     const Common::String _id;
01670     TDComparator(const Common::String &id) : _id(id) {}
01671 
01672     bool operator()(const ThemeEngine::ThemeDescriptor &r) {
01673         return _id == r.id;
01674     }
01675 };
01676 
01677 } // end of anonymous namespace
01678 
01679 void ThemeEngine::listUsableThemes(Common::List<ThemeDescriptor> &list) {
01680 #ifndef DISABLE_GUI_BUILTIN_THEME
01681     ThemeDescriptor th;
01682     th.name = "ResidualVM Classic Theme (Builtin Version)";
01683     th.id = "builtin";
01684     th.filename.clear();
01685     list.push_back(th);
01686 #endif
01687 
01688     if (ConfMan.hasKey("themepath"))
01689         listUsableThemes(Common::FSNode(ConfMan.get("themepath")), list);
01690 
01691     listUsableThemes(SearchMan, list);
01692 
01693     // Now we need to strip all duplicates
01694     // TODO: It might not be the best idea to strip duplicates. The user might
01695     // have different versions of a specific theme in his paths, thus this code
01696     // might show him the wrong version. The problem is we have no ways of checking
01697     // a theme version currently. Also since we want to avoid saving the full path
01698     // in the config file we can not do any better currently.
01699     Common::List<ThemeDescriptor> output;
01700 
01701     for (Common::List<ThemeDescriptor>::const_iterator i = list.begin(); i != list.end(); ++i) {
01702         if (Common::find_if(output.begin(), output.end(), TDComparator(i->id)) == output.end())
01703             output.push_back(*i);
01704     }
01705 
01706     list = output;
01707     output.clear();
01708 }
01709 
01710 void ThemeEngine::listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list) {
01711     ThemeDescriptor td;
01712 
01713     Common::ArchiveMemberList fileList;
01714     archive.listMatchingMembers(fileList, "*.zip");
01715     for (Common::ArchiveMemberList::iterator i = fileList.begin();
01716             i != fileList.end(); ++i) {
01717         td.name.clear();
01718         if (themeConfigUsable(**i, td.name)) {
01719             td.filename = (*i)->getName();
01720             td.id = (*i)->getDisplayName();
01721 
01722             // If the name of the node object also contains
01723             // the ".zip" suffix, we will strip it.
01724             if (td.id.matchString("*.zip", true)) {
01725                 for (int j = 0; j < 4; ++j)
01726                     td.id.deleteLastChar();
01727             }
01728 
01729             list.push_back(td);
01730         }
01731     }
01732 
01733     fileList.clear();
01734 }
01735 
01736 void ThemeEngine::listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth) {
01737     if (!node.exists() || !node.isReadable() || !node.isDirectory())
01738         return;
01739 
01740     ThemeDescriptor td;
01741 
01742     // Check whether we point to a valid theme directory.
01743     if (themeConfigUsable(node, td.name)) {
01744         td.filename = node.getPath();
01745         td.id = node.getName();
01746 
01747         list.push_back(td);
01748 
01749         // A theme directory should never contain any other themes
01750         // thus we just return to the caller here.
01751         return;
01752     }
01753 
01754     Common::FSList fileList;
01755     // Check all files. We need this to find all themes inside ZIP archives.
01756     if (!node.getChildren(fileList, Common::FSNode::kListFilesOnly))
01757         return;
01758 
01759     for (Common::FSList::iterator i = fileList.begin(); i != fileList.end(); ++i) {
01760         // We will only process zip files for now
01761         if (!i->getPath().matchString("*.zip", true))
01762             continue;
01763 
01764         td.name.clear();
01765         if (themeConfigUsable(*i, td.name)) {
01766             td.filename = i->getPath();
01767             td.id = i->getName();
01768 
01769             // If the name of the node object also contains
01770             // the ".zip" suffix, we will strip it.
01771             if (td.id.matchString("*.zip", true)) {
01772                 for (int j = 0; j < 4; ++j)
01773                     td.id.deleteLastChar();
01774             }
01775 
01776             list.push_back(td);
01777         }
01778     }
01779 
01780     fileList.clear();
01781 
01782     // Check if we exceeded the given recursion depth
01783     if (depth - 1 == -1)
01784         return;
01785 
01786     // As next step we will search all subdirectories
01787     if (!node.getChildren(fileList, Common::FSNode::kListDirectoriesOnly))
01788         return;
01789 
01790     for (Common::FSList::iterator i = fileList.begin(); i != fileList.end(); ++i)
01791         listUsableThemes(*i, list, depth == -1 ? - 1 : depth - 1);
01792 }
01793 
01794 Common::String ThemeEngine::getThemeFile(const Common::String &id) {
01795     // FIXME: Actually "default" rather sounds like it should use
01796     // our default theme which would mean "scummmodern" instead
01797     // of the builtin one.
01798     if (id.equalsIgnoreCase("default"))
01799         return Common::String();
01800 
01801     // For our builtin theme we don't have to do anything for now too
01802     if (id.equalsIgnoreCase("builtin"))
01803         return Common::String();
01804 
01805     Common::FSNode node(id);
01806 
01807     // If the given id is a full path we'll just use it
01808     if (node.exists() && (node.isDirectory() || node.getName().matchString("*.zip", true)))
01809         return id;
01810 
01811     // FIXME:
01812     // A very ugly hack to map a id to a filename, this will generate
01813     // a complete theme list, thus it is slower than it could be.
01814     // But it is the easiest solution for now.
01815     Common::List<ThemeDescriptor> list;
01816     listUsableThemes(list);
01817 
01818     for (Common::List<ThemeDescriptor>::const_iterator i = list.begin(); i != list.end(); ++i) {
01819         if (id.equalsIgnoreCase(i->id))
01820             return i->filename;
01821     }
01822 
01823     warning("Could not find theme '%s' falling back to builtin", id.c_str());
01824 
01825     // If no matching id has been found we will
01826     // just fall back to the builtin theme
01827     return Common::String();
01828 }
01829 
01830 Common::String ThemeEngine::getThemeId(const Common::String &filename) {
01831     // If no filename has been given we will initialize the builtin theme
01832     if (filename.empty())
01833         return "builtin";
01834 
01835     Common::FSNode node(filename);
01836     if (node.exists()) {
01837         if (node.getName().matchString("*.zip", true)) {
01838             Common::String id = node.getName();
01839 
01840             for (int i = 0; i < 4; ++i)
01841                 id.deleteLastChar();
01842 
01843             return id;
01844         } else {
01845             return node.getName();
01846         }
01847     }
01848 
01849     // FIXME:
01850     // A very ugly hack to map a id to a filename, this will generate
01851     // a complete theme list, thus it is slower than it could be.
01852     // But it is the easiest solution for now.
01853     Common::List<ThemeDescriptor> list;
01854     listUsableThemes(list);
01855 
01856     for (Common::List<ThemeDescriptor>::const_iterator i = list.begin(); i != list.end(); ++i) {
01857         if (filename.equalsIgnoreCase(i->filename))
01858             return i->id;
01859     }
01860 
01861     return "builtin";
01862 }
01863 
01864 void ThemeEngine::showCursor() {
01865     if (_useCursor) {
01866         CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
01867         CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
01868         CursorMan.showMouse(true);
01869     }
01870 }
01871 
01872 void ThemeEngine::hideCursor() {
01873     if (_useCursor) {
01874         CursorMan.popCursorPalette();
01875         CursorMan.popCursor();
01876     }
01877 }
01878 
01879 void ThemeEngine::drawToBackbuffer() {
01880     _vectorRenderer->setSurface(&_backBuffer);
01881 }
01882 
01883 void ThemeEngine::drawToScreen() {
01884     _vectorRenderer->setSurface(&_screen);
01885 }
01886 
01887 Common::Rect ThemeEngine::swapClipRect(const Common::Rect &newRect) {
01888     Common::Rect oldRect = _clip;
01889     _clip = newRect;
01890     return oldRect;
01891 }
01892 
01893 } // End of namespace GUI.


Generated on Sat Jun 22 2019 05:00:55 for ResidualVM by doxygen 1.7.1
curved edge   curved edge