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
18#include "bspf.hxx"
19#include "BrowserDialog.hxx"
20#include "Dialog.hxx"
21#include "OSystem.hxx"
22#include "FrameBuffer.hxx"
23#include "FBSurface.hxx"
24#include "FileListWidget.hxx"
25#include "PopUpWidget.hxx"
26#include "ScrollBarWidget.hxx"
27#include "EditTextWidget.hxx"
28#include "Settings.hxx"
29#include "TabWidget.hxx"
30#include "Widget.hxx"
31#include "Font.hxx"
32#include "LauncherDialog.hxx"
33#ifdef DEBUGGER_SUPPORT
34 #include "DebuggerDialog.hxx"
35#endif
36#include "UIDialog.hxx"
37
38// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
39UIDialog::UIDialog(OSystem& osystem, DialogContainer& parent,
40 const GUI::Font& font, GuiObject* boss, int max_w, int max_h)
41 : Dialog(osystem, parent, font, "User interface settings"),
42 CommandSender(boss),
43 myFont(font),
44 myIsGlobal(boss != nullptr)
45{
46 const GUI::Font& ifont = instance().frameBuffer().infoFont();
47 const int lineHeight = font.getLineHeight(),
48 fontWidth = font.getMaxCharWidth(),
49 fontHeight = font.getFontHeight(),
50 buttonHeight = font.getLineHeight() + 4;
51
52 const int VBORDER = 8;
53 const int HBORDER = 10;
54 const int INDENT = 16;
55 const int V_GAP = 4;
56 int xpos, ypos, tabID;
57 int lwidth, pwidth, bwidth;
58 WidgetArray wid;
59 VariantList items;
60 const Common::Size& ds = instance().frameBuffer().desktopSize();
61
62 // Set real dimensions
63 setSize(64 * fontWidth + HBORDER * 2, 11 * (lineHeight + V_GAP) + V_GAP * 9 + VBORDER + _th,
64 max_w, max_h);
65
66 // The tab widget
67 myTab = new TabWidget(this, font, 2, 4 + _th, _w - 2*2, _h - _th - buttonHeight - 20);
68 addTabWidget(myTab);
69
70 //////////////////////////////////////////////////////////
71 // 1) Misc. options
72 wid.clear();
73 tabID = myTab->addTab(" Look & Feel ");
74 lwidth = font.getStringWidth("Controller repeat delay ");
75 pwidth = font.getStringWidth("Right bottom");
76 xpos = HBORDER; ypos = VBORDER;
77
78 // UI Palette
79 ypos += 1;
80 items.clear();
81 VarList::push_back(items, "Standard", "standard");
82 VarList::push_back(items, "Classic", "classic");
83 VarList::push_back(items, "Light", "light");
84 myPalettePopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight,
85 items, "Theme ", lwidth);
86 wid.push_back(myPalettePopup);
87 ypos += lineHeight + V_GAP;
88
89 // Dialog position
90 items.clear();
91 VarList::push_back(items, "Centered", 0);
92 VarList::push_back(items, "Left top", 1);
93 VarList::push_back(items, "Right top", 2);
94 VarList::push_back(items, "Right bottom", 3);
95 VarList::push_back(items, "Left bottom", 4);
96 myPositionPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight,
97 items, "Dialogs position ", lwidth);
98 wid.push_back(myPositionPopup);
99 ypos += lineHeight + V_GAP;
100
101 // Enable HiDPI mode
102 myHidpiWidget = new CheckboxWidget(myTab, font, xpos, ypos, "HiDPI mode (*)");
103 wid.push_back(myHidpiWidget);
104 ypos += lineHeight + V_GAP * 4;
105
106 // Delay between quick-selecting characters in ListWidget
107 int swidth = myPalettePopup->getWidth() - lwidth;
108 myListDelaySlider = new SliderWidget(myTab, font, xpos, ypos, swidth, lineHeight,
109 "List input delay ", 0, kListDelay,
110 font.getStringWidth("1 second"));
111 myListDelaySlider->setMinValue(0);
112 myListDelaySlider->setMaxValue(1000);
113 myListDelaySlider->setStepValue(50);
114 myListDelaySlider->setTickmarkIntervals(5);
115 wid.push_back(myListDelaySlider);
116 ypos += lineHeight + V_GAP;
117
118 // Number of lines a mouse wheel will scroll
119 myWheelLinesSlider = new SliderWidget(myTab, font, xpos, ypos, swidth, lineHeight,
120 "Mouse wheel scroll ", 0, kMouseWheel,
121 font.getStringWidth("10 lines"));
122 myWheelLinesSlider->setMinValue(1);
123 myWheelLinesSlider->setMaxValue(10);
124 myWheelLinesSlider->setTickmarkIntervals(3);
125 wid.push_back(myWheelLinesSlider);
126 ypos += lineHeight + V_GAP;
127
128 // Mouse double click speed
129 myDoubleClickSlider = new SliderWidget(myTab, font, xpos, ypos, swidth, lineHeight,
130 "Double-click speed ", 0, 0,
131 font.getStringWidth("900 ms"), " ms");
132 myDoubleClickSlider->setMinValue(100);
133 myDoubleClickSlider->setMaxValue(900);
134 myDoubleClickSlider->setStepValue(50);
135 myDoubleClickSlider->setTickmarkIntervals(8);
136 wid.push_back(myDoubleClickSlider);
137 ypos += lineHeight + V_GAP;
138
139 // Initial delay before controller input will start repeating
140 myControllerDelaySlider = new SliderWidget(myTab, font, xpos, ypos, swidth, lineHeight,
141 "Controller repeat delay ", 0, kControllerDelay,
142 font.getStringWidth("1 second"));
143 myControllerDelaySlider->setMinValue(200);
144 myControllerDelaySlider->setMaxValue(1000);
145 myControllerDelaySlider->setStepValue(100);
146 myControllerDelaySlider->setTickmarkIntervals(4);
147 wid.push_back(myControllerDelaySlider);
148 ypos += lineHeight + V_GAP;
149
150 // Controller repeat rate
151 myControllerRateSlider = new SliderWidget(myTab, font, xpos, ypos, swidth, lineHeight,
152 "Controller repeat rate ", 0, 0,
153 font.getStringWidth("30 repeats/s"), " repeats/s");
154 myControllerRateSlider->setMinValue(2);
155 myControllerRateSlider->setMaxValue(30);
156 myControllerRateSlider->setStepValue(1);
157 myControllerRateSlider->setTickmarkIntervals(14);
158 wid.push_back(myControllerRateSlider);
159
160 // Add message concerning usage
161 ypos = myTab->getHeight() - 5 - fontHeight - ifont.getFontHeight() - 10;
162 lwidth = ifont.getStringWidth("(*) Change requires application restart");
163 new StaticTextWidget(myTab, ifont, xpos, ypos, std::min(lwidth, _w - 20), fontHeight,
164 "(*) Change requires application restart");
165
166 // Add items for tab 0
167 addToFocusList(wid, myTab, tabID);
168
169 //////////////////////////////////////////////////////////
170 // 2) Launcher options
171 wid.clear();
172 tabID = myTab->addTab(" Launcher ");
173 lwidth = font.getStringWidth("Launcher height ");
174 xpos = HBORDER; ypos = VBORDER;
175
176 // ROM path
177 bwidth = font.getStringWidth("ROM path" + ELLIPSIS) + 20 + 1;
178 ButtonWidget* romButton =
179 new ButtonWidget(myTab, font, xpos, ypos, bwidth, buttonHeight,
180 "ROM path" + ELLIPSIS, kChooseRomDirCmd);
181 wid.push_back(romButton);
182 xpos = romButton->getRight() + 8;
183 myRomPath = new EditTextWidget(myTab, font, xpos, ypos + 1,
184 _w - xpos - HBORDER - 2, lineHeight, "");
185 wid.push_back(myRomPath);
186 xpos = HBORDER;
187 ypos += lineHeight + V_GAP * 4;
188
189 // Launcher width and height
190 myLauncherWidthSlider = new SliderWidget(myTab, font, xpos, ypos, "Launcher width ",
191 lwidth, kLauncherSize, 6 * fontWidth, "px");
192 myLauncherWidthSlider->setMinValue(FBMinimum::Width);
193 myLauncherWidthSlider->setMaxValue(ds.w);
194 myLauncherWidthSlider->setStepValue(10);
195 // one tickmark every ~100 pixel
196 myLauncherWidthSlider->setTickmarkIntervals((ds.w - FBMinimum::Width + 50) / 100);
197 wid.push_back(myLauncherWidthSlider);
198 ypos += lineHeight + V_GAP;
199
200 myLauncherHeightSlider = new SliderWidget(myTab, font, xpos, ypos, "Launcher height ",
201 lwidth, kLauncherSize, 6 * fontWidth, "px");
202 myLauncherHeightSlider->setMinValue(FBMinimum::Height);
203 myLauncherHeightSlider->setMaxValue(ds.h);
204 myLauncherHeightSlider->setStepValue(10);
205 // one tickmark every ~100 pixel
206 myLauncherHeightSlider->setTickmarkIntervals((ds.h - FBMinimum::Height + 50) / 100);
207 wid.push_back(myLauncherHeightSlider);
208 ypos += lineHeight + V_GAP;
209
210 // Launcher font
211 pwidth = font.getStringWidth("2x (1000x760)");
212 items.clear();
213 VarList::push_back(items, "Small", "small");
214 VarList::push_back(items, "Medium", "medium");
215 VarList::push_back(items, "Large", "large");
216 myLauncherFontPopup =
217 new PopUpWidget(myTab, font, xpos, ypos + 1, pwidth, lineHeight, items,
218 "Launcher font ", lwidth);
219 wid.push_back(myLauncherFontPopup);
220 ypos += lineHeight + V_GAP * 4;
221
222 // ROM launcher info/snapshot viewer
223 items.clear();
224 VarList::push_back(items, "Off", "0");
225 VarList::push_back(items, "1x (640x480) ", "1");
226 VarList::push_back(items, "2x (1000x760)", "2");
227 myRomViewerPopup =
228 new PopUpWidget(myTab, font, xpos, ypos + 1, pwidth, lineHeight, items,
229 "ROM info viewer ", lwidth, kRomViewer);
230 wid.push_back(myRomViewerPopup);
231 ypos += lineHeight + V_GAP;
232
233 // Snapshot path (load files)
234 xpos = HBORDER + INDENT;
235 bwidth = font.getStringWidth("Image path" + ELLIPSIS) + 20 + 1;
236 myOpenBrowserButton = new ButtonWidget(myTab, font, xpos, ypos, bwidth, buttonHeight,
237 "Image path" + ELLIPSIS, kChooseSnapLoadDirCmd);
238 wid.push_back(myOpenBrowserButton);
239
240 mySnapLoadPath = new EditTextWidget(myTab, font, HBORDER + lwidth, ypos + 1,
241 _w - lwidth - HBORDER * 2 - 2, lineHeight, "");
242 wid.push_back(mySnapLoadPath);
243 ypos += lineHeight + V_GAP * 4;
244
245 // Exit to Launcher
246 xpos = HBORDER;
247 myLauncherExitWidget = new CheckboxWidget(myTab, font, xpos + 1, ypos, "Always exit to Launcher");
248 wid.push_back(myLauncherExitWidget);
249
250 // Add message concerning usage
251 xpos = HBORDER;
252 ypos = myTab->getHeight() - 5 - fontHeight - ifont.getFontHeight() - 10;
253 lwidth = ifont.getStringWidth("(*) Changes require application restart");
254 new StaticTextWidget(myTab, ifont, xpos, ypos, std::min(lwidth, _w - 20), fontHeight,
255 "(*) Changes require application restart");
256
257 // Add items for tab 1
258 addToFocusList(wid, myTab, tabID);
259
260 // All ROM settings are disabled while in game mode
261 if(!myIsGlobal)
262 {
263 romButton->clearFlags(Widget::FLAG_ENABLED);
264 myRomPath->setEditable(false);
265 }
266
267 // Activate the first tab
268 myTab->setActiveTab(0);
269
270 // Add Defaults, OK and Cancel buttons
271 wid.clear();
272 addDefaultsOKCancelBGroup(wid, font);
273 addBGroupToFocusList(wid);
274}
275
276// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
277UIDialog::~UIDialog()
278{
279}
280
281// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
282void UIDialog::loadConfig()
283{
284 const Settings& settings = instance().settings();
285
286 // ROM path
287 myRomPath->setText(settings.getString("romdir"));
288
289 // Launcher size
290 const Common::Size& ls = settings.getSize("launcherres");
291 uInt32 w = ls.w, h = ls.h;
292
293 w = std::max(w, FBMinimum::Width);
294 h = std::max(h, FBMinimum::Height);
295 w = std::min(w, instance().frameBuffer().desktopSize().w);
296 h = std::min(h, instance().frameBuffer().desktopSize().h);
297
298 myLauncherWidthSlider->setValue(w);
299 myLauncherHeightSlider->setValue(h);
300
301 // Launcher font
302 const string& font = settings.getString("launcherfont");
303 myLauncherFontPopup->setSelected(font, "medium");
304
305 // ROM launcher info viewer
306 const string& viewer = settings.getString("romviewer");
307 myRomViewerPopup->setSelected(viewer, "0");
308
309 // ROM launcher info viewer image path
310 mySnapLoadPath->setText(settings.getString("snaploaddir"));
311
312 // Exit to launcher
313 bool exitlauncher = settings.getBool("exitlauncher");
314 myLauncherExitWidget->setState(exitlauncher);
315
316 // UI palette
317 const string& pal = settings.getString("uipalette");
318 myPalettePopup->setSelected(pal, "standard");
319
320 // Enable HiDPI mode
321 if (!instance().frameBuffer().hidpiAllowed())
322 {
323 myHidpiWidget->setState(false);
324 myHidpiWidget->setEnabled(false);
325 }
326 else
327 {
328 myHidpiWidget->setState(settings.getBool("hidpi"));
329 }
330
331 // Dialog position
332 myPositionPopup->setSelected(settings.getString("dialogpos"), "0");
333
334 // Listwidget quick delay
335 int delay = settings.getInt("listdelay");
336 myListDelaySlider->setValue(delay);
337
338 // Mouse wheel lines
339 int mw = settings.getInt("mwheel");
340 myWheelLinesSlider->setValue(mw);
341
342 // Mouse double click
343 int md = settings.getInt("mdouble");
344 myDoubleClickSlider->setValue(md);
345
346 // Controller input delay
347 int cs = settings.getInt("ctrldelay");
348 myControllerDelaySlider->setValue(cs);
349
350 // Controller input rate
351 int cr = settings.getInt("ctrlrate");
352 myControllerRateSlider->setValue(cr);
353
354 handleRomViewer();
355
356 myTab->loadConfig();
357}
358
359// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
360void UIDialog::saveConfig()
361{
362 Settings& settings = instance().settings();
363
364 // ROM path
365 settings.setValue("romdir", myRomPath->getText());
366
367 // Launcher size
368 settings.setValue("launcherres",
369 Common::Size(myLauncherWidthSlider->getValue(),
370 myLauncherHeightSlider->getValue()));
371
372 // Launcher font
373 settings.setValue("launcherfont",
374 myLauncherFontPopup->getSelectedTag().toString());
375
376 // ROM launcher info viewer
377 settings.setValue("romviewer",
378 myRomViewerPopup->getSelectedTag().toString());
379
380 // ROM launcher info viewer image path
381 settings.setValue("snaploaddir", mySnapLoadPath->getText());
382
383 // Exit to Launcher
384 settings.setValue("exitlauncher", myLauncherExitWidget->getState());
385
386 // UI palette
387 settings.setValue("uipalette",
388 myPalettePopup->getSelectedTag().toString());
389 instance().frameBuffer().setUIPalette();
390
391 // Enable HiDPI mode
392 settings.setValue("hidpi", myHidpiWidget->getState());
393
394 // Dialog position
395 settings.setValue("dialogpos", myPositionPopup->getSelectedTag().toString());
396
397 // Listwidget quick delay
398 settings.setValue("listdelay", myListDelaySlider->getValue());
399 FileListWidget::setQuickSelectDelay(myListDelaySlider->getValue());
400
401 // Mouse wheel lines
402 settings.setValue("mwheel", myWheelLinesSlider->getValue());
403 ScrollBarWidget::setWheelLines(myWheelLinesSlider->getValue());
404
405 // Mouse double click
406 settings.setValue("mdouble", myDoubleClickSlider->getValue());
407 DialogContainer::setDoubleClickDelay(myDoubleClickSlider->getValue());
408
409 // Controller input delay
410 settings.setValue("ctrldelay", myControllerDelaySlider->getValue());
411 DialogContainer::setControllerDelay(myControllerDelaySlider->getValue());
412
413 // Controller input rate
414 settings.setValue("ctrlrate", myControllerRateSlider->getValue());
415 DialogContainer::setControllerRate(myControllerRateSlider->getValue());
416
417 // Flush changes to disk and inform the OSystem
418 instance().saveConfig();
419 instance().setConfigPaths();
420}
421
422// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
423void UIDialog::setDefaults()
424{
425 switch(myTab->getActiveTab())
426 {
427 case 0: // Misc. options
428 myPalettePopup->setSelected("standard");
429 myHidpiWidget->setState(false);
430 myPositionPopup->setSelected("0");
431 myListDelaySlider->setValue(300);
432 myWheelLinesSlider->setValue(4);
433 myDoubleClickSlider->setValue(500);
434 myControllerDelaySlider->setValue(400);
435 myControllerRateSlider->setValue(20);
436 break;
437 case 1: // Launcher options
438 {
439 FilesystemNode node("~");
440 myRomPath->setText(node.getShortPath());
441 uInt32 w = std::min(instance().frameBuffer().desktopSize().w, 900u);
442 uInt32 h = std::min(instance().frameBuffer().desktopSize().h, 600u);
443 myLauncherWidthSlider->setValue(w);
444 myLauncherHeightSlider->setValue(h);
445 myLauncherFontPopup->setSelected("medium", "");
446 myRomViewerPopup->setSelected("1", "");
447 mySnapLoadPath->setText(instance().defaultLoadDir());
448 myLauncherExitWidget->setState(false);
449 break;
450 }
451 default:
452 break;
453 }
454}
455
456// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
457void UIDialog::handleCommand(CommandSender* sender, int cmd, int data, int id)
458{
459 switch(cmd)
460 {
461 case GuiObject::kOKCmd:
462 saveConfig();
463 close();
464 if(myIsGlobal) // Let the boss know romdir has changed
465 sendCommand(LauncherDialog::kRomDirChosenCmd, 0, 0);
466 break;
467
468 case GuiObject::kDefaultsCmd:
469 setDefaults();
470 break;
471
472 case kListDelay:
473 if(myListDelaySlider->getValue() == 0)
474 {
475 myListDelaySlider->setValueLabel("Off");
476 myListDelaySlider->setValueUnit("");
477 }
478 else if(myListDelaySlider->getValue() == 1000)
479 {
480 myListDelaySlider->setValueLabel("1");
481 myListDelaySlider->setValueUnit(" second");
482 }
483 else
484 {
485 myListDelaySlider->setValueUnit(" ms");
486 }
487 break;
488
489 case kMouseWheel:
490 if(myWheelLinesSlider->getValue() == 1)
491 myWheelLinesSlider->setValueUnit(" line");
492 else
493 myWheelLinesSlider->setValueUnit(" lines");
494 break;
495
496 case kControllerDelay:
497 if(myControllerDelaySlider->getValue() == 1000)
498 {
499 myControllerDelaySlider->setValueLabel("1");
500 myControllerDelaySlider->setValueUnit(" second");
501 }
502 else
503 {
504 myControllerDelaySlider->setValueUnit(" ms");
505 }
506 break;
507
508 case kChooseRomDirCmd:
509 // This dialog is resizable under certain conditions, so we need
510 // to re-create it as necessary
511 createBrowser("Select ROM directory");
512 myBrowser->show(myRomPath->getText(),
513 BrowserDialog::Directories, LauncherDialog::kRomDirChosenCmd);
514 break;
515
516 case LauncherDialog::kRomDirChosenCmd:
517 myRomPath->setText(myBrowser->getResult().getShortPath());
518 break;
519
520 case kLauncherSize:
521 case kRomViewer:
522 handleRomViewer();
523 break;
524
525 case kChooseSnapLoadDirCmd:
526 // This dialog is resizable under certain conditions, so we need
527 // to re-create it as necessary
528 createBrowser("Select snapshot load directory");
529 myBrowser->show(mySnapLoadPath->getText(),
530 BrowserDialog::Directories, kSnapLoadDirChosenCmd);
531 break;
532
533 case kSnapLoadDirChosenCmd:
534 mySnapLoadPath->setText(myBrowser->getResult().getShortPath());
535 break;
536
537 default:
538 Dialog::handleCommand(sender, cmd, data, 0);
539 break;
540 }
541}
542
543// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
544void UIDialog::handleRomViewer()
545{
546 int size = myRomViewerPopup->getSelected();
547 bool enable = myRomViewerPopup->getSelectedName() != "Off";
548 VariantList items;
549
550 myOpenBrowserButton->setEnabled(enable);
551 mySnapLoadPath->setEnabled(enable);
552
553 items.clear();
554 VarList::push_back(items, "Off", "0");
555 VarList::push_back(items, "1x (640x480) ", "1");
556 if(myLauncherWidthSlider->getValue() >= 1000 &&
557 myLauncherHeightSlider->getValue() >= 760)
558 {
559 VarList::push_back(items, "2x (1000x760)", "2");
560 }
561 else if (size == 2)
562 {
563 myRomViewerPopup->setSelected(1);
564 }
565
566 myRomViewerPopup->addItems(items);
567}
568
569// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
570void UIDialog::createBrowser(const string& title)
571{
572 uInt32 w = 0, h = 0;
573 getDynamicBounds(w, h);
574
575 // Create file browser dialog
576 if(!myBrowser || uInt32(myBrowser->getWidth()) != w ||
577 uInt32(myBrowser->getHeight()) != h)
578 myBrowser = make_unique<BrowserDialog>(this, myFont, w, h, title);
579 else
580 myBrowser->setTitle(title);
581}
582