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

gui-manager.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/events.h"
00024 #include "common/system.h"
00025 #include "common/util.h"
00026 #include "common/config-manager.h"
00027 #include "common/algorithm.h"
00028 #include "common/rect.h"
00029 #include "common/textconsole.h"
00030 #include "common/translation.h"
00031 #include "gui/EventRecorder.h"
00032 
00033 #include "backends/keymapper/keymapper.h"
00034 
00035 #include "gui/gui-manager.h"
00036 #include "gui/dialog.h"
00037 #include "gui/ThemeEngine.h"
00038 #include "gui/ThemeEval.h"
00039 #include "gui/Tooltip.h"
00040 #include "gui/widget.h"
00041 
00042 #include "graphics/cursorman.h"
00043 
00044 namespace Common {
00045 DECLARE_SINGLETON(GUI::GuiManager);
00046 }
00047 
00048 namespace GUI {
00049 
00050 enum {
00051     kDoubleClickDelay = 500, // milliseconds
00052     kCursorAnimateDelay = 250,
00053     kTooltipDelay = 1250
00054 };
00055 
00056 // Constructor
00057 GuiManager::GuiManager() : _redrawStatus(kRedrawDisabled), _stateIsSaved(false),
00058     _cursorAnimateCounter(0), _cursorAnimateTimer(0) {
00059     _theme = 0;
00060     _useStdCursor = false;
00061 
00062     _system = g_system;
00063     _lastScreenChangeID = _system->getScreenChangeID();
00064     _width = _system->getOverlayWidth();
00065     _height = _system->getOverlayHeight();
00066 
00067     _launched = false;
00068 
00069     // Clear the cursor
00070     memset(_cursor, 0xFF, sizeof(_cursor));
00071 
00072 #ifdef USE_TRANSLATION
00073     // Enable translation
00074     TransMan.setLanguage(ConfMan.get("gui_language").c_str());
00075 #endif // USE_TRANSLATION
00076 
00077     ConfMan.registerDefault("gui_theme", "modern");
00078     Common::String themefile(ConfMan.get("gui_theme"));
00079 
00080     ConfMan.registerDefault("gui_renderer", ThemeEngine::findModeConfigName(ThemeEngine::_defaultRendererMode));
00081     ThemeEngine::GraphicsMode gfxMode = (ThemeEngine::GraphicsMode)ThemeEngine::findMode(ConfMan.get("gui_renderer"));
00082 
00083 #ifdef __DS__
00084     // Searching for the theme file takes ~10 seconds on the DS.
00085     // Disable this search here because external themes are not supported.
00086     if (!loadNewTheme("builtin", gfxMode)) {
00087         // Loading the built-in theme failed as well. Bail out
00088         error("Failed to load any GUI theme, aborting");
00089     }
00090 #else
00091     // Try to load the theme
00092     if (!loadNewTheme(themefile, gfxMode)) {
00093         // Loading the theme failed, try to load the built-in theme
00094         if (!loadNewTheme("builtin", gfxMode)) {
00095             // Loading the built-in theme failed as well. Bail out
00096             error("Failed to load any GUI theme, aborting");
00097         }
00098     }
00099 #endif
00100 }
00101 
00102 GuiManager::~GuiManager() {
00103     delete _theme;
00104 }
00105 
00106 #ifdef ENABLE_KEYMAPPER
00107 void GuiManager::initKeymap() {
00108     using namespace Common;
00109 
00110     Keymapper *mapper = _system->getEventManager()->getKeymapper();
00111 
00112     // Do not try to recreate same keymap over again
00113     if (mapper->getKeymap(kGuiKeymapName) != 0)
00114         return;
00115 
00116     Action *act;
00117     Keymap *guiMap = new Keymap(kGuiKeymapName);
00118 
00119     act = new Action(guiMap, "CLOS", _("Close"));
00120     act->addKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE, 0));
00121 
00122     act = new Action(guiMap, "CLIK", _("Mouse click"));
00123     act->addLeftClickEvent();
00124 
00125 #ifdef ENABLE_VKEYBD
00126     act = new Action(guiMap, "VIRT", _("Display keyboard"));
00127     act->addEvent(EVENT_VIRTUAL_KEYBOARD);
00128 #endif
00129 
00130     act = new Action(guiMap, "REMP", _("Remap keys"));
00131     act->addEvent(EVENT_KEYMAPPER_REMAP);
00132 
00133     act = new Action(guiMap, "FULS", _("Toggle fullscreen"));
00134     act->addKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN, KBD_ALT));
00135 
00136     mapper->addGlobalKeymap(guiMap);
00137 }
00138 
00139 void GuiManager::pushKeymap() {
00140     _system->getEventManager()->getKeymapper()->pushKeymap(Common::kGuiKeymapName);
00141 }
00142 
00143 void GuiManager::popKeymap() {
00144     _system->getEventManager()->getKeymapper()->popKeymap(Common::kGuiKeymapName);
00145 }
00146 #endif
00147 
00148 bool GuiManager::loadNewTheme(Common::String id, ThemeEngine::GraphicsMode gfx, bool forced) {
00149     // If we are asked to reload the currently active theme, just do nothing
00150     // FIXME: Actually, why? It might be desirable at times to force a theme reload...
00151     if (!forced)
00152         if (_theme && id == _theme->getThemeId() && gfx == _theme->getGraphicsMode())
00153             return true;
00154 
00155     ThemeEngine *newTheme = 0;
00156 
00157     if (gfx == ThemeEngine::kGfxDisabled)
00158         gfx = ThemeEngine::_defaultRendererMode;
00159 
00160     // Try to load the new theme
00161     newTheme = new ThemeEngine(id, gfx);
00162     assert(newTheme);
00163 
00164     if (!newTheme->init())
00165         return false;
00166 
00167     //
00168     // Disable and delete the old theme
00169     //
00170     if (_theme)
00171         _theme->disable();
00172     delete _theme;
00173 
00174     if (_useStdCursor) {
00175         CursorMan.popCursorPalette();
00176         CursorMan.popCursor();
00177     }
00178 
00179     //
00180     // Enable the new theme
00181     //
00182     _theme = newTheme;
00183     _useStdCursor = !_theme->ownCursor();
00184 
00185     // If _stateIsSaved is set, we know that a Theme is already initialized,
00186     // thus we initialize the new theme properly
00187     if (_stateIsSaved) {
00188         _theme->enable();
00189 
00190         if (_useStdCursor)
00191             setupCursor();
00192     }
00193 
00194     // refresh all dialogs
00195     for (DialogStack::size_type i = 0; i < _dialogStack.size(); ++i)
00196         _dialogStack[i]->reflowLayout();
00197 
00198     // We need to redraw immediately. Otherwise
00199     // some other event may cause a widget to be
00200     // redrawn before redraw() has been called.
00201     _redrawStatus = kRedrawFull;
00202     redraw();
00203     _system->updateScreen();
00204 
00205     return true;
00206 }
00207 
00208 void GuiManager::redraw() {
00209     ThemeEngine::ShadingStyle shading;
00210 
00211     if (_dialogStack.empty())
00212         return;
00213 
00214     shading = (ThemeEngine::ShadingStyle)xmlEval()->getVar("Dialog." + _dialogStack.top()->_name + ".Shading", 0);
00215 
00216     // Tanoku: Do not apply shading more than once when opening many dialogs
00217     // on top of each other. Screen ends up being too dark and it's a
00218     // performance hog.
00219     if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 3)
00220         shading = ThemeEngine::kShadingNone;
00221 
00222     switch (_redrawStatus) {
00223         case kRedrawCloseDialog:
00224         case kRedrawFull:
00225         case kRedrawTopDialog:
00226             _theme->clearAll();
00227             _theme->drawToBackbuffer();
00228 
00229             for (DialogStack::size_type i = 0; i < _dialogStack.size() - 1; i++) {
00230                 _dialogStack[i]->drawDialog(kDrawLayerBackground);
00231                 _dialogStack[i]->drawDialog(kDrawLayerForeground);
00232             }
00233 
00234             // fall through
00235 
00236         case kRedrawOpenDialog:
00237             // This case is an optimization to avoid redrawing the whole dialog
00238             // stack when opening a new dialog.
00239 
00240             _theme->drawToBackbuffer();
00241 
00242             if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 1) {
00243                 Dialog *previousDialog = _dialogStack[_dialogStack.size() - 2];
00244                 previousDialog->drawDialog(kDrawLayerForeground);
00245             }
00246 
00247             _theme->applyScreenShading(shading);
00248             _dialogStack.top()->drawDialog(kDrawLayerBackground);
00249 
00250             _theme->drawToScreen();
00251             _theme->copyBackBufferToScreen();
00252 
00253             _dialogStack.top()->drawDialog(kDrawLayerForeground);
00254             break;
00255 
00256         default:
00257             break;
00258     }
00259 
00260     // Redraw the widgets that are marked as dirty
00261     _theme->drawToScreen();
00262     _dialogStack.top()->drawWidgets();
00263 
00264     _theme->updateScreen();
00265     _redrawStatus = kRedrawDisabled;
00266 }
00267 
00268 Dialog *GuiManager::getTopDialog() const {
00269     if (_dialogStack.empty())
00270         return 0;
00271     return _dialogStack.top();
00272 }
00273 
00274 void GuiManager::addToTrash(GuiObject* object, Dialog* parent) {
00275     debug(7, "Adding Gui Object %p to trash", (void *)object);
00276     GuiObjectTrashItem t;
00277     t.object = object;
00278     t.parent = 0;
00279     // If a dialog was provided, check it is in the dialog stack
00280     if (parent != 0) {
00281         for (uint i = 0 ; i < _dialogStack.size() ; ++i) {
00282             if (_dialogStack[i] == parent) {
00283                 t.parent = parent;
00284                 break;
00285             }
00286         }
00287     }
00288     _guiObjectTrash.push_back(t);
00289 }
00290 
00291 void GuiManager::runLoop() {
00292     Dialog * const activeDialog = getTopDialog();
00293     bool didSaveState = false;
00294 
00295     if (activeDialog == 0)
00296         return;
00297 
00298 #ifdef ENABLE_EVENTRECORDER
00299     // Suspend recording while GUI is shown
00300     g_eventRec.suspendRecording();
00301 #endif
00302 
00303     if (!_stateIsSaved) {
00304         saveState();
00305         _theme->enable();
00306         didSaveState = true;
00307 
00308         _useStdCursor = !_theme->ownCursor();
00309         if (_useStdCursor)
00310             setupCursor();
00311 
00312 //      _theme->refresh();
00313 
00314         _redrawStatus = kRedrawFull;
00315         redraw();
00316     }
00317 
00318     Common::EventManager *eventMan = _system->getEventManager();
00319     const uint32 targetFrameDuration = 1000 / 60;
00320 
00321     while (!_dialogStack.empty() && activeDialog == getTopDialog() && !eventMan->shouldQuit()) {
00322         uint32 frameStartTime = _system->getMillis(true);
00323 
00324         // Don't "tickle" the dialog until the theme has had a chance
00325         // to re-allocate buffers in case of a scaler change.
00326 
00327         activeDialog->handleTickle();
00328 
00329         if (_useStdCursor)
00330             animateCursor();
00331 
00332         Common::Event event;
00333 
00334         while (eventMan->pollEvent(event)) {
00335             // We will need to check whether the screen changed while polling
00336             // for an event here. While we do send EVENT_SCREEN_CHANGED
00337             // whenever this happens we still cannot be sure that we get such
00338             // an event immediately. For example, we might have an mouse move
00339             // event queued before an screen changed event. In some rare cases
00340             // this would make the GUI redraw (with the code a few lines
00341             // below) when it is not yet updated for new overlay dimensions.
00342             // As a result ScummVM would crash because it tries to copy data
00343             // outside the actual overlay screen.
00344             if (event.type != Common::EVENT_SCREEN_CHANGED) {
00345                 checkScreenChange();
00346             }
00347 
00348             // The top dialog can change during the event loop. In that case, flush all the
00349             // dialog-related events since they were probably generated while the old dialog
00350             // was still visible, and therefore not intended for the new one.
00351             //
00352             // This hopefully fixes strange behavior/crashes with pop-up widgets. (Most easily
00353             // triggered in 3x mode or when running ScummVM under Valgrind.)
00354             if (activeDialog != getTopDialog() && event.type != Common::EVENT_SCREEN_CHANGED) {
00355                 processEvent(event, getTopDialog());
00356                 continue;
00357             }
00358 
00359             processEvent(event, activeDialog);
00360         }
00361 
00362         // Delete GuiObject that have been added to the trash for a delayed deletion
00363         Common::List<GuiObjectTrashItem>::iterator it = _guiObjectTrash.begin();
00364         while (it != _guiObjectTrash.end()) {
00365             if ((*it).parent == 0 || (*it).parent == activeDialog) {
00366                 debug(7, "Delayed deletion of Gui Object %p", (void *)(*it).object);
00367                 delete (*it).object;
00368                 it = _guiObjectTrash.erase(it);
00369             } else
00370                 ++it;
00371         }
00372 
00373         if (_lastMousePosition.time + kTooltipDelay < _system->getMillis(true)) {
00374             Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
00375             if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)) {
00376                 Tooltip *tooltip = new Tooltip();
00377                 tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
00378                 tooltip->runModal();
00379                 delete tooltip;
00380             }
00381         }
00382 
00383         redraw();
00384 
00385         // Delay until the allocated frame time is elapsed to match the target frame rate
00386         uint32 actualFrameDuration = _system->getMillis(true) - frameStartTime;
00387         if (actualFrameDuration < targetFrameDuration) {
00388             _system->delayMillis(targetFrameDuration - actualFrameDuration);
00389         }
00390         _system->updateScreen();
00391     }
00392 
00393     // WORKAROUND: When quitting we might not properly close the dialogs on
00394     // the dialog stack, thus we do this here to avoid any problems.
00395     // This is most noticable in bug #3481395 "LAUNCHER: Can't quit from unsupported game dialog".
00396     // It seems that Dialog::runModal never removes the dialog from the dialog
00397     // stack, thus if the dialog does not call Dialog::close to close itself
00398     // it will never be removed. Since we can have multiple run loops being
00399     // called we cannot rely on catching EVENT_QUIT in the event loop above,
00400     // since it would only catch it for the top run loop.
00401     if (eventMan->shouldQuit() && activeDialog == getTopDialog())
00402         getTopDialog()->close();
00403 
00404     if (didSaveState) {
00405         _theme->disable();
00406         restoreState();
00407         _useStdCursor = false;
00408     }
00409 
00410 #ifdef ENABLE_EVENTRECORDER
00411     // Resume recording once GUI is shown
00412     g_eventRec.resumeRecording();
00413 #endif
00414 }
00415 
00416 #pragma mark -
00417 
00418 void GuiManager::saveState() {
00419 #ifdef ENABLE_KEYMAPPER
00420     initKeymap();
00421     pushKeymap();
00422 #endif
00423     // Backup old cursor
00424     _lastClick.x = _lastClick.y = 0;
00425     _lastClick.time = 0;
00426     _lastClick.count = 0;
00427 
00428     _stateIsSaved = true;
00429 }
00430 
00431 void GuiManager::restoreState() {
00432 #ifdef ENABLE_KEYMAPPER
00433     popKeymap();
00434 #endif
00435     if (_useStdCursor) {
00436         CursorMan.popCursor();
00437         CursorMan.popCursorPalette();
00438     }
00439 
00440     _system->updateScreen();
00441 
00442     _stateIsSaved = false;
00443 }
00444 
00445 void GuiManager::openDialog(Dialog *dialog) {
00446     giveFocusToDialog(dialog);
00447 
00448     if (!_dialogStack.empty())
00449         getTopDialog()->lostFocus();
00450 
00451     _dialogStack.push(dialog);
00452     if (_redrawStatus != kRedrawFull)
00453         _redrawStatus = kRedrawOpenDialog;
00454 
00455     // We reflow the dialog just before opening it. If the screen changed
00456     // since the last time we looked, also refresh the loaded theme,
00457     // and reflow all other open dialogs, too.
00458     if (!checkScreenChange())
00459         dialog->reflowLayout();
00460 }
00461 
00462 void GuiManager::closeTopDialog() {
00463     // Don't do anything if no dialog is open
00464     if (_dialogStack.empty())
00465         return;
00466 
00467     // Remove the dialog from the stack
00468     _dialogStack.pop()->lostFocus();
00469 
00470     if (!_dialogStack.empty()) {
00471         Dialog *dialog = getTopDialog();
00472         giveFocusToDialog(dialog);
00473     }
00474 
00475     if (_redrawStatus != kRedrawFull)
00476         _redrawStatus = kRedrawCloseDialog;
00477 
00478     redraw();
00479 }
00480 
00481 void GuiManager::setupCursor() {
00482     const byte palette[] = {
00483         255, 255, 255,
00484         255, 255, 255,
00485         171, 171, 171,
00486          87,  87,  87
00487     };
00488 
00489     CursorMan.pushCursorPalette(palette, 0, 4);
00490     CursorMan.pushCursor(NULL, 0, 0, 0, 0, 0);
00491     CursorMan.showMouse(true);
00492 }
00493 
00494 // Draw the mouse cursor (animated). This is pretty much the same as in old
00495 // SCUMM games, but the code no longer resembles what we have in cursor.cpp
00496 // very much. We could plug in a different cursor here if we like to.
00497 
00498 void GuiManager::animateCursor() {
00499     int time = _system->getMillis(true);
00500     if (time > _cursorAnimateTimer + kCursorAnimateDelay) {
00501         for (int i = 0; i < 15; i++) {
00502             if ((i < 6) || (i > 8)) {
00503                 _cursor[16 * 7 + i] = _cursorAnimateCounter;
00504                 _cursor[16 * i + 7] = _cursorAnimateCounter;
00505             }
00506         }
00507 
00508         CursorMan.replaceCursor(_cursor, 16, 16, 7, 7, 255);
00509 
00510         _cursorAnimateTimer = time;
00511         _cursorAnimateCounter = (_cursorAnimateCounter + 1) % 4;
00512     }
00513 }
00514 
00515 bool GuiManager::checkScreenChange() {
00516     int tmpScreenChangeID = _system->getScreenChangeID();
00517     if (_lastScreenChangeID != tmpScreenChangeID) {
00518         screenChange();
00519         return true;
00520     }
00521     return false;
00522 }
00523 
00524 void GuiManager::screenChange() {
00525     _lastScreenChangeID = _system->getScreenChangeID();
00526     _width = _system->getOverlayWidth();
00527     _height = _system->getOverlayHeight();
00528 
00529     // reinit the whole theme
00530     _theme->refresh();
00531 
00532     // refresh all dialogs
00533     for (DialogStack::size_type i = 0; i < _dialogStack.size(); ++i) {
00534         _dialogStack[i]->reflowLayout();
00535     }
00536     // We need to redraw immediately. Otherwise
00537     // some other event may cause a widget to be
00538     // redrawn before redraw() has been called.
00539     _redrawStatus = kRedrawFull;
00540     redraw();
00541     _system->updateScreen();
00542 }
00543 
00544 void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDialog) {
00545     if (activeDialog == 0)
00546         return;
00547     int button;
00548     uint32 time;
00549     Common::Point mouse(event.mouse.x - activeDialog->_x, event.mouse.y - activeDialog->_y);
00550 
00551     switch (event.type) {
00552     case Common::EVENT_KEYDOWN:
00553         activeDialog->handleKeyDown(event.kbd);
00554         break;
00555     case Common::EVENT_KEYUP:
00556         activeDialog->handleKeyUp(event.kbd);
00557         break;
00558     case Common::EVENT_MOUSEMOVE:
00559         _globalMousePosition.x = event.mouse.x;
00560         _globalMousePosition.y = event.mouse.y;
00561         activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
00562 
00563         if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
00564             setLastMousePos(mouse.x, mouse.y);
00565         }
00566 
00567         break;
00568         // We don't distinguish between mousebuttons (for now at least)
00569     case Common::EVENT_LBUTTONDOWN:
00570     case Common::EVENT_RBUTTONDOWN:
00571         button = (event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2);
00572         time = _system->getMillis(true);
00573         if (_lastClick.count && (time < _lastClick.time + kDoubleClickDelay)
00574             && ABS(_lastClick.x - event.mouse.x) < 3
00575             && ABS(_lastClick.y - event.mouse.y) < 3) {
00576                 _lastClick.count++;
00577         } else {
00578             _lastClick.x = event.mouse.x;
00579             _lastClick.y = event.mouse.y;
00580             _lastClick.count = 1;
00581         }
00582         _lastClick.time = time;
00583         activeDialog->handleMouseDown(mouse.x, mouse.y, button, _lastClick.count);
00584         break;
00585     case Common::EVENT_LBUTTONUP:
00586     case Common::EVENT_RBUTTONUP:
00587         button = (event.type == Common::EVENT_LBUTTONUP ? 1 : 2);
00588         activeDialog->handleMouseUp(mouse.x, mouse.y, button, _lastClick.count);
00589         break;
00590     case Common::EVENT_WHEELUP:
00591         activeDialog->handleMouseWheel(mouse.x, mouse.y, -1);
00592         break;
00593     case Common::EVENT_WHEELDOWN:
00594         activeDialog->handleMouseWheel(mouse.x, mouse.y, 1);
00595         break;
00596     case Common::EVENT_SCREEN_CHANGED:
00597         screenChange();
00598         break;
00599     default:
00600     #ifdef ENABLE_KEYMAPPER
00601         activeDialog->handleOtherEvent(event);
00602     #endif
00603         break;
00604     }
00605 }
00606 
00607 void GuiManager::scheduleTopDialogRedraw() {
00608     _redrawStatus = kRedrawTopDialog;
00609 }
00610 
00611 void GuiManager::giveFocusToDialog(Dialog *dialog) {
00612     int16 dialogX = _globalMousePosition.x - dialog->_x;
00613     int16 dialogY = _globalMousePosition.y - dialog->_y;
00614     dialog->receivedFocus(dialogX, dialogY);
00615     setLastMousePos(dialogX, dialogY);
00616 }
00617 
00618 void GuiManager::setLastMousePos(int16 x, int16 y) {
00619     _lastMousePosition.x = x;
00620     _lastMousePosition.y = y;
00621     _lastMousePosition.time = _system->getMillis(true);
00622 }
00623 
00624 } // End of namespace GUI


Generated on Sat Mar 23 2019 05:01:42 for ResidualVM by doxygen 1.7.1
curved edge   curved edge