0x47 stub
[scummvm-innocent.git] / gui / PopUpWidget.cpp
blob35cfaf6d4ad37e795f66e68bb1ce71bbd62d20bc
1 /* ScummVM - Graphic Adventure Engine
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $URL$
22 * $Id$
25 #include "common/system.h"
26 #include "common/events.h"
27 #include "gui/dialog.h"
28 #include "gui/GuiManager.h"
29 #include "gui/PopUpWidget.h"
30 #include "engines/engine.h"
32 #include "gui/ThemeEval.h"
34 namespace GUI {
37 // PopUpDialog
40 class PopUpDialog : public Dialog {
41 protected:
42 PopUpWidget *_popUpBoss;
43 int _clickX, _clickY;
44 byte *_buffer;
45 int _selection;
46 uint32 _openTime;
47 bool _twoColumns;
48 int _entriesPerColumn;
50 int _leftPadding;
51 int _rightPadding;
53 public:
54 PopUpDialog(PopUpWidget *boss, int clickX, int clickY);
56 void drawDialog();
58 void handleMouseUp(int x, int y, int button, int clickCount);
59 void handleMouseWheel(int x, int y, int direction); // Scroll through entries with scroll wheel
60 void handleMouseMoved(int x, int y, int button); // Redraw selections depending on mouse position
61 void handleKeyDown(Common::KeyState state); // Scroll through entries with arrow keys etc.
63 protected:
64 void drawMenuEntry(int entry, bool hilite);
66 int findItem(int x, int y) const;
67 void setSelection(int item);
68 bool isMouseDown();
70 void moveUp();
71 void moveDown();
74 PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY)
75 : Dialog(0, 0, 16, 16),
76 _popUpBoss(boss) {
78 // Copy the selection index
79 _selection = _popUpBoss->_selectedItem;
81 // Calculate real popup dimensions
82 _x = _popUpBoss->getAbsX();
83 _y = _popUpBoss->getAbsY() - _popUpBoss->_selectedItem * kLineHeight;
84 _h = _popUpBoss->_entries.size() * kLineHeight + 2;
85 _w = _popUpBoss->_w - kLineHeight + 2;
87 _leftPadding = _popUpBoss->_leftPadding;
88 _rightPadding = _popUpBoss->_rightPadding;
90 // Perform clipping / switch to scrolling mode if we don't fit on the screen
91 // FIXME - OSystem should send out notification messages when the screen
92 // resolution changes... we could generalize CommandReceiver and CommandSender.
94 const int screenH = g_system->getOverlayHeight();
96 // HACK: For now, we do not do scrolling. Instead, we draw the dialog
97 // in two columns if it's too tall.
99 if (_h >= screenH) {
100 const int screenW = g_system->getOverlayWidth();
102 _twoColumns = true;
103 _entriesPerColumn = _popUpBoss->_entries.size() / 2;
105 if (_popUpBoss->_entries.size() & 1)
106 _entriesPerColumn++;
108 _h = _entriesPerColumn * kLineHeight + 2;
109 _w = 0;
111 for (uint i = 0; i < _popUpBoss->_entries.size(); i++) {
112 int width = g_gui.getStringWidth(_popUpBoss->_entries[i].name);
114 if (width > _w)
115 _w = width;
118 _w = 2 * _w + 10;
120 if (!(_w & 1))
121 _w++;
123 if (_popUpBoss->_selectedItem >= _entriesPerColumn) {
124 _x -= _w / 2;
125 _y = _popUpBoss->getAbsY() - (_popUpBoss->_selectedItem - _entriesPerColumn) * kLineHeight;
128 if (_w >= screenW)
129 _w = screenW - 1;
130 if (_x < 0)
131 _x = 0;
132 if (_x + _w >= screenW)
133 _x = screenW - 1 - _w;
134 } else
135 _twoColumns = false;
137 if (_h >= screenH)
138 _h = screenH - 1;
139 if (_y < 0)
140 _y = 0;
141 else if (_y + _h >= screenH)
142 _y = screenH - 1 - _h;
144 // TODO - implement scrolling if we had to move the menu, or if there are too many entries
146 // Remember original mouse position
147 _clickX = clickX - _x;
148 _clickY = clickY - _y;
150 _openTime = 0;
153 void PopUpDialog::drawDialog() {
154 // Draw the menu border
155 g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x+_w, _y+_h), 0);
157 /*if (_twoColumns)
158 g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/
160 // Draw the entries
161 int count = _popUpBoss->_entries.size();
162 for (int i = 0; i < count; i++) {
163 drawMenuEntry(i, i == _selection);
166 // The last entry may be empty. Fill it with black.
167 /*if (_twoColumns && (count & 1)) {
168 g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
171 if (_openTime == 0) {
172 // Time the popup was opened
173 _openTime = getMillis();
177 void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
178 // Mouse was released. If it wasn't moved much since the original mouse down,
179 // let the popup stay open. If it did move, assume the user made his selection.
180 int dist = (_clickX - x) * (_clickX - x) + (_clickY - y) * (_clickY - y);
181 if (dist > 3 * 3 || getMillis() - _openTime > 300) {
182 setResult(_selection);
183 close();
185 _clickX = -1;
186 _clickY = -1;
187 _openTime = (uint32)-1;
190 void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
191 if (direction < 0)
192 moveUp();
193 else if (direction > 0)
194 moveDown();
197 void PopUpDialog::handleMouseMoved(int x, int y, int button) {
198 // Compute over which item the mouse is...
199 int item = findItem(x, y);
201 if (item >= 0 && _popUpBoss->_entries[item].name.size() == 0)
202 item = -1;
204 if (item == -1 && !isMouseDown()) {
205 setSelection(_popUpBoss->_selectedItem);
206 return;
209 // ...and update the selection accordingly
210 setSelection(item);
213 void PopUpDialog::handleKeyDown(Common::KeyState state) {
214 if (state.keycode == Common::KEYCODE_ESCAPE) {
215 // Don't change the previous selection
216 setResult(-1);
217 close();
218 return;
221 if (isMouseDown())
222 return;
224 switch (state.keycode) {
225 case Common::KEYCODE_RETURN:
226 case Common::KEYCODE_KP_ENTER:
227 setResult(_selection);
228 close();
229 break;
230 case Common::KEYCODE_UP:
231 moveUp();
232 break;
233 case Common::KEYCODE_DOWN:
234 moveDown();
235 break;
236 case Common::KEYCODE_HOME:
237 setSelection(0);
238 break;
239 case Common::KEYCODE_END:
240 setSelection(_popUpBoss->_entries.size()-1);
241 break;
242 default:
243 break;
247 int PopUpDialog::findItem(int x, int y) const {
248 if (x >= 0 && x < _w && y >= 0 && y < _h) {
249 if (_twoColumns) {
250 uint entry = (y - 2) / kLineHeight;
251 if (x > _w / 2) {
252 entry += _entriesPerColumn;
254 if (entry >= _popUpBoss->_entries.size())
255 return -1;
257 return entry;
259 return (y - 2) / kLineHeight;
261 return -1;
264 void PopUpDialog::setSelection(int item) {
265 if (item != _selection) {
266 // Undraw old selection
267 if (_selection >= 0)
268 drawMenuEntry(_selection, false);
270 // Change selection
271 _selection = item;
273 // Draw new selection
274 if (item >= 0)
275 drawMenuEntry(item, true);
279 bool PopUpDialog::isMouseDown() {
280 // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
281 // Sure, we could just count mouse button up/down events, but that is cumbersome and
282 // error prone. Would be much nicer to add an API to OSystem for this...
284 return false;
287 void PopUpDialog::moveUp() {
288 if (_selection < 0) {
289 setSelection(_popUpBoss->_entries.size() - 1);
290 } else if (_selection > 0) {
291 int item = _selection;
292 do {
293 item--;
294 } while (item >= 0 && _popUpBoss->_entries[item].name.size() == 0);
295 if (item >= 0)
296 setSelection(item);
300 void PopUpDialog::moveDown() {
301 int lastItem = _popUpBoss->_entries.size() - 1;
303 if (_selection < 0) {
304 setSelection(0);
305 } else if (_selection < lastItem) {
306 int item = _selection;
307 do {
308 item++;
309 } while (item <= lastItem && _popUpBoss->_entries[item].name.size() == 0);
310 if (item <= lastItem)
311 setSelection(item);
315 void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
316 // Draw one entry of the popup menu, including selection
317 assert(entry >= 0);
318 int x, y, w;
320 if (_twoColumns) {
321 int n = _popUpBoss->_entries.size() / 2;
323 if (_popUpBoss->_entries.size() & 1)
324 n++;
326 if (entry >= n) {
327 x = _x + 1 + _w / 2;
328 y = _y + 1 + kLineHeight * (entry - n);
329 } else {
330 x = _x + 1;
331 y = _y + 1 + kLineHeight * entry;
334 w = _w / 2 - 1;
335 } else {
336 x = _x + 1;
337 y = _y + 1 + kLineHeight * entry;
338 w = _w - 2;
341 Common::String &name(_popUpBoss->_entries[entry].name);
343 if (name.size() == 0) {
344 // Draw a separator
345 g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x+w, y+kLineHeight));
346 } else {
347 g_gui.theme()->drawText(Common::Rect(x+1, y+2, x+w, y+2+kLineHeight), name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled,
348 Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding);
353 #pragma mark -
356 // PopUpWidget
359 PopUpWidget::PopUpWidget(GuiObject *boss, const String &name)
360 : Widget(boss, name), CommandSender(boss) {
361 setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
362 _type = kPopUpWidget;
364 _selectedItem = -1;
367 void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {
368 if (isEnabled()) {
369 PopUpDialog popupDialog(this, x + getAbsX(), y + getAbsY());
370 int newSel = popupDialog.runModal();
371 if (newSel != -1 && _selectedItem != newSel) {
372 _selectedItem = newSel;
373 sendCommand(kPopUpItemSelectedCmd, _entries[_selectedItem].tag);
378 void PopUpWidget::handleMouseWheel(int x, int y, int direction) {
379 int newSelection = _selectedItem + direction;
381 // Skip separator entries
382 while ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
383 _entries[newSelection].name.equals("")) {
384 newSelection += direction;
387 // Just update the selected item when we're in range
388 if ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
389 (newSelection != _selectedItem)) {
390 _selectedItem = newSelection;
391 draw();
395 void PopUpWidget::reflowLayout() {
396 _leftPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Left", 0);
397 _rightPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Right", 0);
399 Widget::reflowLayout();
402 void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
403 Entry e;
404 e.name = entry;
405 e.tag = tag;
406 _entries.push_back(e);
409 void PopUpWidget::clearEntries() {
410 _entries.clear();
411 _selectedItem = -1;
414 void PopUpWidget::setSelected(int item) {
415 if (item != _selectedItem) {
416 if (item >= 0 && item < (int)_entries.size()) {
417 _selectedItem = item;
418 } else {
419 _selectedItem = -1;
424 void PopUpWidget::setSelectedTag(uint32 tag) {
425 uint item;
426 for (item = 0; item < _entries.size(); ++item) {
427 if (_entries[item].tag == tag) {
428 setSelected(item);
429 return;
434 void PopUpWidget::drawWidget() {
435 Common::String sel;
436 if (_selectedItem >= 0)
437 sel = _entries[_selectedItem].name;
438 g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, _leftPadding, _state, Graphics::kTextAlignLeft);
441 } // End of namespace GUI