/gui/widgets/popup.cpp
C++ | 548 lines | 376 code | 106 blank | 66 comment | 102 complexity | 37b046ef32f2d055b3b6932b458de7d5 MD5 | raw file
1/* ScummVM - Graphic Adventure Engine
2 *
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.
6 *
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.
11 *
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.
16 *
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.
20 *
21 */
22
23#include "common/system.h"
24#include "gui/gui-manager.h"
25#include "gui/widgets/popup.h"
26
27#include "gui/ThemeEval.h"
28
29namespace GUI {
30
31//
32// PopUpDialog
33//
34
35PopUpDialog::PopUpDialog(Widget *boss, const Common::String &name, int clickX, int clickY):
36 Dialog(name),
37 _boss(boss),
38 // Remember original mouse position
39 _clickX(clickX),
40 _clickY(clickY),
41 _selection(-1),
42 _initialSelection(-1),
43 _openTime(0),
44 _twoColumns(false),
45 _entriesPerColumn(1),
46 _leftPadding(0),
47 _rightPadding(0),
48 _lineHeight(kLineHeight),
49 _lastRead(-1) {
50 _backgroundType = ThemeEngine::kDialogBackgroundNone;
51 _w = _boss->getWidth();
52}
53
54void PopUpDialog::open() {
55 // Time the popup was opened
56 _openTime = g_system->getMillis();
57
58 _initialSelection = _selection;
59
60 // Calculate real popup dimensions
61 _h = _entries.size() * _lineHeight + 2;
62
63 _entriesPerColumn = 1;
64
65 // Perform clipping / switch to scrolling mode if we don't fit on the screen
66 // FIXME - OSystem should send out notification messages when the screen
67 // resolution changes... we could generalize CommandReceiver and CommandSender.
68
69 const int screenH = g_system->getOverlayHeight();
70
71 // HACK: For now, we do not do scrolling. Instead, we draw the dialog
72 // in two columns if it's too tall.
73
74 if (_h >= screenH) {
75 const int screenW = g_system->getOverlayWidth();
76
77 _twoColumns = true;
78 _entriesPerColumn = _entries.size() / 2;
79
80 if (_entries.size() & 1)
81 _entriesPerColumn++;
82
83 _h = _entriesPerColumn * _lineHeight + 2;
84 _w = 0;
85
86 for (uint i = 0; i < _entries.size(); i++) {
87 int width = g_gui.getStringWidth(_entries[i]);
88
89 if (width > _w)
90 _w = width;
91 }
92
93 _w = 2 * _w + 10;
94
95 if (!(_w & 1))
96 _w++;
97
98 if (_selection >= _entriesPerColumn) {
99 _x -= _w / 2;
100 _y = _boss->getAbsY() - (_selection - _entriesPerColumn) * _lineHeight;
101 }
102
103 if (_w >= screenW)
104 _w = screenW - 1;
105 if (_x < 0)
106 _x = 0;
107 if (_x + _w >= screenW)
108 _x = screenW - 1 - _w;
109 } else
110 _twoColumns = false;
111
112 if (_h >= screenH)
113 _h = screenH - 1;
114 if (_y < 0)
115 _y = 0;
116 else if (_y + _h >= screenH)
117 _y = screenH - 1 - _h;
118
119 // TODO - implement scrolling if we had to move the menu, or if there are too many entries
120
121 _lastRead = -1;
122
123 Dialog::open();
124}
125
126void PopUpDialog::reflowLayout() {
127}
128
129void PopUpDialog::drawDialog(DrawLayer layerToDraw) {
130 Dialog::drawDialog(layerToDraw);
131
132 if (g_gui.useRTL()) {
133 _x = g_system->getOverlayWidth() - _x - _w + g_gui.getOverlayOffset();
134 }
135
136 // Draw the menu border
137 g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), ThemeEngine::kWidgetBackgroundPlain);
138
139 /*if (_twoColumns)
140 g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/
141
142 // Draw the entries
143 int count = _entries.size();
144 for (int i = 0; i < count; i++) {
145 drawMenuEntry(i, i == _selection);
146 }
147
148 // The last entry may be empty. Fill it with black.
149 /*if (_twoColumns && (count & 1)) {
150 g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
151 }*/
152}
153
154void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
155 int absX = x + getAbsX();
156 int absY = y + getAbsY();
157
158 // Mouse was released. If it wasn't moved much since the original mouse down,
159 // let the popup stay open. If it did move, assume the user made his selection.
160 int dist = (_clickX - absX) * (_clickX - absX) + (_clickY - absY) * (_clickY - absY);
161 if (dist > 3 * 3 || g_system->getMillis() - _openTime > 300) {
162 int item = findItem(x, y);
163 setResult(item);
164 close();
165 }
166 _clickX = -1;
167 _clickY = -1;
168 _openTime = (uint32)-1;
169}
170
171void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
172 if (direction < 0)
173 moveUp();
174 else if (direction > 0)
175 moveDown();
176}
177
178void PopUpDialog::handleMouseMoved(int x, int y, int button) {
179 // Compute over which item the mouse is...
180 int item = findItem(x, y);
181
182 if (item >= 0 && _entries[item].size() == 0)
183 item = -1;
184
185 if (item == -1 && !isMouseDown()) {
186 setSelection(_initialSelection);
187 return;
188 }
189
190 // ...and update the selection accordingly
191 setSelection(item);
192 if (_lastRead != item && _entries.size() > 0 && item != -1) {
193 read(_entries[item]);
194 _lastRead = item;
195 }
196}
197
198void PopUpDialog::handleMouseLeft(int button) {
199 _lastRead = -1;
200}
201
202void PopUpDialog::read(const Common::U32String &str) {
203 if (ConfMan.hasKey("tts_enabled", "scummvm") &&
204 ConfMan.getBool("tts_enabled", "scummvm")) {
205 Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
206 if (ttsMan != nullptr)
207 ttsMan->say(str);
208 }
209}
210
211void PopUpDialog::handleKeyDown(Common::KeyState state) {
212 if (state.keycode == Common::KEYCODE_ESCAPE) {
213 // Don't change the previous selection
214 setResult(-1);
215 close();
216 return;
217 }
218
219 if (isMouseDown())
220 return;
221
222 switch (state.keycode) {
223
224 case Common::KEYCODE_RETURN:
225 case Common::KEYCODE_KP_ENTER:
226 setResult(_selection);
227 close();
228 break;
229
230 // Keypad & special keys
231 // - if num lock is set, we ignore the keypress
232 // - if num lock is not set, we fall down to the special key case
233
234 case Common::KEYCODE_KP1:
235 if (state.flags & Common::KBD_NUM)
236 break;
237 // fall through
238 case Common::KEYCODE_END:
239 setSelection(_entries.size()-1);
240 break;
241
242 case Common::KEYCODE_KP2:
243 if (state.flags & Common::KBD_NUM)
244 break;
245 // fall through
246 case Common::KEYCODE_DOWN:
247 moveDown();
248 break;
249
250 case Common::KEYCODE_KP7:
251 if (state.flags & Common::KBD_NUM)
252 break;
253 // fall through
254 case Common::KEYCODE_HOME:
255 setSelection(0);
256 break;
257
258 case Common::KEYCODE_KP8:
259 if (state.flags & Common::KBD_NUM)
260 break;
261 // fall through
262 case Common::KEYCODE_UP:
263 moveUp();
264 break;
265
266 default:
267 break;
268 }
269}
270
271void PopUpDialog::setPosition(int x, int y) {
272 _x = x;
273 _y = y;
274}
275
276void PopUpDialog::setPadding(int left, int right) {
277 _leftPadding = left;
278 _rightPadding = right;
279}
280
281void PopUpDialog::setLineHeight(int lineHeight) {
282 _lineHeight = lineHeight;
283}
284
285void PopUpDialog::setWidth(uint16 width) {
286 _w = width;
287}
288
289void PopUpDialog::appendEntry(const Common::U32String &entry) {
290 _entries.push_back(entry);
291}
292
293void PopUpDialog::clearEntries() {
294 _entries.clear();
295}
296
297int PopUpDialog::findItem(int x, int y) const {
298 if (x >= 0 && x < _w && y >= 0 && y < _h) {
299 if (_twoColumns) {
300 uint entry = (y - 2) / _lineHeight;
301 if (x > _w / 2) {
302 entry += _entriesPerColumn;
303
304 if (entry >= _entries.size())
305 return -1;
306 }
307 return entry;
308 }
309 return (y - 2) / _lineHeight;
310 }
311 return -1;
312}
313
314void PopUpDialog::setSelection(int item) {
315 if (item != _selection) {
316 // Undraw old selection
317 if (_selection >= 0)
318 drawMenuEntry(_selection, false);
319
320 // Change selection
321 _selection = item;
322
323 // Draw new selection
324 if (item >= 0)
325 drawMenuEntry(item, true);
326 }
327}
328
329bool PopUpDialog::isMouseDown() {
330 // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
331 // Sure, we could just count mouse button up/down events, but that is cumbersome and
332 // error prone. Would be much nicer to add an API to OSystem for this...
333
334 return false;
335}
336
337void PopUpDialog::moveUp() {
338 if (_selection < 0) {
339 setSelection(_entries.size() - 1);
340 } else if (_selection > 0) {
341 int item = _selection;
342 do {
343 item--;
344 } while (item >= 0 && _entries[item].size() == 0);
345 if (item >= 0)
346 setSelection(item);
347 }
348}
349
350void PopUpDialog::moveDown() {
351 int lastItem = _entries.size() - 1;
352
353 if (_selection < 0) {
354 setSelection(0);
355 } else if (_selection < lastItem) {
356 int item = _selection;
357 do {
358 item++;
359 } while (item <= lastItem && _entries[item].size() == 0);
360 if (item <= lastItem)
361 setSelection(item);
362 }
363}
364
365void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
366 // Draw one entry of the popup menu, including selection
367 assert(entry >= 0);
368 int x, y, w;
369
370 if (_twoColumns) {
371 int n = _entries.size() / 2;
372
373 if (_entries.size() & 1)
374 n++;
375
376 if (entry >= n) {
377 x = _x + 1 + _w / 2;
378 y = _y + 1 + _lineHeight * (entry - n);
379 } else {
380 x = _x + 1;
381 y = _y + 1 + _lineHeight * entry;
382 }
383
384 w = _w / 2 - 1;
385 } else {
386 x = _x + 1;
387 y = _y + 1 + _lineHeight * entry;
388 w = _w - 2;
389 }
390
391 Common::U32String &name(_entries[entry]);
392
393 Common::Rect r1(x, y, x + w, y + _lineHeight);
394 Common::Rect r2(x + 1, y + 2, x + w, y + 2 + _lineHeight);
395 Graphics::TextAlign alignment = Graphics::kTextAlignLeft;
396 int pad = _leftPadding;
397
398 if (g_gui.useRTL()) {
399 if (_twoColumns) {
400 r1.translate(this->getWidth() - w, 0); // Shift the line-separator to the "first" col of RTL popup
401 }
402
403 r2.left = g_system->getOverlayWidth() - r2.left - w + g_gui.getOverlayOffset();
404 r2.right = r2.left + w;
405
406 alignment = Graphics::kTextAlignRight;
407 pad = _rightPadding;
408 }
409
410 if (name.size() == 0) {
411 // Draw a separator
412 g_gui.theme()->drawLineSeparator(r1);
413 } else {
414 g_gui.theme()->drawText(
415 r2,
416 name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled,
417 alignment, ThemeEngine::kTextInversionNone, pad
418 );
419 }
420}
421
422
423#pragma mark -
424
425//
426// PopUpWidget
427//
428
429PopUpWidget::PopUpWidget(GuiObject *boss, const String &name, const U32String &tooltip, uint32 cmd)
430 : Widget(boss, name, tooltip), CommandSender(boss) {
431 setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
432 _type = kPopUpWidget;
433 _cmd = cmd;
434
435 _selectedItem = -1;
436 _leftPadding = _rightPadding = 0;
437}
438
439PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const U32String &tooltip, uint32 cmd)
440 : Widget(boss, x, y, w, h, tooltip), CommandSender(boss) {
441 setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
442 _type = kPopUpWidget;
443 _cmd = cmd;
444
445 _selectedItem = -1;
446
447 _leftPadding = _rightPadding = 0;
448}
449
450void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {
451 if (isEnabled()) {
452 PopUpDialog popupDialog(this, "", x + getAbsX(), y + getAbsY());
453 popupDialog.setPosition(getAbsX(), getAbsY() - _selectedItem * kLineHeight);
454 popupDialog.setPadding(_leftPadding, _rightPadding);
455 popupDialog.setWidth(getWidth() - kLineHeight + 2);
456
457
458 for (uint i = 0; i < _entries.size(); i++) {
459 popupDialog.appendEntry(_entries[i].name);
460 }
461 popupDialog.setSelection(_selectedItem);
462
463 int newSel = popupDialog.runModal();
464 if (newSel != -1 && _selectedItem != newSel) {
465 _selectedItem = newSel;
466 sendCommand(_cmd, _entries[_selectedItem].tag);
467 markAsDirty();
468 }
469 }
470}
471
472void PopUpWidget::handleMouseWheel(int x, int y, int direction) {
473 if (isEnabled()) {
474 int newSelection = _selectedItem + direction;
475
476 // Skip separator entries
477 while ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
478 _entries[newSelection].name.empty()) {
479 newSelection += direction;
480 }
481
482 // Just update the selected item when we're in range
483 if ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
484 (newSelection != _selectedItem)) {
485 _selectedItem = newSelection;
486 sendCommand(_cmd, _entries[_selectedItem].tag);
487 markAsDirty();
488 }
489 }
490}
491
492void PopUpWidget::reflowLayout() {
493 _leftPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Left", 0);
494 _rightPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Right", 0);
495
496 Widget::reflowLayout();
497}
498
499void PopUpWidget::appendEntry(const U32String &entry, uint32 tag) {
500 Entry e;
501 e.name = entry;
502 e.tag = tag;
503 _entries.push_back(e);
504}
505
506void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
507 appendEntry(U32String(entry), tag);
508}
509
510void PopUpWidget::clearEntries() {
511 _entries.clear();
512 _selectedItem = -1;
513}
514
515void PopUpWidget::setSelected(int item) {
516 if (item != _selectedItem) {
517 if (item >= 0 && item < (int)_entries.size()) {
518 _selectedItem = item;
519 } else {
520 _selectedItem = -1;
521 }
522 }
523}
524
525void PopUpWidget::setSelectedTag(uint32 tag) {
526 uint item;
527 for (item = 0; item < _entries.size(); ++item) {
528 if (_entries[item].tag == tag) {
529 setSelected(item);
530 return;
531 }
532 }
533}
534
535void PopUpWidget::drawWidget() {
536 Common::U32String sel;
537 if (_selectedItem >= 0)
538 sel = _entries[_selectedItem].name;
539
540 int pad = _leftPadding;
541
542 if (g_gui.useRTL() && _useRTL)
543 pad = _rightPadding;
544
545 g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, pad, _state, (g_gui.useRTL() && _useRTL));
546}
547
548} // End of namespace GUI