PageRenderTime 57ms CodeModel.GetById 27ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/gui/widgets/popup.cpp

https://github.com/bluegr/scummvm
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