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/scummsys.h"
24 #include "common/system.h"
25 #include "common/rect.h"
26 #include "common/textconsole.h"
27 #include "common/translation.h"
28 #include "graphics/pixelformat.h"
29 #include "graphics/svg.h"
30 #include "gui/widget.h"
31 #include "gui/gui-manager.h"
32 
33 #include "gui/ThemeEval.h"
34 
35 #include "gui/dialog.h"
36 #include "gui/widgets/popup.h"
37 #include "gui/widgets/scrollcontainer.h"
38 
39 namespace GUI {
40 
Widget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & tooltip)41 Widget::Widget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip)
42 	: GuiObject(x, y, w, h), _type(0), _boss(boss), _tooltip(tooltip),
43 	  _flags(0), _hasFocus(false), _state(ThemeEngine::kStateEnabled) {
44 	init();
45 }
46 
Widget(GuiObject * boss,const Common::String & name,const Common::U32String & tooltip)47 Widget::Widget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip)
48 	: GuiObject(name), _type(0), _boss(boss), _tooltip(tooltip),
49 	  _flags(0), _hasFocus(false), _state(ThemeEngine::kStateDisabled) {
50 	init();
51 }
52 
init()53 void Widget::init() {
54 	// Insert into the widget list of the boss
55 	_next = _boss->_firstWidget;
56 	_boss->_firstWidget = this;
57 	_needsRedraw = true;
58 }
59 
~Widget()60 Widget::~Widget() {
61 	delete _next;
62 	_next = nullptr;
63 }
64 
setFlags(int flags)65 void Widget::setFlags(int flags) {
66 	updateState(_flags, _flags | flags);
67 	_flags |= flags;
68 }
69 
clearFlags(int flags)70 void Widget::clearFlags(int flags) {
71 	updateState(_flags, _flags & ~flags);
72 	_flags &= ~flags;
73 }
74 
updateState(int oldFlags,int newFlags)75 void Widget::updateState(int oldFlags, int newFlags) {
76 	if (newFlags & WIDGET_ENABLED) {
77 		_state = ThemeEngine::kStateEnabled;
78 		if (newFlags & WIDGET_HILITED)
79 			_state = ThemeEngine::kStateHighlight;
80 		if (newFlags & WIDGET_PRESSED)
81 			_state = ThemeEngine::kStatePressed;
82 	} else {
83 		_state = ThemeEngine::kStateDisabled;
84 	}
85 }
86 
markAsDirty()87 void Widget::markAsDirty() {
88 	_needsRedraw = true;
89 
90 	Widget *w = _firstWidget;
91 	while (w) {
92 		w->markAsDirty();
93 		w = w->next();
94 	}
95 }
96 
draw()97 void Widget::draw() {
98 	if (!isVisible() || !_boss->isVisible())
99 		return;
100 
101 	if (_needsRedraw) {
102 		int oldX = _x, oldY = _y;
103 
104 		// Account for our relative position in the dialog
105 		_x = getAbsX();
106 		_y = getAbsY();
107 
108 		Common::Rect oldClip = g_gui.theme()->swapClipRect(_boss->getClipRect());
109 
110 		if (g_gui.useRTL()) {
111 			_x = g_system->getOverlayWidth() - _x - _w;
112 
113 			if (this->_name.contains("GameOptions") || this->_name.contains("GlobalOptions") || this->_name.contains("Browser") || this->_name.empty()) {
114 				/** The dialogs named above are the stacked dialogs for which the left+right paddings need to be adjusted for RTL.
115 					The _name is empty for some special widgets - like RemapWidgets, NavBars, ScrollBars and they need to be adjusted too.
116 				*/
117 				_x = _x + g_gui.getOverlayOffset();
118 			}
119 
120 			Common::Rect r = _boss->getClipRect();
121 			r.moveTo(_x, r.top);
122 
123 			g_gui.theme()->swapClipRect(r);
124 		}
125 
126 		// Draw border
127 		if (_flags & WIDGET_BORDER) {
128 			g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
129 			                                    ThemeEngine::kWidgetBackgroundBorder);
130 			_x += 4;
131 			_y += 4;
132 			_w -= 8;
133 			_h -= 8;
134 		}
135 
136 		// Now perform the actual widget draw
137 		drawWidget();
138 
139 		g_gui.theme()->swapClipRect(oldClip);
140 
141 		// Restore x/y
142 		if (_flags & WIDGET_BORDER) {
143 			_x -= 4;
144 			_y -= 4;
145 			_w += 8;
146 			_h += 8;
147 		}
148 
149 		_x = oldX;
150 		_y = oldY;
151 
152 		_needsRedraw = false;
153 	}
154 
155 	// Draw all children
156 	Widget *w = _firstWidget;
157 	while (w) {
158 		w->draw();
159 		w = w->_next;
160 	}
161 }
162 
findWidgetInChain(Widget * w,int x,int y)163 Widget *Widget::findWidgetInChain(Widget *w, int x, int y) {
164 	while (w) {
165 		// Stop as soon as we find a widget that contains the point (x,y)
166 		if (x >= w->_x && x < w->_x + w->_w && y >= w->_y && y < w->_y + w->getHeight())
167 			break;
168 		w = w->_next;
169 	}
170 	if (w)
171 		w = w->findWidget(x - w->_x, y - w->_y);
172 	return w;
173 }
174 
findWidgetInChain(Widget * w,const char * name)175 Widget *Widget::findWidgetInChain(Widget *w, const char *name) {
176 	while (w) {
177 		if (w->_name == name) {
178 			return w;
179 		}
180 		w = w->_next;
181 	}
182 	return nullptr;
183 }
184 
containsWidgetInChain(Widget * w,Widget * search)185 bool Widget::containsWidgetInChain(Widget *w, Widget *search) {
186 	while (w) {
187 		if (w == search || w->containsWidget(search))
188 			return true;
189 		w = w->_next;
190 	}
191 	return false;
192 }
193 
setEnabled(bool e)194 void Widget::setEnabled(bool e) {
195 	if ((_flags & WIDGET_ENABLED) != e) {
196 		if (e)
197 			setFlags(WIDGET_ENABLED);
198 		else
199 			clearFlags(WIDGET_ENABLED);
200 
201 		g_gui.scheduleTopDialogRedraw();
202 	}
203 }
204 
isEnabled() const205 bool Widget::isEnabled() const {
206 	return ((_flags & WIDGET_ENABLED) != 0);
207 }
208 
setVisible(bool e)209 void Widget::setVisible(bool e) {
210 	if (e)
211 		clearFlags(WIDGET_INVISIBLE);
212 	else
213 		setFlags(WIDGET_INVISIBLE);
214 }
215 
isVisible() const216 bool Widget::isVisible() const {
217 	if (g_gui.xmlEval()->getVar("Dialog." + _name + ".Visible", 1) == 0)
218 		return false;
219 
220 	return !(_flags & WIDGET_INVISIBLE);
221 }
222 
useRTL() const223 bool Widget::useRTL() const {
224 	return _useRTL;
225 }
226 
parseHotkey(const Common::U32String & label)227 uint8 Widget::parseHotkey(const Common::U32String &label) {
228 	if (!label.contains('~'))
229 		return 0;
230 
231 	int state = 0;
232 	uint8 hotkey = 0;
233 
234 	for (uint i = 0; i < label.size() && state != 3; i++) {
235 		switch (state) {
236 		case 0:
237 			if (label[i] == '~')
238 				state = 1;
239 			break;
240 		case 1:
241 			if (label[i] != '~') {
242 				state = 2;
243 				hotkey = label[i];
244 			} else
245 				state = 0;
246 			break;
247 		case 2:
248 			if (label[i] == '~')
249 				state = 3;
250 			else
251 				state = 0;
252 			break;
253 		default:
254 			break;
255 		}
256 	}
257 
258 	if (state == 3)
259 		return hotkey;
260 
261 	return 0;
262 }
263 
cleanupHotkey(const Common::U32String & label)264 Common::U32String Widget::cleanupHotkey(const Common::U32String &label) {
265 	Common::U32String res("");
266 
267 	for (Common::U32String::const_iterator itr = label.begin(); itr != label.end(); itr++) {
268 		if (*itr != '~') {
269 			res += *itr;
270 		}
271 	}
272 
273 	return res;
274 }
275 
read(const Common::U32String & str)276 void Widget::read(const Common::U32String &str) {
277 	if (ConfMan.hasKey("tts_enabled", "scummvm") &&
278 			ConfMan.getBool("tts_enabled", "scummvm")) {
279 		Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
280 		if (ttsMan == nullptr)
281 			return;
282 		ttsMan->say(str);
283 	}
284 }
285 
286 #pragma mark -
287 
StaticTextWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & text,Graphics::TextAlign align,const Common::U32String & tooltip,ThemeEngine::FontStyle font,Common::Language lang)288 StaticTextWidget::StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, Graphics::TextAlign align, const Common::U32String &tooltip, ThemeEngine::FontStyle font, Common::Language lang)
289 	: Widget(boss, x, y, w, h, tooltip) {
290 	setFlags(WIDGET_ENABLED);
291 	_type = kStaticTextWidget;
292 	_label = text;
293 	_align = Graphics::convertTextAlignH(align, g_gui.useRTL() && _useRTL);
294 	setFont(font, lang);
295 }
296 
StaticTextWidget(GuiObject * boss,const Common::String & name,const Common::U32String & text,const Common::U32String & tooltip,ThemeEngine::FontStyle font,Common::Language lang)297 StaticTextWidget::StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip, ThemeEngine::FontStyle font, Common::Language lang)
298 	: Widget(boss, name, tooltip) {
299 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
300 	_type = kStaticTextWidget;
301 	_label = text;
302 	_align = Graphics::convertTextAlignH(g_gui.xmlEval()->getWidgetTextHAlign(name), g_gui.useRTL() && _useRTL);
303 	setFont(font, lang);
304 }
305 
setValue(int value)306 void StaticTextWidget::setValue(int value) {
307 	_label = Common::String::format("%d", value);
308 }
309 
setLabel(const Common::U32String & label)310 void StaticTextWidget::setLabel(const Common::U32String &label) {
311 	if (_label != label) {
312 		_label = label;
313 
314 		markAsDirty();
315 	}
316 }
317 
setAlign(Graphics::TextAlign align)318 void StaticTextWidget::setAlign(Graphics::TextAlign align) {
319 	align = Graphics::convertTextAlignH(align, g_gui.useRTL() && _useRTL);
320 	if (_align != align){
321 		_align = align;
322 
323 		markAsDirty();
324 	}
325 }
326 
327 
drawWidget()328 void StaticTextWidget::drawWidget() {
329 	g_gui.theme()->drawText(
330 			Common::Rect(_x, _y, _x + _w, _y + _h),
331 			_label, _state, _align, ThemeEngine::kTextInversionNone, 0, true, _font
332 	);
333 }
334 
setFont(ThemeEngine::FontStyle font,Common::Language lang)335 void StaticTextWidget::setFont(ThemeEngine::FontStyle font, Common::Language lang) {
336 	_font = font;
337 
338 	if (lang == Common::UNK_LANG)
339 		return;
340 
341 	if (g_gui.theme()->loadExtraFont(font, lang))
342 		_font = GUI::ThemeEngine::kFontStyleLangExtra;
343 }
344 
345 #pragma mark -
346 
ButtonWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)347 ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
348 	: StaticTextWidget(boss, x, y, w, h, cleanupHotkey(label), Graphics::kTextAlignCenter, tooltip), CommandSender(boss),
349 	  _cmd(cmd), _hotkey(hotkey), _duringPress(false) {
350 
351 	if (hotkey == 0)
352 		_hotkey = parseHotkey(label);
353 
354 	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
355 	_type = kButtonWidget;
356 }
357 
ButtonWidget(GuiObject * boss,const Common::String & name,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)358 ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
359 	: StaticTextWidget(boss, name, cleanupHotkey(label), tooltip), CommandSender(boss),
360 	  _cmd(cmd), _hotkey(hotkey), _duringPress(false) {
361 	if (hotkey == 0)
362 		_hotkey = parseHotkey(label);
363 	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
364 	_type = kButtonWidget;
365 }
366 
getMinSize(int & minWidth,int & minHeight)367 void ButtonWidget::getMinSize(int &minWidth, int &minHeight) {
368 	const Graphics::Font &font = g_gui.getFont(_font);
369 
370 	minWidth  = font.getStringWidth(_label);
371 	minHeight = font.getFontHeight();
372 }
373 
handleMouseUp(int x,int y,int button,int clickCount)374 void ButtonWidget::handleMouseUp(int x, int y, int button, int clickCount) {
375 	if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) {
376 		setUnpressedState();
377 		sendCommand(_cmd, 0);
378 	}
379 	_duringPress = false;
380 }
381 
handleMouseDown(int x,int y,int button,int clickCount)382 void ButtonWidget::handleMouseDown(int x, int y, int button, int clickCount) {
383 	_duringPress = true;
384 	setPressedState();
385 }
386 
drawWidget()387 void ButtonWidget::drawWidget() {
388 	g_gui.theme()->drawButton(Common::Rect(_x, _y, _x + _w, _y + _h), _label, _state, getFlags());
389 }
390 
setLabel(const Common::U32String & label)391 void ButtonWidget::setLabel(const Common::U32String &label) {
392 	StaticTextWidget::setLabel(cleanupHotkey(label));
393 }
394 
setLabel(const Common::String & label)395 void ButtonWidget::setLabel(const Common::String &label) {
396 	ButtonWidget::setLabel(Common::U32String(label));
397 }
398 
addClearButton(GuiObject * boss,const Common::String & name,uint32 cmd,int x,int y,int w,int h)399 ButtonWidget *addClearButton(GuiObject *boss, const Common::String &name, uint32 cmd, int x, int y, int w, int h) {
400 	ButtonWidget *button;
401 
402 #ifndef DISABLE_FANCY_THEMES
403 	if (g_gui.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui.theme()->supportsImages()) {
404 		if (!name.empty())
405 			button = new PicButtonWidget(boss, name, _("Clear value"), cmd);
406 		else
407 			button = new PicButtonWidget(boss, x, y, w, h, _("Clear value"), cmd);
408 		((PicButtonWidget *)button)->useThemeTransparency(true);
409 		((PicButtonWidget *)button)->setGfxFromTheme(ThemeEngine::kImageEraser, kPicButtonStateEnabled, false);
410 	} else
411 #endif
412 		if (!name.empty())
413 			button = new ButtonWidget(boss, name, Common::U32String("C"), _("Clear value"), cmd);
414 		else
415 			button = new ButtonWidget(boss, x, y, w, h, Common::U32String("C"), _("Clear value"), cmd);
416 
417 	return button;
418 }
419 
setHighLighted(bool enable)420 void ButtonWidget::setHighLighted(bool enable) {
421 	(enable) ? setFlags(WIDGET_HILITED) : clearFlags(WIDGET_HILITED);
422 	markAsDirty();
423 }
424 
setPressedState()425 void ButtonWidget::setPressedState() {
426 	setFlags(WIDGET_PRESSED);
427 	clearFlags(WIDGET_HILITED);
428 	markAsDirty();
429 }
430 
setUnpressedState()431 void ButtonWidget::setUnpressedState() {
432 	clearFlags(WIDGET_PRESSED);
433 	markAsDirty();
434 }
435 
436 #pragma mark -
437 
DropdownButtonWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)438 DropdownButtonWidget::DropdownButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey) :
439 		ButtonWidget(boss, x, y, w, h, label, tooltip, cmd, hotkey) {
440 	setFlags(getFlags() | WIDGET_TRACK_MOUSE);
441 
442 	reset();
443 }
444 
DropdownButtonWidget(GuiObject * boss,const Common::String & name,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)445 DropdownButtonWidget::DropdownButtonWidget(GuiObject *boss, const Common::String &name, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey) :
446 		ButtonWidget(boss, name, label, tooltip, cmd, hotkey) {
447 	setFlags(getFlags() | WIDGET_TRACK_MOUSE);
448 
449 	reset();
450 }
451 
reset()452 void DropdownButtonWidget::reset() {
453 	_inDropdown = false;
454 	_inButton   = false;
455 	_dropdownWidth = g_gui.xmlEval()->getVar("Globals.DropdownButton.Width", 13);
456 }
457 
isInDropDown(int x,int y) const458 bool DropdownButtonWidget::isInDropDown(int x, int y) const {
459 	Common::Rect dropdownRect(_w - _dropdownWidth, 0, _w, _h);
460 	return dropdownRect.contains(x, y);
461 }
462 
handleMouseMoved(int x,int y,int button)463 void DropdownButtonWidget::handleMouseMoved(int x, int y, int button) {
464 	if (_entries.empty()) {
465 		return;
466 	}
467 
468 	// Detect which part of the button the cursor is over
469 	bool inDropdown = isInDropDown(x, y);
470 	bool inButton   = Common::Rect(_w, _h).contains(x, y) && !inDropdown;
471 
472 	if (inDropdown != _inDropdown) {
473 		_inDropdown = inDropdown;
474 		markAsDirty();
475 	}
476 
477 	if (inButton != _inButton) {
478 		_inButton = inButton;
479 		markAsDirty();
480 	}
481 }
482 
handleMouseUp(int x,int y,int button,int clickCount)483 void DropdownButtonWidget::handleMouseUp(int x, int y, int button, int clickCount) {
484 	if (isEnabled() && !_entries.empty() && _duringPress && isInDropDown(x, y)) {
485 
486 		PopUpDialog popupDialog(this, "DropdownDialog", x + getAbsX(), y + getAbsY());
487 		popupDialog.setPosition(getAbsX(), getAbsY() + _h);
488 		popupDialog.setLineHeight(_h);
489 		popupDialog.setPadding(_dropdownWidth, _dropdownWidth);
490 
491 		for (uint i = 0; i < _entries.size(); i++) {
492 			popupDialog.appendEntry(_entries[i].label);
493 		}
494 
495 		int newSel = popupDialog.runModal();
496 		if (newSel != -1) {
497 			sendCommand(_entries[newSel].cmd, 0);
498 		}
499 
500 		setUnpressedState();
501 		_duringPress = false;
502 	} else {
503 		ButtonWidget::handleMouseUp(x, y, button, clickCount);
504 	}
505 }
506 
reflowLayout()507 void DropdownButtonWidget::reflowLayout() {
508 	ButtonWidget::reflowLayout();
509 
510 	reset();
511 }
512 
getMinSize(int & minWidth,int & minHeight)513 void DropdownButtonWidget::getMinSize(int &minWidth, int &minHeight) {
514 	ButtonWidget::getMinSize(minWidth, minHeight);
515 
516 	if (minWidth >= 0) {
517 		minWidth += _dropdownWidth * 2;
518 	}
519 }
520 
appendEntry(const Common::U32String & label,uint32 cmd)521 void DropdownButtonWidget::appendEntry(const Common::U32String &label, uint32 cmd) {
522 	Entry e;
523 	e.label = label;
524 	e.cmd = cmd;
525 	_entries.push_back(e);
526 }
527 
clearEntries()528 void DropdownButtonWidget::clearEntries() {
529 	_entries.clear();
530 }
531 
drawWidget()532 void DropdownButtonWidget::drawWidget() {
533 	if (_entries.empty()) {
534 		// Degrade to a regular button
535 		g_gui.theme()->drawButton(Common::Rect(_x, _y, _x + _w, _y + _h), _label, _state);
536 	} else {
537 		g_gui.theme()->drawDropDownButton(Common::Rect(_x, _y, _x + _w, _y + _h), _dropdownWidth, _label,
538 										  _state, _inButton, _inDropdown, (g_gui.useRTL() && _useRTL));
539 	}
540 }
541 
542 #pragma mark -
543 
scaleGfx(const Graphics::ManagedSurface * gfx,int w,int h)544 const Graphics::ManagedSurface *scaleGfx(const Graphics::ManagedSurface *gfx, int w, int h) {
545 	int nw = w, nh = h;
546 
547 	// Maintain aspect ratio
548 	float xRatio = 1.0f * w / gfx->w;
549 	float yRatio = 1.0f * h / gfx->h;
550 
551 	if (xRatio < yRatio)
552 		nh = gfx->h * xRatio;
553 	else
554 		nw = gfx->w * yRatio;
555 
556 	if ((nw == w && nh == h) || (nw == gfx->w && nh == gfx->h))
557 		return gfx;
558 
559 	w = nw;
560 	h = nh;
561 
562 	Graphics::ManagedSurface tmp(*gfx);
563 
564 	const Graphics::ManagedSurface *tmp2 = new Graphics::ManagedSurface(tmp.surfacePtr()->scale(w, h, false));
565 	tmp.free();
566 
567 	return tmp2;
568 }
569 
PicButtonWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)570 PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
571 	: ButtonWidget(boss, x, y, w, h, Common::U32String(), tooltip, cmd, hotkey),
572 	  _alpha(255), _transparency(false), _showButton(true) {
573 
574 	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
575 	_type = kButtonWidget;
576 }
577 
PicButtonWidget(GuiObject * boss,const Common::String & name,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)578 PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
579 	: ButtonWidget(boss, name, Common::U32String(), tooltip, cmd, hotkey),
580 	  _alpha(255), _transparency(false), _showButton(true) {
581 	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
582 	_type = kButtonWidget;
583 }
584 
~PicButtonWidget()585 PicButtonWidget::~PicButtonWidget() {
586 	for (int i = 0; i < kPicButtonStateMax + 1; i++)
587 		_gfx[i].free();
588 }
589 
setGfx(const Graphics::ManagedSurface * gfx,int statenum,bool scale)590 void PicButtonWidget::setGfx(const Graphics::ManagedSurface *gfx, int statenum, bool scale) {
591 	_gfx[statenum].free();
592 
593 	if (!gfx || !gfx->getPixels())
594 		return;
595 
596 	if (gfx->format.bytesPerPixel == 1) {
597 		warning("PicButtonWidget::setGfx got paletted surface passed");
598 		return;
599 	}
600 
601 	if (!isVisible() || !_boss->isVisible())
602 		return;
603 
604 	float sf = g_gui.getScaleFactor();
605 	if (scale && sf != 1.0) {
606 		Graphics::Surface *tmp2 = gfx->rawSurface().scale(gfx->w * sf, gfx->h * sf, false);
607 		_gfx[statenum].copyFrom(*tmp2);
608 		tmp2->free();
609 		delete tmp2;
610 	} else {
611 		_gfx[statenum].copyFrom(*gfx);
612 	}
613 }
614 
setGfx(const Graphics::Surface * gfx,int statenum,bool scale)615 void PicButtonWidget::setGfx(const Graphics::Surface *gfx, int statenum, bool scale) {
616 	const Graphics::ManagedSurface *tmpGfx = new Graphics::ManagedSurface(gfx);
617 	setGfx(tmpGfx, statenum, scale);
618 	delete tmpGfx;
619 }
620 
setGfxFromTheme(const char * name,int statenum,bool scale)621 void PicButtonWidget::setGfxFromTheme(const char *name, int statenum, bool scale) {
622 	const Graphics::ManagedSurface *gfx = g_gui.theme()->getImageSurface(name);
623 
624 	setGfx(gfx, statenum, scale);
625 
626 	return;
627 }
628 
setGfx(int w,int h,int r,int g,int b,int statenum)629 void PicButtonWidget::setGfx(int w, int h, int r, int g, int b, int statenum) {
630 	_gfx[statenum].free();
631 
632 	if (!isVisible() || !_boss->isVisible())
633 		return;
634 
635 	if (w == -1)
636 		w = _w;
637 	if (h == -1)
638 		h = _h;
639 
640 	const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
641 
642 	_gfx[statenum].create(w, h, requiredFormat);
643 	_gfx[statenum].fillRect(Common::Rect(0, 0, w, h), _gfx[statenum].format.RGBToColor(r, g, b));
644 }
645 
drawWidget()646 void PicButtonWidget::drawWidget() {
647 	if (_showButton)
648 		g_gui.theme()->drawButton(Common::Rect(_x, _y, _x + _w, _y + _h), Common::U32String(), _state, getFlags());
649 
650 	Graphics::ManagedSurface *gfx;
651 
652 	if (_state == ThemeEngine::kStateHighlight)
653 		gfx = &_gfx[kPicButtonHighlight];
654 	else if (_state == ThemeEngine::kStateDisabled)
655 		gfx = &_gfx[kPicButtonStateDisabled];
656 	else if (_state == ThemeEngine::kStatePressed)
657 		gfx = &_gfx[kPicButtonStatePressed];
658 	else
659 		gfx = &_gfx[kPicButtonStateEnabled];
660 
661 	if (!gfx->getPixels())
662 		gfx = &_gfx[kPicButtonStateEnabled];
663 
664 	if (gfx->getPixels()) {
665 		const int x = _x + (_w - gfx->w) / 2;
666 		const int y = _y + (_h - gfx->h) / 2;
667 
668 		g_gui.theme()->drawSurface(Common::Point(x, y), *gfx, _transparency);
669 	}
670 }
671 
672 #pragma mark -
673 
CheckboxWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)674 CheckboxWidget::CheckboxWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
675 	: ButtonWidget(boss, x, y, w, h, label, tooltip, cmd, hotkey), _state(false) {
676 	setFlags(WIDGET_ENABLED);
677 	_type = kCheckboxWidget;
678 }
679 
CheckboxWidget(GuiObject * boss,const Common::String & name,const Common::U32String & label,const Common::U32String & tooltip,uint32 cmd,uint8 hotkey)680 CheckboxWidget::CheckboxWidget(GuiObject *boss, const Common::String &name, const Common::U32String &label, const Common::U32String &tooltip, uint32 cmd, uint8 hotkey)
681 	: ButtonWidget(boss, name, label, tooltip, cmd, hotkey), _state(false) {
682 	setFlags(WIDGET_ENABLED);
683 	_type = kCheckboxWidget;
684 }
685 
handleMouseUp(int x,int y,int button,int clickCount)686 void CheckboxWidget::handleMouseUp(int x, int y, int button, int clickCount) {
687 	if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) {
688 		toggleState();
689 	}
690 	_duringPress = false;
691 }
692 
setState(bool state)693 void CheckboxWidget::setState(bool state) {
694 	if (_state != state) {
695 		_state = state;
696 		//_flags ^= WIDGET_INV_BORDER;
697 		markAsDirty();
698 	}
699 	sendCommand(_cmd, _state);
700 }
701 
drawWidget()702 void CheckboxWidget::drawWidget() {
703 	g_gui.theme()->drawCheckbox(Common::Rect(_x, _y, _x + _w, _y + _h), _label, _state, Widget::_state, (g_gui.useRTL() && _useRTL));
704 }
705 
706 #pragma mark -
RadiobuttonGroup(GuiObject * boss,uint32 cmd)707 RadiobuttonGroup::RadiobuttonGroup(GuiObject *boss, uint32 cmd) : CommandSender(boss) {
708 	_value = -1;
709 	_cmd = cmd;
710 }
711 
setValue(int value)712 void RadiobuttonGroup::setValue(int value) {
713 	Common::Array<RadiobuttonWidget *>::iterator button = _buttons.begin();
714 	while (button != _buttons.end()) {
715 		(*button)->setState((*button)->getValue() == value, false);
716 
717 		button++;
718 	}
719 
720 	_value = value;
721 
722 	sendCommand(_cmd, _value);
723 }
724 
setEnabled(bool ena)725 void RadiobuttonGroup::setEnabled(bool ena) {
726 	Common::Array<RadiobuttonWidget *>::iterator button = _buttons.begin();
727 	while (button != _buttons.end()) {
728 		(*button)->setEnabled(ena);
729 
730 		button++;
731 	}
732 }
733 
734 #pragma mark -
735 
RadiobuttonWidget(GuiObject * boss,int x,int y,int w,int h,RadiobuttonGroup * group,int value,const Common::U32String & label,const Common::U32String & tooltip,uint8 hotkey)736 RadiobuttonWidget::RadiobuttonWidget(GuiObject *boss, int x, int y, int w, int h, RadiobuttonGroup *group, int value, const Common::U32String &label, const Common::U32String &tooltip, uint8 hotkey)
737 	: ButtonWidget(boss, x, y, w, h, label, tooltip, 0, hotkey), _state(false), _value(value), _group(group) {
738 	setFlags(WIDGET_ENABLED);
739 	_type = kRadiobuttonWidget;
740 	_group->addButton(this);
741 }
742 
RadiobuttonWidget(GuiObject * boss,const Common::String & name,RadiobuttonGroup * group,int value,const Common::U32String & label,const Common::U32String & tooltip,uint8 hotkey)743 RadiobuttonWidget::RadiobuttonWidget(GuiObject *boss, const Common::String &name, RadiobuttonGroup *group, int value, const Common::U32String &label, const Common::U32String &tooltip, uint8 hotkey)
744 	: ButtonWidget(boss, name, label, tooltip, 0, hotkey), _state(false), _value(value), _group(group) {
745 	setFlags(WIDGET_ENABLED);
746 	_type = kRadiobuttonWidget;
747 	_group->addButton(this);
748 }
749 
handleMouseUp(int x,int y,int button,int clickCount)750 void RadiobuttonWidget::handleMouseUp(int x, int y, int button, int clickCount) {
751 	if (isEnabled() && _duringPress && x >= 0 && x < _w && y >= 0 && y < _h) {
752 		toggleState();
753 	}
754 	_duringPress = false;
755 }
756 
setState(bool state,bool setGroup)757 void RadiobuttonWidget::setState(bool state, bool setGroup) {
758 	if (setGroup) {
759 		_group->setValue(_value);
760 		return;
761 	}
762 
763 	if (_state != state) {
764 		_state = state;
765 		//_flags ^= WIDGET_INV_BORDER;
766 		markAsDirty();
767 	}
768 	sendCommand(_cmd, _state);
769 }
770 
drawWidget()771 void RadiobuttonWidget::drawWidget() {
772 	g_gui.theme()->drawRadiobutton(Common::Rect(_x, _y, _x + _w, _y + _h), _label, _state, Widget::_state, (g_gui.useRTL() && _useRTL));
773 }
774 
775 #pragma mark -
776 
SliderWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & tooltip,uint32 cmd)777 SliderWidget::SliderWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip, uint32 cmd)
778 	: Widget(boss, x, y, w, h, tooltip), CommandSender(boss),
779 	  _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) {
780 	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
781 	_type = kSliderWidget;
782 }
783 
SliderWidget(GuiObject * boss,const Common::String & name,const Common::U32String & tooltip,uint32 cmd)784 SliderWidget::SliderWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd)
785 	: Widget(boss, name, tooltip), CommandSender(boss),
786 	  _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) {
787 	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
788 	_type = kSliderWidget;
789 }
790 
handleMouseMoved(int x,int y,int button)791 void SliderWidget::handleMouseMoved(int x, int y, int button) {
792 	if (g_gui.useRTL() && _useRTL == false) {
793 		x = _w - x;		// If internal flipping is off, adjust the mouse to behave as if it were LTR.
794 	}
795 	if (isEnabled() && _isDragging) {
796 		int newValue = posToValue(x);
797 		if (newValue < _valueMin)
798 			newValue = _valueMin;
799 		else if (newValue > _valueMax)
800 			newValue = _valueMax;
801 
802 		if (newValue != _value) {
803 			_value = newValue;
804 			markAsDirty();
805 			sendCommand(_cmd, _value);	// FIXME - hack to allow for "live update" in sound dialog
806 		}
807 	}
808 }
809 
handleMouseDown(int x,int y,int button,int clickCount)810 void SliderWidget::handleMouseDown(int x, int y, int button, int clickCount) {
811 	if (isEnabled()) {
812 		_isDragging = true;
813 		handleMouseMoved(x, y, button);
814 	}
815 }
816 
handleMouseUp(int x,int y,int button,int clickCount)817 void SliderWidget::handleMouseUp(int x, int y, int button, int clickCount) {
818 	if (isEnabled() && _isDragging) {
819 		sendCommand(_cmd, _value);
820 	}
821 	_isDragging = false;
822 }
823 
handleMouseWheel(int x,int y,int direction)824 void SliderWidget::handleMouseWheel(int x, int y, int direction) {
825 	if (isEnabled() && !_isDragging) {
826 		// Increment or decrement by one
827 		int newValue = _value - direction;
828 
829 		if (newValue < _valueMin)
830 			newValue = _valueMin;
831 		else if (newValue > _valueMax)
832 			newValue = _valueMax;
833 
834 		if (newValue != _value) {
835 			_value = newValue;
836 			markAsDirty();
837 			sendCommand(_cmd, _value);	// FIXME - hack to allow for "live update" in sound dialog
838 		}
839 	}
840 }
841 
drawWidget()842 void SliderWidget::drawWidget() {
843 	Common::Rect r1(_x, _y, _x + _w, _y + _h);
844 	g_gui.theme()->drawSlider(r1, valueToBarWidth(_value), _state, (g_gui.useRTL() && _useRTL));
845 }
846 
valueToBarWidth(int value)847 int SliderWidget::valueToBarWidth(int value) {
848 	value = CLIP(value, _valueMin, _valueMax);
849 	return (_w * (value - _valueMin) / (_valueMax - _valueMin));
850 }
851 
valueToPos(int value)852 int SliderWidget::valueToPos(int value) {
853 	value = CLIP(value, _valueMin, _valueMax);
854 	return ((_w - 1) * (value - _valueMin + 1) / (_valueMax - _valueMin));
855 }
856 
posToValue(int pos)857 int SliderWidget::posToValue(int pos) {
858 	return (pos) * (_valueMax - _valueMin) / (_w - 1) + _valueMin;
859 }
860 
861 #pragma mark -
862 
GraphicsWidget(GuiObject * boss,int x,int y,int w,int h,const Common::U32String & tooltip)863 GraphicsWidget::GraphicsWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip)
864 	: Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(255), _transparency(false) {
865 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
866 	_type = kGraphicsWidget;
867 }
868 
GraphicsWidget(GuiObject * boss,const Common::String & name,const Common::U32String & tooltip)869 GraphicsWidget::GraphicsWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip)
870 	: Widget(boss, name, tooltip), _gfx(), _alpha(255), _transparency(false) {
871 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
872 	_type = kGraphicsWidget;
873 }
874 
~GraphicsWidget()875 GraphicsWidget::~GraphicsWidget() {
876 	_gfx.free();
877 }
878 
setGfx(const Graphics::ManagedSurface * gfx,bool scale)879 void GraphicsWidget::setGfx(const Graphics::ManagedSurface *gfx, bool scale) {
880 	_gfx.free();
881 
882 	if (!gfx || !gfx->getPixels())
883 		return;
884 
885 	if (gfx->format.bytesPerPixel == 1) {
886 		warning("GraphicsWidget::setGfx got paletted surface passed");
887 		return;
888 	}
889 
890 	if (!isVisible() || !_boss->isVisible())
891 		return;
892 
893 	float sf = g_gui.getScaleFactor();
894 	if (scale && sf != 1.0) {
895 		_w = gfx->w * sf;
896 		_h = gfx->h * sf;
897 	} else {
898 		_w = gfx->w;
899 		_h = gfx->h;
900 	}
901 
902 	if ((_w != gfx->w || _h != gfx->h) && _w && _h) {
903 		Graphics::Surface *tmp2 = gfx->rawSurface().scale(_w, _h, false);
904 		_gfx.copyFrom(*tmp2);
905 		tmp2->free();
906 		delete tmp2;
907 	} else {
908 		_gfx.copyFrom(*gfx);
909 	}
910 }
911 
setGfx(const Graphics::Surface * gfx,bool scale)912 void GraphicsWidget::setGfx(const Graphics::Surface *gfx, bool scale) {
913 	const Graphics::ManagedSurface *tmpGfx = new Graphics::ManagedSurface(gfx);
914 	setGfx(tmpGfx, scale);
915 	delete tmpGfx;
916 }
917 
setGfx(int w,int h,int r,int g,int b)918 void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) {
919 	_gfx.free();
920 
921 	if (!isVisible() || !_boss->isVisible())
922 		return;
923 
924 	if (w == -1)
925 		w = _w;
926 	if (h == -1)
927 		h = _h;
928 
929 	const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
930 
931 	_gfx.create(w, h, requiredFormat);
932 	_gfx.fillRect(Common::Rect(0, 0, w, h), _gfx.format.RGBToColor(r, g, b));
933 }
934 
setGfxFromTheme(const char * name)935 void GraphicsWidget::setGfxFromTheme(const char *name) {
936 	const Graphics::ManagedSurface *gfx = g_gui.theme()->getImageSurface(name);
937 
938 	setGfx(gfx, false);
939 }
940 
drawWidget()941 void GraphicsWidget::drawWidget() {
942 	if (_gfx.getPixels()) {
943 		const int x = _x + (_w - _gfx.w) / 2;
944 		const int y = _y + (_h - _gfx.h) / 2;
945 
946 		g_gui.theme()->drawSurface(Common::Point(x, y), _gfx, _transparency);
947 	}
948 }
949 
950 #pragma mark -
951 
ContainerWidget(GuiObject * boss,int x,int y,int w,int h)952 ContainerWidget::ContainerWidget(GuiObject *boss, int x, int y, int w, int h) :
953 		Widget(boss, x, y, w, h),
954 		_backgroundType(ThemeEngine::kWidgetBackgroundBorder) {
955 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
956 	_type = kContainerWidget;
957 }
958 
ContainerWidget(GuiObject * boss,const Common::String & name)959 ContainerWidget::ContainerWidget(GuiObject *boss, const Common::String &name) :
960 		Widget(boss, name),
961 		_backgroundType(ThemeEngine::kWidgetBackgroundBorder) {
962 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
963 	_type = kContainerWidget;
964 }
965 
~ContainerWidget()966 ContainerWidget::~ContainerWidget() {
967 	// We also remove the widget from the boss to avoid segfaults, when the
968 	// deleted widget is an active widget in the boss.
969 	for (Widget *w = _firstWidget; w; w = w->next()) {
970 		_boss->removeWidget(w);
971 	}
972 }
973 
containsWidget(Widget * w) const974 bool ContainerWidget::containsWidget(Widget *w) const {
975 	return containsWidgetInChain(_firstWidget, w);
976 }
977 
findWidget(int x,int y)978 Widget *ContainerWidget::findWidget(int x, int y) {
979 	Widget *w = findWidgetInChain(_firstWidget, x, y);
980 	if (w)
981 		return w;
982 	return this;
983 }
984 
removeWidget(Widget * widget)985 void ContainerWidget::removeWidget(Widget *widget) {
986 	// We also remove the widget from the boss to avoid a reference to a
987 	// widget not in the widget chain anymore.
988 	_boss->removeWidget(widget);
989 
990 	Widget::removeWidget(widget);
991 }
992 
setBackgroundType(ThemeEngine::WidgetBackground backgroundType)993 void ContainerWidget::setBackgroundType(ThemeEngine::WidgetBackground backgroundType) {
994 	_backgroundType = backgroundType;
995 }
996 
drawWidget()997 void ContainerWidget::drawWidget() {
998 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), _backgroundType);
999 }
1000 
1001 #pragma mark -
1002 
OptionsContainerWidget(GuiObject * boss,const Common::String & name,const Common::String & dialogLayout,bool scrollable,const Common::String & domain)1003 OptionsContainerWidget::OptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &dialogLayout,
1004 											   bool scrollable, const Common::String &domain) :
1005 		Widget(boss, name),
1006 		_domain(domain),
1007 		_dialogLayout(dialogLayout),
1008 		_parentDialog(nullptr),
1009 		_scrollContainer(nullptr) {
1010 
1011 	if (scrollable) {
1012 		_scrollContainer = new ScrollContainerWidget(this, name, _dialogLayout, kReflowCmd);
1013 		_scrollContainer->setTarget(this);
1014 		_scrollContainer->setBackgroundType(GUI::ThemeEngine::kWidgetBackgroundNo);
1015 	}
1016 }
1017 
~OptionsContainerWidget()1018 OptionsContainerWidget::~OptionsContainerWidget() {
1019 }
1020 
reflowLayout()1021 void OptionsContainerWidget::reflowLayout() {
1022 	Widget::reflowLayout();
1023 
1024 	if (!_dialogLayout.empty()) {
1025 		// Since different engines have different number of options,
1026 		// we have to create it every time.
1027 		defineLayout(*g_gui.xmlEval(), _dialogLayout, _name);
1028 
1029 		if (!_scrollContainer) {
1030 			g_gui.xmlEval()->reflowDialogLayout(_dialogLayout, _firstWidget);
1031 		}
1032 	}
1033 
1034 	if (_scrollContainer) {
1035 		_scrollContainer->resize(_x, _y, _w, _h, false);
1036 	}
1037 
1038 	Widget *w = _firstWidget;
1039 	while (w) {
1040 		w->reflowLayout();
1041 		w = w->next();
1042 	}
1043 }
1044 
containsWidget(Widget * widget) const1045 bool OptionsContainerWidget::containsWidget(Widget *widget) const {
1046 	return containsWidgetInChain(_firstWidget, widget);
1047 }
1048 
findWidget(int x,int y)1049 Widget *OptionsContainerWidget::findWidget(int x, int y) {
1050 	// Iterate over all child widgets and find the one which was clicked
1051 	return Widget::findWidgetInChain(_firstWidget, x, y);
1052 }
1053 
removeWidget(Widget * widget)1054 void OptionsContainerWidget::removeWidget(Widget *widget) {
1055 	_boss->removeWidget(widget);
1056 	Widget::removeWidget(widget);
1057 }
1058 
widgetsBoss()1059 GuiObject *OptionsContainerWidget::widgetsBoss() {
1060 	if (_scrollContainer) {
1061 		return _scrollContainer;
1062 	}
1063 
1064 	return this;
1065 }
1066 
1067 } // End of namespace GUI
1068