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

popup.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 "gui/dialog.h"
00025 #include "gui/gui-manager.h"
00026 #include "gui/widgets/popup.h"
00027 
00028 #include "gui/ThemeEval.h"
00029 
00030 namespace GUI {
00031 
00032 //
00033 // PopUpDialog
00034 //
00035 
00036 class PopUpDialog : public Dialog {
00037 protected:
00038     PopUpWidget *_popUpBoss;
00039     int         _clickX, _clickY;
00040     int         _selection;
00041     uint32      _openTime;
00042     bool        _twoColumns;
00043     int         _entriesPerColumn;
00044 
00045     int         _leftPadding;
00046     int         _rightPadding;
00047 
00048 public:
00049     PopUpDialog(PopUpWidget *boss, int clickX, int clickY);
00050 
00051     void drawDialog(DrawLayer layerToDraw) override;
00052 
00053     void handleMouseUp(int x, int y, int button, int clickCount) override;
00054     void handleMouseWheel(int x, int y, int direction) override;    // Scroll through entries with scroll wheel
00055     void handleMouseMoved(int x, int y, int button) override;   // Redraw selections depending on mouse position
00056     void handleKeyDown(Common::KeyState state) override;    // Scroll through entries with arrow keys etc.
00057 
00058 protected:
00059     void drawMenuEntry(int entry, bool hilite);
00060 
00061     int findItem(int x, int y) const;
00062     void setSelection(int item);
00063     bool isMouseDown();
00064 
00065     void moveUp();
00066     void moveDown();
00067 };
00068 
00069 PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY)
00070     : Dialog(0, 0, 16, 16),
00071     _popUpBoss(boss) {
00072     _backgroundType = ThemeEngine::kDialogBackgroundNone;
00073 
00074     _openTime = 0;
00075     _entriesPerColumn = 1;
00076 
00077     // Copy the selection index
00078     _selection = _popUpBoss->_selectedItem;
00079 
00080     // Calculate real popup dimensions
00081     _x = _popUpBoss->getAbsX();
00082     _y = _popUpBoss->getAbsY() - _popUpBoss->_selectedItem * kLineHeight;
00083     _h = _popUpBoss->_entries.size() * kLineHeight + 2;
00084     _w = _popUpBoss->_w - kLineHeight + 2;
00085 
00086     _leftPadding = _popUpBoss->_leftPadding;
00087     _rightPadding = _popUpBoss->_rightPadding;
00088 
00089     // Perform clipping / switch to scrolling mode if we don't fit on the screen
00090     // FIXME - OSystem should send out notification messages when the screen
00091     // resolution changes... we could generalize CommandReceiver and CommandSender.
00092 
00093     const int screenH = g_system->getOverlayHeight();
00094 
00095     // HACK: For now, we do not do scrolling. Instead, we draw the dialog
00096     // in two columns if it's too tall.
00097 
00098     if (_h >= screenH) {
00099         const int screenW = g_system->getOverlayWidth();
00100 
00101         _twoColumns = true;
00102         _entriesPerColumn = _popUpBoss->_entries.size() / 2;
00103 
00104         if (_popUpBoss->_entries.size() & 1)
00105             _entriesPerColumn++;
00106 
00107         _h = _entriesPerColumn * kLineHeight + 2;
00108         _w = 0;
00109 
00110         for (uint i = 0; i < _popUpBoss->_entries.size(); i++) {
00111             int width = g_gui.getStringWidth(_popUpBoss->_entries[i].name);
00112 
00113             if (width > _w)
00114                 _w = width;
00115         }
00116 
00117         _w = 2 * _w + 10;
00118 
00119         if (!(_w & 1))
00120             _w++;
00121 
00122         if (_popUpBoss->_selectedItem >= _entriesPerColumn) {
00123             _x -= _w / 2;
00124             _y = _popUpBoss->getAbsY() - (_popUpBoss->_selectedItem - _entriesPerColumn) * kLineHeight;
00125         }
00126 
00127         if (_w >= screenW)
00128             _w = screenW - 1;
00129         if (_x < 0)
00130             _x = 0;
00131         if (_x + _w >= screenW)
00132             _x = screenW - 1 - _w;
00133     } else
00134         _twoColumns = false;
00135 
00136     if (_h >= screenH)
00137         _h = screenH - 1;
00138     if (_y < 0)
00139         _y = 0;
00140     else if (_y + _h >= screenH)
00141         _y = screenH - 1 - _h;
00142 
00143     // TODO - implement scrolling if we had to move the menu, or if there are too many entries
00144 
00145     // Remember original mouse position
00146     _clickX = clickX - _x;
00147     _clickY = clickY - _y;
00148 }
00149 
00150 void PopUpDialog::drawDialog(DrawLayer layerToDraw) {
00151     Dialog::drawDialog(layerToDraw);
00152 
00153     // Draw the menu border
00154     g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), 0);
00155 
00156     /*if (_twoColumns)
00157         g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/
00158 
00159     // Draw the entries
00160     int count = _popUpBoss->_entries.size();
00161     for (int i = 0; i < count; i++) {
00162         drawMenuEntry(i, i == _selection);
00163     }
00164 
00165     // The last entry may be empty. Fill it with black.
00166     /*if (_twoColumns && (count & 1)) {
00167         g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
00168     }*/
00169 
00170     if (_openTime == 0) {
00171         // Time the popup was opened
00172         _openTime = g_system->getMillis();
00173     }
00174 }
00175 
00176 void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
00177     // Mouse was released. If it wasn't moved much since the original mouse down,
00178     // let the popup stay open. If it did move, assume the user made his selection.
00179     int dist = (_clickX - x) * (_clickX - x) + (_clickY - y) * (_clickY - y);
00180     if (dist > 3 * 3 || g_system->getMillis() - _openTime > 300) {
00181         setResult(_selection);
00182         close();
00183     }
00184     _clickX = -1;
00185     _clickY = -1;
00186     _openTime = (uint32)-1;
00187 }
00188 
00189 void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
00190     if (direction < 0)
00191         moveUp();
00192     else if (direction > 0)
00193         moveDown();
00194 }
00195 
00196 void PopUpDialog::handleMouseMoved(int x, int y, int button) {
00197     // Compute over which item the mouse is...
00198     int item = findItem(x, y);
00199 
00200     if (item >= 0 && _popUpBoss->_entries[item].name.size() == 0)
00201         item = -1;
00202 
00203     if (item == -1 && !isMouseDown()) {
00204         setSelection(_popUpBoss->_selectedItem);
00205         return;
00206     }
00207 
00208     // ...and update the selection accordingly
00209     setSelection(item);
00210 }
00211 
00212 void PopUpDialog::handleKeyDown(Common::KeyState state) {
00213     if (state.keycode == Common::KEYCODE_ESCAPE) {
00214         // Don't change the previous selection
00215         setResult(-1);
00216         close();
00217         return;
00218     }
00219 
00220     if (isMouseDown())
00221         return;
00222 
00223     switch (state.keycode) {
00224 
00225     case Common::KEYCODE_RETURN:
00226     case Common::KEYCODE_KP_ENTER:
00227         setResult(_selection);
00228         close();
00229         break;
00230 
00231     // Keypad & special keys
00232     //   - if num lock is set, we ignore the keypress
00233     //   - if num lock is not set, we fall down to the special key case
00234 
00235     case Common::KEYCODE_KP1:
00236         if (state.flags & Common::KBD_NUM)
00237             break;
00238         // fall through
00239     case Common::KEYCODE_END:
00240         setSelection(_popUpBoss->_entries.size()-1);
00241         break;
00242 
00243     case Common::KEYCODE_KP2:
00244         if (state.flags & Common::KBD_NUM)
00245             break;
00246         // fall through
00247     case Common::KEYCODE_DOWN:
00248         moveDown();
00249         break;
00250 
00251     case Common::KEYCODE_KP7:
00252         if (state.flags & Common::KBD_NUM)
00253             break;
00254         // fall through
00255     case Common::KEYCODE_HOME:
00256         setSelection(0);
00257         break;
00258 
00259     case Common::KEYCODE_KP8:
00260         if (state.flags & Common::KBD_NUM)
00261             break;
00262         // fall through
00263     case Common::KEYCODE_UP:
00264         moveUp();
00265         break;
00266 
00267     default:
00268         break;
00269     }
00270 }
00271 
00272 int PopUpDialog::findItem(int x, int y) const {
00273     if (x >= 0 && x < _w && y >= 0 && y < _h) {
00274         if (_twoColumns) {
00275             uint entry = (y - 2) / kLineHeight;
00276             if (x > _w / 2) {
00277                 entry += _entriesPerColumn;
00278 
00279                 if (entry >= _popUpBoss->_entries.size())
00280                     return -1;
00281             }
00282             return entry;
00283         }
00284         return (y - 2) / kLineHeight;
00285     }
00286     return -1;
00287 }
00288 
00289 void PopUpDialog::setSelection(int item) {
00290     if (item != _selection) {
00291         // Undraw old selection
00292         if (_selection >= 0)
00293             drawMenuEntry(_selection, false);
00294 
00295         // Change selection
00296         _selection = item;
00297 
00298         // Draw new selection
00299         if (item >= 0)
00300             drawMenuEntry(item, true);
00301     }
00302 }
00303 
00304 bool PopUpDialog::isMouseDown() {
00305     // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
00306     // Sure, we could just count mouse button up/down events, but that is cumbersome and
00307     // error prone. Would be much nicer to add an API to OSystem for this...
00308 
00309     return false;
00310 }
00311 
00312 void PopUpDialog::moveUp() {
00313     if (_selection < 0) {
00314         setSelection(_popUpBoss->_entries.size() - 1);
00315     } else if (_selection > 0) {
00316         int item = _selection;
00317         do {
00318             item--;
00319         } while (item >= 0 && _popUpBoss->_entries[item].name.size() == 0);
00320         if (item >= 0)
00321             setSelection(item);
00322     }
00323 }
00324 
00325 void PopUpDialog::moveDown() {
00326     int lastItem = _popUpBoss->_entries.size() - 1;
00327 
00328     if (_selection < 0) {
00329         setSelection(0);
00330     } else if (_selection < lastItem) {
00331         int item = _selection;
00332         do {
00333             item++;
00334         } while (item <= lastItem && _popUpBoss->_entries[item].name.size() == 0);
00335         if (item <= lastItem)
00336             setSelection(item);
00337     }
00338 }
00339 
00340 void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
00341     // Draw one entry of the popup menu, including selection
00342     assert(entry >= 0);
00343     int x, y, w;
00344 
00345     if (_twoColumns) {
00346         int n = _popUpBoss->_entries.size() / 2;
00347 
00348         if (_popUpBoss->_entries.size() & 1)
00349             n++;
00350 
00351         if (entry >= n) {
00352             x = _x + 1 + _w / 2;
00353             y = _y + 1 + kLineHeight * (entry - n);
00354         } else {
00355             x = _x + 1;
00356             y = _y + 1 + kLineHeight * entry;
00357         }
00358 
00359         w = _w / 2 - 1;
00360     } else {
00361         x = _x + 1;
00362         y = _y + 1 + kLineHeight * entry;
00363         w = _w - 2;
00364     }
00365 
00366     Common::String &name(_popUpBoss->_entries[entry].name);
00367 
00368     if (name.size() == 0) {
00369         // Draw a separator
00370         g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x + w, y + kLineHeight));
00371     } else {
00372         g_gui.theme()->drawText(
00373             Common::Rect(x + 1, y + 2, x + w, y + 2 + kLineHeight),
00374             name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled,
00375             Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding
00376         );
00377     }
00378 }
00379 
00380 
00381 #pragma mark -
00382 
00383 //
00384 // PopUpWidget
00385 //
00386 
00387 PopUpWidget::PopUpWidget(GuiObject *boss, const String &name, const char *tooltip)
00388     : Widget(boss, name, tooltip), CommandSender(boss) {
00389     setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
00390     _type = kPopUpWidget;
00391 
00392     _selectedItem = -1;
00393     _leftPadding = _rightPadding = 0;
00394 }
00395 
00396 PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip)
00397     : Widget(boss, x, y, w, h, tooltip), CommandSender(boss) {
00398     setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
00399     _type = kPopUpWidget;
00400 
00401     _selectedItem = -1;
00402 
00403     _leftPadding = _rightPadding = 0;
00404 }
00405 
00406 void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {
00407     if (isEnabled()) {
00408         PopUpDialog popupDialog(this, x + getAbsX(), y + getAbsY());
00409         int newSel = popupDialog.runModal();
00410         if (newSel != -1 && _selectedItem != newSel) {
00411             _selectedItem = newSel;
00412             sendCommand(kPopUpItemSelectedCmd, _entries[_selectedItem].tag);
00413             markAsDirty();
00414         }
00415     }
00416 }
00417 
00418 void PopUpWidget::handleMouseWheel(int x, int y, int direction) {
00419     if (isEnabled()) {
00420         int newSelection = _selectedItem + direction;
00421 
00422         // Skip separator entries
00423         while ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
00424             _entries[newSelection].name.equals("")) {
00425             newSelection += direction;
00426         }
00427 
00428         // Just update the selected item when we're in range
00429         if ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
00430             (newSelection != _selectedItem)) {
00431             _selectedItem = newSelection;
00432             sendCommand(kPopUpItemSelectedCmd, _entries[_selectedItem].tag);
00433             markAsDirty();
00434         }
00435     }
00436 }
00437 
00438 void PopUpWidget::reflowLayout() {
00439     _leftPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Left", 0);
00440     _rightPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Right", 0);
00441 
00442     Widget::reflowLayout();
00443 }
00444 
00445 void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
00446     Entry e;
00447     e.name = entry;
00448     e.tag = tag;
00449     _entries.push_back(e);
00450 }
00451 
00452 void PopUpWidget::clearEntries() {
00453     _entries.clear();
00454     _selectedItem = -1;
00455 }
00456 
00457 void PopUpWidget::setSelected(int item) {
00458     if (item != _selectedItem) {
00459         if (item >= 0 && item < (int)_entries.size()) {
00460             _selectedItem = item;
00461         } else {
00462             _selectedItem = -1;
00463         }
00464     }
00465 }
00466 
00467 void PopUpWidget::setSelectedTag(uint32 tag) {
00468     uint item;
00469     for (item = 0; item < _entries.size(); ++item) {
00470         if (_entries[item].tag == tag) {
00471             setSelected(item);
00472             return;
00473         }
00474     }
00475 }
00476 
00477 void PopUpWidget::drawWidget() {
00478     Common::String sel;
00479     if (_selectedItem >= 0)
00480         sel = _entries[_selectedItem].name;
00481     g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, _leftPadding, _state);
00482 }
00483 
00484 } // End of namespace GUI


Generated on Sat Feb 23 2019 05:01:14 for ResidualVM by doxygen 1.7.1
curved edge   curved edge