1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//
17// Based on code from ScummVM - Scumm Interpreter
18// Copyright (C) 2002-2004 The ScummVM project
19//============================================================================
20
21#include "OSystem.hxx"
22#include "EventHandler.hxx"
23#include "FrameBuffer.hxx"
24#include "FBSurface.hxx"
25#include "Font.hxx"
26#include "Menu.hxx"
27#include "Dialog.hxx"
28#include "Widget.hxx"
29#include "TabWidget.hxx"
30
31#include "ContextMenu.hxx"
32#include "PopUpWidget.hxx"
33#include "Settings.hxx"
34#include "Console.hxx"
35
36#include "Vec.hxx"
37#include "TIA.hxx"
38
39/*
40 * TODO list
41 * - add some sense of the window being "active" (i.e. in front) or not. If it
42 * was inactive and just became active, reset certain vars (like who is focused).
43 * Maybe we should just add lostFocus and receivedFocus methods to Dialog, just
44 * like we have for class Widget?
45 * ...
46 */
47// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
48Dialog::Dialog(OSystem& instance, DialogContainer& parent, const GUI::Font& font,
49 const string& title, int x, int y, int w, int h)
50 : GuiObject(instance, parent, *this, x, y, w, h),
51 _font(font),
52 _mouseWidget(nullptr),
53 _focusedWidget(nullptr),
54 _dragWidget(nullptr),
55 _defaultWidget(nullptr),
56 _okWidget(nullptr),
57 _cancelWidget(nullptr),
58 _visible(false),
59 _onTop(true),
60 _processCancel(false),
61 _title(title),
62 _th(0),
63 _layer(0),
64 _surface(nullptr),
65 _tabID(0),
66 _flags(Widget::FLAG_ENABLED | Widget::FLAG_BORDER | Widget::FLAG_CLEARBG),
67 _max_w(0),
68 _max_h(0)
69{
70 setTitle(title);
71 setDirty();
72}
73
74// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75Dialog::Dialog(OSystem& instance, DialogContainer& parent,
76 int x, int y, int w, int h)
77 : Dialog(instance, parent, instance.frameBuffer().font(), "", x, y, w, h)
78{
79}
80
81// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
82Dialog::~Dialog()
83{
84 _myFocus.list.clear();
85 _myTabList.clear();
86
87 delete _firstWidget;
88 _firstWidget = nullptr;
89
90 _buttonGroup.clear();
91}
92
93// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
94void Dialog::open()
95{
96 // Make sure we have a valid surface to draw into
97 // Technically, this shouldn't be needed until drawDialog(), but some
98 // dialogs cause drawing to occur within loadConfig()
99 if (_surface == nullptr)
100 _surface = instance().frameBuffer().allocateSurface(_w, _h);
101 else if (uInt32(_w) > _surface->width() || uInt32(_h) > _surface->height())
102 _surface->resize(_w, _h);
103 _surface->setSrcSize(_w, _h);
104 _layer = parent().addDialog(this);
105
106 // Take hidpi scaling into account
107 const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
108 _surface->setDstSize(_w * scale, _h * scale);
109
110 center();
111
112 if(_myTabList.size())
113 // (Re)-build the focus list to use for all widgets of all tabs
114 for(auto& tabfocus : _myTabList)
115 buildCurrentFocusList(tabfocus.widget->getID());
116 else
117 buildCurrentFocusList();
118
119 /*if (!_surface->attributes().blending)
120 {
121 _surface->attributes().blending = true;
122 _surface->attributes().blendalpha = 90;
123 _surface->applyAttributes();
124 }*/
125
126 loadConfig(); // has to be done AFTER (re)building the focus list
127
128 _visible = true;
129
130 setDirty();
131}
132
133// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
134void Dialog::close()
135{
136 if(_mouseWidget)
137 {
138 _mouseWidget->handleMouseLeft();
139 _mouseWidget = nullptr;
140 }
141
142 releaseFocus();
143
144 _visible = false;
145
146 parent().removeDialog();
147 setDirty();
148}
149
150// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151void Dialog::setTitle(const string& title)
152{
153 _title = title;
154 _h -= _th;
155 if(title.empty())
156 _th = 0;
157 else
158 _th = _font.getLineHeight() + 4;
159 _h += _th;
160
161 setDirty();
162}
163
164// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
165void Dialog::center()
166{
167 positionAt(instance().settings().getInt("dialogpos"));
168}
169
170// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
171void Dialog::positionAt(uInt32 pos)
172{
173 const bool fullscreen = instance().settings().getBool("fullscreen");
174 const double overscan = fullscreen ? instance().settings().getInt("tia.fs_overscan") / 200.0 : 0.0;
175 const Common::Size& screen = instance().frameBuffer().screenSize();
176 const Common::Rect& dst = _surface->dstRect();
177 // shift stacked dialogs
178 Int32 hgap = (screen.w >> 6) * _layer + screen.w * overscan;
179 Int32 vgap = (screen.w >> 6) * _layer + screen.h * overscan;
180 int top = std::min(std::max(0, Int32(screen.h - dst.h())), vgap);
181 int btm = std::max(0, Int32(screen.h - dst.h() - vgap));
182 int left = std::min(std::max(0, Int32(screen.w - dst.w())), hgap);
183 int right = std::max(0, Int32(screen.w - dst.w() - hgap));
184
185 switch (pos)
186 {
187 case 1:
188 _surface->setDstPos(left, top);
189 break;
190
191 case 2:
192 _surface->setDstPos(right, top);
193 break;
194
195 case 3:
196 _surface->setDstPos(right, btm);
197 break;
198
199 case 4:
200 _surface->setDstPos(left, btm);
201 break;
202
203 default:
204 // center
205 _surface->setDstPos((screen.w - dst.w()) >> 1, (screen.h - dst.h()) >> 1);
206 break;
207 }
208}
209
210// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
211bool Dialog::render()
212{
213 if(!_dirty || !isVisible())
214 return false;
215
216 // Draw this dialog
217 center();
218 drawDialog();
219
220 // Update dialog surface; also render any extra surfaces
221 // Extra surfaces must be rendered afterwards, so they are drawn on top
222 if(_surface->render())
223 {
224 mySurfaceStack.applyAll([](shared_ptr<FBSurface>& surface){
225 surface->render();
226 });
227 }
228 _dirty = false;
229
230 return true;
231}
232
233// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
234void Dialog::releaseFocus()
235{
236 if(_focusedWidget)
237 {
238 // remember focus of all tabs for when dialog is reopened again
239 for(auto& tabfocus : _myTabList)
240 tabfocus.saveCurrentFocus(_focusedWidget);
241
242 //_focusedWidget->lostFocus();
243 //_focusedWidget = nullptr;
244 }
245}
246
247// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
248void Dialog::addFocusWidget(Widget* w)
249{
250 if(!w)
251 return;
252
253 // All focusable widgets should retain focus
254 w->setFlags(Widget::FLAG_RETAIN_FOCUS);
255
256 _myFocus.widget = w;
257 _myFocus.list.push_back(w);
258}
259
260// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
261void Dialog::addToFocusList(WidgetArray& list)
262{
263 // All focusable widgets should retain focus
264 for(const auto& w: list)
265 w->setFlags(Widget::FLAG_RETAIN_FOCUS);
266
267 Vec::append(_myFocus.list, list);
268 _focusList = _myFocus.list;
269
270 if(list.size() > 0)
271 _myFocus.widget = list[0];
272}
273
274// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
275void Dialog::addToFocusList(WidgetArray& list, TabWidget* w, int tabId)
276{
277 // Only add the list if the tab actually exists
278 if(!w || w->getID() >= _myTabList.size())
279 return;
280
281 assert(w == _myTabList[w->getID()].widget);
282
283 // All focusable widgets should retain focus
284 for(const auto& fw: list)
285 fw->setFlags(Widget::FLAG_RETAIN_FOCUS);
286
287 // First get the appropriate focus list
288 FocusList& focus = _myTabList[w->getID()].focus;
289
290 // Now insert in the correct place in that focus list
291 uInt32 id = tabId;
292 if(id < focus.size())
293 Vec::append(focus[id].list, list);
294 else
295 {
296 // Make sure the array is large enough
297 while(focus.size() <= id)
298 focus.push_back(Focus());
299
300 Vec::append(focus[id].list, list);
301 }
302
303 if(list.size() > 0)
304 focus[id].widget = list[0];
305}
306
307// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
308void Dialog::addTabWidget(TabWidget* w)
309{
310 if(!w)
311 return;
312
313 // Make sure the array is large enough
314 uInt32 id = w->getID();
315 while(_myTabList.size() < id)
316 _myTabList.push_back(TabFocus());
317
318 _myTabList.push_back(TabFocus(w));
319}
320
321// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
322void Dialog::setFocus(Widget* w)
323{
324 // If the click occured inside a widget which is not the currently
325 // focused one, change the focus to that widget.
326 if(w && w != _focusedWidget && w->wantsFocus())
327 {
328 // Redraw widgets for new focus
329 _focusedWidget = Widget::setFocusForChain(this, getFocusList(), w, 0);
330
331 // Update current tab based on new focused widget
332 getTabIdForWidget(_focusedWidget);
333 }
334}
335
336// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
337void Dialog::buildCurrentFocusList(int tabID)
338{
339 // Yes, this is hideously complex. That's the price we pay for
340 // tab navigation ...
341 _focusList.clear();
342
343 // Remember which tab item previously had focus, if applicable
344 // This only applies if this method was called for a tab change
345 Widget* tabFocusWidget = nullptr;
346 if(tabID >= 0 && tabID < int(_myTabList.size()))
347 {
348 // Save focus in previously selected tab column,
349 // and get focus for new tab column
350 TabFocus& tabfocus = _myTabList[tabID];
351 tabfocus.saveCurrentFocus(_focusedWidget);
352 tabFocusWidget = tabfocus.getNewFocus();
353
354 _tabID = tabID;
355 }
356
357 // Add appropriate items from tablist (if present)
358 for(auto& tabfocus: _myTabList)
359 tabfocus.appendFocusList(_focusList);
360
361 // Add remaining items from main focus list
362 Vec::append(_focusList, _myFocus.list);
363
364 // Add button group at end of current focus list
365 // We do it this way for TabWidget, so that buttons are scanned
366 // *after* the widgets in the current tab
367 if(_buttonGroup.size() > 0)
368 Vec::append(_focusList, _buttonGroup);
369
370 // Finally, the moment we've all been waiting for :)
371 // Set the actual focus widget
372 if(tabFocusWidget)
373 _focusedWidget = tabFocusWidget;
374 else if(!_focusedWidget && _focusList.size() > 0)
375 _focusedWidget = _focusList[0];
376}
377
378// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
379void Dialog::addSurface(shared_ptr<FBSurface> surface)
380{
381 mySurfaceStack.push(surface);
382}
383
384// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
385void Dialog::drawDialog()
386{
387 if(!isVisible())
388 return;
389
390 FBSurface& s = surface();
391
392 // Dialog is still on top if e.g a ContextMenu is opened
393 _onTop = parent().myDialogStack.top() == this
394 || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this
395 && !parent().myDialogStack.top()->hasTitle());
396
397 if(_flags & Widget::FLAG_CLEARBG)
398 {
399 // cerr << "Dialog::drawDialog(): w = " << _w << ", h = " << _h << " @ " << &s << endl << endl;
400 s.fillRect(_x, _y + _th, _w, _h - _th, _onTop ? kDlgColor : kBGColorLo);
401 if(_th)
402 {
403 s.fillRect(_x, _y, _w, _th, _onTop ? kColorTitleBar : kColorTitleBarLo);
404 s.drawString(_font, _title, _x + 10, _y + 2 + 1, _font.getStringWidth(_title),
405 _onTop ? kColorTitleText : kColorTitleTextLo);
406 }
407 }
408 else
409 s.invalidate();
410 if(_flags & Widget::FLAG_BORDER) // currently only used by Dialog itself
411 s.frameRect(_x, _y, _w, _h, _onTop ? kColor : kShadowColor);
412
413 // Make all child widget dirty
414 Widget* w = _firstWidget;
415 Widget::setDirtyInChain(w);
416
417 // Draw all children
418 w = _firstWidget;
419 while(w)
420 {
421 w->draw();
422 w = w->_next;
423 }
424
425 // Draw outlines for focused widgets
426 // Don't change focus, since this will trigger lost and received
427 // focus events
428 if(_focusedWidget)
429 {
430 _focusedWidget = Widget::setFocusForChain(this, getFocusList(),
431 _focusedWidget, 0, false);
432 if(_focusedWidget)
433 _focusedWidget->draw(); // make sure the highlight color is drawn initially
434 }
435}
436
437// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
438void Dialog::handleText(char text)
439{
440 // Focused widget receives text events
441 if(_focusedWidget)
442 _focusedWidget->handleText(text);
443}
444
445// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
446void Dialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeated)
447{
448 Event::Type e = Event::NoType;
449
450// FIXME - I don't think this will compile!
451#if defined(RETRON77)
452 // special keys used for R77
453 if (key == KBDK_F13)
454 e = Event::UITabPrev;
455 else if (key == KBDK_BACKSPACE)
456 e = Event::UITabNext;
457#endif
458
459 // Check the keytable now, since we might get one of the above events,
460 // which must always be processed before any widget sees it.
461 if(e == Event::NoType)
462 e = instance().eventHandler().eventForKey(EventMode::kMenuMode, key, mod);
463
464 // Unless a widget has claimed all responsibility for data, we assume
465 // that if an event exists for the given data, it should have priority.
466 if(!handleNavEvent(e, repeated) && _focusedWidget)
467 {
468 if(_focusedWidget->wantsRaw() || e == Event::NoType)
469 _focusedWidget->handleKeyDown(key, mod);
470 else
471 _focusedWidget->handleEvent(e);
472 }
473}
474
475// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
476void Dialog::handleKeyUp(StellaKey key, StellaMod mod)
477{
478 // Focused widget receives keyup events
479 if(_focusedWidget)
480 _focusedWidget->handleKeyUp(key, mod);
481}
482
483// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
484void Dialog::handleMouseDown(int x, int y, MouseButton b, int clickCount)
485{
486 Widget* w = findWidget(x, y);
487
488 _dragWidget = w;
489 setFocus(w);
490
491 if(w)
492 w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y),
493 b, clickCount);
494}
495
496// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
497void Dialog::handleMouseUp(int x, int y, MouseButton b, int clickCount)
498{
499 if(_focusedWidget)
500 {
501 // Lose focus on mouseup unless the widget requested to retain the focus
502 if(! (_focusedWidget->getFlags() & Widget::FLAG_RETAIN_FOCUS ))
503 releaseFocus();
504 }
505
506 Widget* w = _dragWidget;
507 if(w)
508 w->handleMouseUp(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y),
509 b, clickCount);
510
511 _dragWidget = nullptr;
512}
513
514// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
515void Dialog::handleMouseWheel(int x, int y, int direction)
516{
517 // This may look a bit backwards, but I think it makes more sense for
518 // the mouse wheel to primarily affect the widget the mouse is at than
519 // the widget that happens to be focused.
520
521 Widget* w = findWidget(x, y);
522 if(!w)
523 w = _focusedWidget;
524 if(w)
525 w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction);
526}
527
528// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
529void Dialog::handleMouseMoved(int x, int y)
530{
531 Widget* w;
532
533 if(_focusedWidget && !_dragWidget)
534 {
535 w = _focusedWidget;
536 int wx = w->getAbsX() - _x;
537 int wy = w->getAbsY() - _y;
538
539 // We still send mouseEntered/Left messages to the focused item
540 // (but to no other items).
541 bool mouseInFocusedWidget = (x >= wx && x < wx + w->_w && y >= wy && y < wy + w->_h);
542 if(mouseInFocusedWidget && _mouseWidget != w)
543 {
544 if(_mouseWidget)
545 _mouseWidget->handleMouseLeft();
546 _mouseWidget = w;
547 w->handleMouseEntered();
548 }
549 else if (!mouseInFocusedWidget && _mouseWidget == w)
550 {
551 _mouseWidget = nullptr;
552 w->handleMouseLeft();
553 }
554
555 w->handleMouseMoved(x - wx, y - wy);
556 }
557
558 // While a "drag" is in process (i.e. mouse is moved while a button is pressed),
559 // only deal with the widget in which the click originated.
560 if (_dragWidget)
561 w = _dragWidget;
562 else
563 w = findWidget(x, y);
564
565 if (_mouseWidget != w)
566 {
567 if (_mouseWidget)
568 _mouseWidget->handleMouseLeft();
569 if (w)
570 w->handleMouseEntered();
571 _mouseWidget = w;
572 }
573
574 if (w && (w->getFlags() & Widget::FLAG_TRACK_MOUSE))
575 w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y));
576}
577
578// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
579bool Dialog::handleMouseClicks(int x, int y, MouseButton b)
580{
581 Widget* w = findWidget(x, y);
582
583 if(w)
584 return w->handleMouseClicks(x - (w->getAbsX() - _x),
585 y - (w->getAbsY() - _y), b);
586 else
587 return false;
588}
589
590// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
591void Dialog::handleJoyDown(int stick, int button, bool longPress)
592{
593 Event::Type e =
594 instance().eventHandler().eventForJoyButton(EventMode::kMenuMode, stick, button);
595
596 // Unless a widget has claimed all responsibility for data, we assume
597 // that if an event exists for the given data, it should have priority.
598 if(!handleNavEvent(e) && _focusedWidget)
599 {
600 if(_focusedWidget->wantsRaw() || e == Event::NoType)
601 _focusedWidget->handleJoyDown(stick, button, longPress);
602 else
603 _focusedWidget->handleEvent(e);
604 }
605}
606
607// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
608void Dialog::handleJoyUp(int stick, int button)
609{
610 // Focused widget receives joystick events
611 if(_focusedWidget)
612 _focusedWidget->handleJoyUp(stick, button);
613}
614
615// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
616Event::Type Dialog::getJoyAxisEvent(int stick, JoyAxis axis, JoyDir adir, int button)
617{
618 return instance().eventHandler().eventForJoyAxis(EventMode::kMenuMode, stick, axis, adir, button);
619}
620
621// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
622void Dialog::handleJoyAxis(int stick, JoyAxis axis, JoyDir adir, int button)
623{
624 Event::Type e = getJoyAxisEvent(stick, axis, adir, button);
625
626 // Unless a widget has claimed all responsibility for data, we assume
627 // that if an event exists for the given data, it should have priority.
628 if(!handleNavEvent(e) && _focusedWidget)
629 {
630 if(_focusedWidget->wantsRaw() || e == Event::NoType)
631 _focusedWidget->handleJoyAxis(stick, axis, adir, button);
632 else if(adir != JoyDir::NONE)
633 _focusedWidget->handleEvent(e);
634 }
635}
636
637// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
638bool Dialog::handleJoyHat(int stick, int hat, JoyHatDir hdir, int button)
639{
640 Event::Type e =
641 instance().eventHandler().eventForJoyHat(EventMode::kMenuMode, stick, hat, hdir, button);
642
643 // Unless a widget has claimed all responsibility for data, we assume
644 // that if an event exists for the given data, it should have priority.
645 if(!handleNavEvent(e) && _focusedWidget)
646 {
647 if(_focusedWidget->wantsRaw() || e == Event::NoType)
648 return _focusedWidget->handleJoyHat(stick, hat, hdir, button);
649 else
650 return _focusedWidget->handleEvent(e);
651 }
652 return true;
653}
654
655// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
656bool Dialog::handleNavEvent(Event::Type e, bool repeated)
657{
658 switch(e)
659 {
660 case Event::UITabPrev:
661 if (cycleTab(-1))
662 return true;
663 break;
664
665 case Event::UITabNext:
666 if (cycleTab(1))
667 return true;
668 break;
669
670 case Event::UINavPrev:
671 if(_focusedWidget && !_focusedWidget->wantsTab())
672 {
673 _focusedWidget = Widget::setFocusForChain(this, getFocusList(),
674 _focusedWidget, -1);
675 // Update current tab based on new focused widget
676 getTabIdForWidget(_focusedWidget);
677
678 return true;
679 }
680 break;
681
682 case Event::UINavNext:
683 if(_focusedWidget && !_focusedWidget->wantsTab())
684 {
685 _focusedWidget = Widget::setFocusForChain(this, getFocusList(),
686 _focusedWidget, +1);
687 // Update current tab based on new focused widget
688 getTabIdForWidget(_focusedWidget);
689
690 return true;
691 }
692 break;
693
694 case Event::UIOK:
695 if(_okWidget && _okWidget->isEnabled() && !repeated)
696 {
697 // Receiving 'OK' is the same as getting the 'Select' event
698 _okWidget->handleEvent(Event::UISelect);
699 return true;
700 }
701 break;
702
703 case Event::UICancel:
704 if(_cancelWidget && _cancelWidget->isEnabled() && !repeated)
705 {
706 // Receiving 'Cancel' is the same as getting the 'Select' event
707 _cancelWidget->handleEvent(Event::UISelect);
708 return true;
709 }
710 else if(_processCancel)
711 {
712 // Some dialogs want the ability to cancel without actually having
713 // a corresponding cancel button
714 processCancel();
715 return true;
716 }
717 break;
718
719 default:
720 return false;
721 }
722 return false;
723}
724
725// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
726void Dialog::getTabIdForWidget(Widget* w)
727{
728 if(_myTabList.size() == 0 || !w)
729 return;
730
731 for(uInt32 id = 0; id < _myTabList.size(); ++id)
732 {
733 if(w->_boss == _myTabList[id].widget)
734 {
735 _tabID = id;
736 return;
737 }
738 }
739}
740
741// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
742bool Dialog::cycleTab(int direction)
743{
744 if(_tabID >= 0 && _tabID < int(_myTabList.size()))
745 {
746 _myTabList[_tabID].widget->cycleTab(direction);
747 return true;
748 }
749 return false;
750}
751
752// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
753void Dialog::handleCommand(CommandSender* sender, int cmd, int data, int id)
754{
755 switch(cmd)
756 {
757 case TabWidget::kTabChangedCmd:
758 if(_visible)
759 buildCurrentFocusList(id);
760 break;
761
762 case GuiObject::kCloseCmd:
763 close();
764 break;
765 }
766}
767
768/*
769 * Determine the widget at location (x,y) if any. Assumes the coordinates are
770 * in the local coordinate system, i.e. relative to the top left of the dialog.
771 */
772// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
773Widget* Dialog::findWidget(int x, int y) const
774{
775 return Widget::findWidgetInChain(_firstWidget, x, y);
776}
777
778// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
779void Dialog::addOKCancelBGroup(WidgetArray& wid, const GUI::Font& font,
780 const string& okText, const string& cancelText,
781 bool focusOKButton, int buttonWidth)
782{
783 const int HBORDER = 10;
784 const int VBORDER = 10;
785 const int BTN_BORDER = 20;
786 const int BUTTON_GAP = 8;
787 buttonWidth = std::max(buttonWidth,
788 std::max(font.getStringWidth("Defaults"),
789 std::max(font.getStringWidth(okText),
790 font.getStringWidth(cancelText))) + BTN_BORDER);
791 int buttonHeight = font.getLineHeight() + 4;
792
793 _w = std::max(HBORDER * 2 + buttonWidth * 2 + BUTTON_GAP, _w);
794
795#ifndef BSPF_MACOS
796 addOKWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP,
797 _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd));
798 addCancelWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER),
799 _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd));
800#else
801 addCancelWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP,
802 _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd));
803 addOKWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER),
804 _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd));
805#endif
806
807 // Note that 'focusOKButton' only takes effect when there are no other UI
808 // elements in the dialog; otherwise, the first widget of the dialog is always
809 // automatically focused first
810 // Changing this behaviour would require a fairly major refactoring of the UI code
811 if(focusOKButton)
812 {
813 wid.push_back(_okWidget);
814 wid.push_back(_cancelWidget);
815 }
816 else
817 {
818 wid.push_back(_cancelWidget);
819 wid.push_back(_okWidget);
820 }
821}
822
823// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
824void Dialog::addDefaultsOKCancelBGroup(WidgetArray& wid, const GUI::Font& font,
825 const string& okText, const string& cancelText,
826 const string& defaultsText,
827 bool focusOKButton)
828{
829 const int HBORDER = 10;
830 const int VBORDER = 10;
831 const int BTN_BORDER = 20;
832 int buttonWidth = font.getStringWidth(defaultsText) + BTN_BORDER;
833 int buttonHeight = font.getLineHeight() + 4;
834
835 addDefaultWidget(new ButtonWidget(this, font, HBORDER, _h - buttonHeight - VBORDER,
836 buttonWidth, buttonHeight, defaultsText, GuiObject::kDefaultsCmd));
837 wid.push_back(_defaultWidget);
838
839 addOKCancelBGroup(wid, font, okText, cancelText, focusOKButton, buttonWidth);
840}
841
842// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
843void Dialog::TabFocus::appendFocusList(WidgetArray& list)
844{
845 int active = widget->getActiveTab();
846
847 if(active >= 0 && active < int(focus.size()))
848 Vec::append(list, focus[active].list);
849}
850
851// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
852void Dialog::TabFocus::saveCurrentFocus(Widget* w)
853{
854 if(currentTab < focus.size() &&
855 Widget::isWidgetInChain(focus[currentTab].list, w))
856 focus[currentTab].widget = w;
857}
858
859// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
860Widget* Dialog::TabFocus::getNewFocus()
861{
862 currentTab = widget->getActiveTab();
863
864 return (currentTab < focus.size()) ? focus[currentTab].widget : nullptr;
865}
866
867// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
868bool Dialog::getDynamicBounds(uInt32& w, uInt32& h) const
869{
870 const Common::Rect& r = instance().frameBuffer().imageRect();
871 const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
872
873 if(r.w() <= FBMinimum::Width || r.h() <= FBMinimum::Height)
874 {
875 w = r.w() / scale;
876 h = r.h() / scale;
877 return false;
878 }
879 else
880 {
881 w = uInt32(0.95 * r.w() / scale);
882 h = uInt32(0.95 * r.h() / scale);
883 return true;
884 }
885}
886
887// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
888void Dialog::setSize(uInt32 w, uInt32 h, uInt32 max_w, uInt32 max_h)
889{
890 _w = std::min(w, max_w);
891 _max_w = w;
892 _h = std::min(h, max_h);
893 _max_h = h;
894}
895
896// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
897bool Dialog::shouldResize(uInt32& w, uInt32& h) const
898{
899 getDynamicBounds(w, h);
900
901 // returns true if the current size is larger than the allowed size or
902 // if the current size is smaller than the allowed and wanted size
903 return (uInt32(_w) > w || uInt32(_h) > h ||
904 (uInt32(_w) < w && uInt32(_w) < _max_w) ||
905 (uInt32(_h) < h && uInt32(_h) < _max_h));
906}
907