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 "Console.hxx"
19#include "EventHandler.hxx"
20#include "Launcher.hxx"
21#include "PropsSet.hxx"
22#include "ControllerDetector.hxx"
23#include "NTSCFilter.hxx"
24#include "PopUpWidget.hxx"
25#include "MessageBox.hxx"
26
27#include "StellaSettingsDialog.hxx"
28
29// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
30StellaSettingsDialog::StellaSettingsDialog(OSystem& osystem, DialogContainer& parent,
31 const GUI::Font& font, int max_w, int max_h, Menu::AppMode mode)
32 : Dialog(osystem, parent, font, "Basic settings"),
33 myMode(mode)
34{
35 const int VBORDER = 8;
36 const int HBORDER = 10;
37 const int INDENT = 20;
38 const int buttonHeight = font.getLineHeight() + 6,
39 lineHeight = font.getLineHeight(),
40 fontWidth = font.getMaxCharWidth(),
41 buttonWidth = _font.getStringWidth("Help" + ELLIPSIS) + 32;
42 const int VGAP = 5;
43 int xpos, ypos;
44 ButtonWidget* bw = nullptr;
45
46 WidgetArray wid;
47 VariantList items;
48
49 // Set real dimensions
50 setSize(35 * fontWidth + HBORDER * 2 + 3, 14 * (lineHeight + VGAP) + VGAP * 9 + 10 + _th, max_w, max_h);
51
52 xpos = HBORDER;
53 ypos = VBORDER + _th;
54
55 bw = new ButtonWidget(this, font, xpos, ypos, _w - HBORDER * 2 - buttonWidth - 8, buttonHeight,
56 "Use Advanced Settings" + ELLIPSIS, kAdvancedSettings);
57 wid.push_back(bw);
58 bw = new ButtonWidget(this, font, bw->getRight() + 8, ypos, buttonWidth, buttonHeight,
59 "Help" + ELLIPSIS, kHelp);
60 wid.push_back(bw);
61
62 ypos += lineHeight + VGAP*4;
63
64 new StaticTextWidget(this, font, xpos, ypos + 1, "Global settings:");
65 xpos += INDENT;
66 ypos += lineHeight + VGAP;
67
68 addUIOptions(wid, xpos, ypos, font);
69 ypos += VGAP * 4;
70 addVideoOptions(wid, xpos, ypos, font);
71 ypos += VGAP * 4;
72
73 xpos -= INDENT;
74 myGameSettings = new StaticTextWidget(this, font, xpos, ypos + 1, "Game settings:");
75 xpos += INDENT;
76 ypos += lineHeight + VGAP;
77
78 addGameOptions(wid, xpos, ypos, font);
79
80 // Add Defaults, OK and Cancel buttons
81 addDefaultsOKCancelBGroup(wid, font);
82
83 addToFocusList(wid);
84}
85
86// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87void StellaSettingsDialog::addUIOptions(WidgetArray& wid, int& xpos, int& ypos, const GUI::Font& font)
88{
89 const int VGAP = 4;
90 const int lineHeight = font.getLineHeight();
91 VariantList items;
92 int pwidth = font.getStringWidth("Right bottom"); // align width with other popup
93
94 ypos += 1;
95 VarList::push_back(items, "Standard", "standard");
96 VarList::push_back(items, "Classic", "classic");
97 VarList::push_back(items, "Light", "light");
98 myThemePopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, items, "UI theme ");
99 wid.push_back(myThemePopup);
100 ypos += lineHeight + VGAP;
101
102 // Dialog position
103 items.clear();
104 VarList::push_back(items, "Centered", 0);
105 VarList::push_back(items, "Left top", 1);
106 VarList::push_back(items, "Right top", 2);
107 VarList::push_back(items, "Right bottom", 3);
108 VarList::push_back(items, "Left bottom", 4);
109 myPositionPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight,
110 items, "Dialogs position ");
111 wid.push_back(myPositionPopup);
112 ypos += lineHeight + VGAP;
113}
114
115// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
116void StellaSettingsDialog::addVideoOptions(WidgetArray& wid, int& xpos, int& ypos,
117 const GUI::Font& font)
118{
119 const int VGAP = 4;
120 const int lineHeight = font.getLineHeight(),
121 fontWidth = font.getMaxCharWidth();
122 VariantList items;
123
124 // TV effects options
125 int swidth = font.getMaxCharWidth() * 11;
126
127 // TV Mode
128 items.clear();
129 VarList::push_back(items, "Disabled", static_cast<uInt32>(NTSCFilter::Preset::OFF));
130 VarList::push_back(items, "RGB", static_cast<uInt32>(NTSCFilter::Preset::RGB));
131 VarList::push_back(items, "S-Video", static_cast<uInt32>(NTSCFilter::Preset::SVIDEO));
132 VarList::push_back(items, "Composite", static_cast<uInt32>(NTSCFilter::Preset::COMPOSITE));
133 VarList::push_back(items, "Bad adjust", static_cast<uInt32>(NTSCFilter::Preset::BAD));
134 int pwidth = font.getStringWidth("Right bottom");
135 int lwidth = font.getStringWidth("Scanline intensity ");
136
137 myTVMode = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight,
138 items, "TV mode ");
139 wid.push_back(myTVMode);
140 ypos += lineHeight + VGAP;
141
142 // Scanline intensity
143 myTVScanIntense = new SliderWidget(this, font, xpos, ypos-1, swidth, lineHeight,
144 "Scanline intensity", lwidth, kScanlinesChanged, fontWidth * 3);
145 myTVScanIntense->setMinValue(0); myTVScanIntense->setMaxValue(10);
146 myTVScanIntense->setTickmarkIntervals(2);
147 wid.push_back(myTVScanIntense);
148 ypos += lineHeight + VGAP;
149
150 // TV Phosphor blend level
151 myTVPhosLevel = new SliderWidget(this, font, xpos, ypos-1, swidth, lineHeight,
152 "Phosphor blend ", lwidth, kPhosphorChanged, fontWidth * 3);
153 myTVPhosLevel->setMinValue(0); myTVPhosLevel->setMaxValue(10);
154 myTVPhosLevel->setTickmarkIntervals(2);
155 wid.push_back(myTVPhosLevel);
156 ypos += lineHeight + VGAP;
157
158 // FS overscan
159 myTVOverscan = new SliderWidget(this, font, xpos, ypos - 1, swidth, lineHeight,
160 "Overscan ", lwidth, kOverscanChanged, fontWidth * 3);
161 myTVOverscan->setMinValue(0); myTVOverscan->setMaxValue(10);
162 myTVOverscan->setTickmarkIntervals(2);
163 wid.push_back(myTVOverscan);
164 ypos += lineHeight + VGAP;
165}
166
167// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
168void StellaSettingsDialog::addGameOptions(WidgetArray& wid, int& xpos, int& ypos, const GUI::Font& font)
169{
170 const int VGAP = 4;
171 const int lineHeight = font.getLineHeight();
172 const GUI::Font& ifont = instance().frameBuffer().infoFont();
173 VariantList ctrls;
174
175 ctrls.clear();
176 VarList::push_back(ctrls, "Auto-detect", "AUTO");
177 VarList::push_back(ctrls, "Joystick", "JOYSTICK");
178 VarList::push_back(ctrls, "Paddles", "PADDLES");
179 VarList::push_back(ctrls, "BoosterGrip", "BOOSTERGRIP");
180 VarList::push_back(ctrls, "Driving", "DRIVING");
181 VarList::push_back(ctrls, "Keyboard", "KEYBOARD");
182 VarList::push_back(ctrls, "AmigaMouse", "AMIGAMOUSE");
183 VarList::push_back(ctrls, "AtariMouse", "ATARIMOUSE");
184 VarList::push_back(ctrls, "Trakball", "TRAKBALL");
185 VarList::push_back(ctrls, "Sega Genesis", "GENESIS");
186
187 int pwidth = font.getStringWidth("Sega Genesis");
188 myLeftPortLabel = new StaticTextWidget(this, font, xpos, ypos + 1, "Left port ");
189 myLeftPort = new PopUpWidget(this, font, myLeftPortLabel->getRight(),
190 myLeftPortLabel->getTop() - 1, pwidth, lineHeight, ctrls, "", 0, kLeftCChanged);
191 wid.push_back(myLeftPort);
192 ypos += lineHeight + VGAP;
193
194 myLeftPortDetected = new StaticTextWidget(this, ifont, myLeftPort->getLeft(), ypos,
195 "Sega Genesis detected");
196 ypos += ifont.getLineHeight() + VGAP;
197
198 myRightPortLabel = new StaticTextWidget(this, font, xpos, ypos + 1, "Right port ");
199 myRightPort = new PopUpWidget(this, font, myRightPortLabel->getRight(),
200 myRightPortLabel->getTop() - 1, pwidth, lineHeight, ctrls, "", 0, kRightCChanged);
201 wid.push_back(myRightPort);
202 ypos += lineHeight + VGAP;
203 myRightPortDetected = new StaticTextWidget(this, ifont, myRightPort->getLeft(), ypos,
204 "Sega Genesis detected");
205 ypos += ifont.getLineHeight() + VGAP;
206}
207
208// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
209void StellaSettingsDialog::loadConfig()
210{
211 const Settings& settings = instance().settings();
212
213 // UI palette
214 const string& theme = settings.getString("uipalette");
215 myThemePopup->setSelected(theme, "standard");
216 // Dialog position
217 myPositionPopup->setSelected(settings.getString("dialogpos"), "0");
218
219 // TV Mode
220 myTVMode->setSelected(
221 settings.getString("tv.filter"), "0");
222
223 // TV scanline intensity
224 myTVScanIntense->setValue(valueToLevel(settings.getInt("tv.scanlines")));
225
226 // TV phosphor blend
227 myTVPhosLevel->setValue(valueToLevel(settings.getInt("tv.phosblend")));
228
229 // TV overscan
230 myTVOverscan->setValue(settings.getInt("tia.fs_overscan"));
231
232 handleOverscanChange();
233
234 // Controllers
235 Properties props;
236
237 if (instance().hasConsole())
238 {
239 myGameProperties = instance().console().properties();
240 }
241 else
242 {
243 const string& md5 = instance().launcher().selectedRomMD5();
244 instance().propSet().getMD5(md5, myGameProperties);
245 }
246 loadControllerProperties(myGameProperties);
247}
248
249// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
250void StellaSettingsDialog::saveConfig()
251{
252 Settings& settings = instance().settings();
253
254 // UI palette
255 settings.setValue("uipalette",
256 myThemePopup->getSelectedTag().toString());
257 instance().frameBuffer().setUIPalette();
258
259 // Dialog position
260 settings.setValue("dialogpos", myPositionPopup->getSelectedTag().toString());
261
262 // TV Mode
263 instance().settings().setValue("tv.filter",
264 myTVMode->getSelectedTag().toString());
265
266 // TV phosphor mode
267 instance().settings().setValue("tv.phosphor",
268 myTVPhosLevel->getValue() > 0 ? "always" : "byrom");
269 // TV phosphor blend
270 instance().settings().setValue("tv.phosblend",
271 levelToValue(myTVPhosLevel->getValue()));
272
273 // TV scanline intensity and interpolation
274 instance().settings().setValue("tv.scanlines",
275 levelToValue(myTVScanIntense->getValue()));
276
277 // TV overscan
278 instance().settings().setValue("tia.fs_overscan", myTVOverscan->getValueLabel());
279
280 // Controller properties
281 myGameProperties.set(PropType::Controller_Left, myLeftPort->getSelectedTag().toString());
282 myGameProperties.set(PropType::Controller_Right, myRightPort->getSelectedTag().toString());
283
284 // Always insert; if the properties are already present, nothing will happen
285 instance().propSet().insert(myGameProperties);
286 instance().saveConfig();
287
288 // In any event, inform the Console
289 if (instance().hasConsole())
290 {
291 instance().console().setProperties(myGameProperties);
292 }
293
294 // Finally, issue a complete framebuffer re-initialization
295 instance().createFrameBuffer();
296}
297
298// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
299void StellaSettingsDialog::setDefaults()
300{
301 // UI Theme
302 myThemePopup->setSelected("standard");
303 // Dialog position
304 myPositionPopup->setSelected("0");
305
306 // TV effects
307 myTVMode->setSelected("RGB", static_cast<uInt32>(NTSCFilter::Preset::RGB));
308 // TV scanline intensity
309 myTVScanIntense->setValue(3); // 18
310 // TV phosphor blend
311 myTVPhosLevel->setValue(6); // = 45
312 // TV overscan
313 myTVOverscan->setValue(0);
314
315 // Load the default game properties
316 Properties defaultProperties;
317 const string& md5 = myGameProperties.get(PropType::Cart_MD5);
318
319 instance().propSet().getMD5(md5, defaultProperties, true);
320
321 loadControllerProperties(defaultProperties);
322}
323
324// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
325void StellaSettingsDialog::handleCommand(CommandSender* sender, int cmd,
326 int data, int id)
327{
328 switch (cmd)
329 {
330 case GuiObject::kDefaultsCmd:
331 setDefaults();
332 break;
333
334 case GuiObject::kOKCmd:
335 saveConfig();
336 [[fallthrough]];
337 case GuiObject::kCloseCmd:
338 if (myMode != Menu::AppMode::emulator)
339 close();
340 else
341 instance().eventHandler().leaveMenuMode();
342 break;
343
344 case kAdvancedSettings:
345 switchSettingsMode();
346 break;
347
348 case kConfirmSwitchCmd:
349 instance().settings().setValue("basic_settings", false);
350 if (myMode != Menu::AppMode::emulator)
351 close();
352 else
353 instance().eventHandler().leaveMenuMode();
354 break;
355
356 case kHelp:
357 openHelp();
358 break;
359
360 case kScanlinesChanged:
361 if(myTVScanIntense->getValue() == 0)
362 myTVScanIntense->setValueLabel("Off");
363 break;
364
365 case kPhosphorChanged:
366 if(myTVPhosLevel->getValue() == 0)
367 myTVPhosLevel->setValueLabel("Off");
368 break;
369
370 case kOverscanChanged:
371 handleOverscanChange();
372 break;
373
374 case kLeftCChanged:
375 case kRightCChanged:
376 updateControllerStates();
377 break;
378
379 default:
380 Dialog::handleCommand(sender, cmd, data, 0);
381 break;
382 }
383}
384
385// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
386void StellaSettingsDialog::handleOverscanChange()
387{
388 if (myTVOverscan->getValue() == 0)
389 {
390 myTVOverscan->setValueLabel("Off");
391 myTVOverscan->setValueUnit("");
392 }
393 else
394 myTVOverscan->setValueUnit("%");
395}
396
397// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
398void StellaSettingsDialog::switchSettingsMode()
399{
400 StringList msg;
401
402 msg.push_back("Warning!");
403 msg.push_back("");
404 msg.push_back("Advanced settings should be");
405 msg.push_back("handled with care! When in");
406 msg.push_back("doubt, read the manual.");
407 msg.push_back("");
408 msg.push_back("If you are sure you want to");
409 msg.push_back("proceed with the switch, click");
410 msg.push_back("'OK', otherwise click 'Cancel'.");
411
412 myConfirmMsg = make_unique<GUI::MessageBox>(this, instance().frameBuffer().font(), msg,
413 _w-16, _h, kConfirmSwitchCmd, "OK", "Cancel", "Switch settings mode", false);
414 myConfirmMsg->show();
415}
416
417// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
418void StellaSettingsDialog::loadControllerProperties(const Properties& props)
419{
420 // Determine whether we should enable the "Game settings:"
421 // We always enable it in emulation mode, or if a valid ROM is selected
422 // in launcher mode
423 bool enable = false;
424
425 // Note: The state returned seems not consistent here
426 switch (instance().eventHandler().state())
427 {
428 case EventHandlerState::OPTIONSMENU: // game is running!
429 case EventHandlerState::CMDMENU: // game is running!
430 enable = true;
431 break;
432 case EventHandlerState::LAUNCHER:
433 enable = (instance().launcher().selectedRomMD5() != "");
434 break;
435 default:
436 break;
437 }
438
439 myGameSettings->setEnabled(enable);
440 myLeftPort->setEnabled(enable);
441 myLeftPortLabel->setEnabled(enable);
442 myLeftPortDetected->setEnabled(enable);
443 myRightPort->setEnabled(enable);
444 myRightPortLabel->setEnabled(enable);
445 myRightPortDetected->setEnabled(enable);
446
447 if (enable)
448 {
449 string controller = props.get(PropType::Controller_Left);
450 myLeftPort->setSelected(controller, "AUTO");
451 controller = props.get(PropType::Controller_Right);
452 myRightPort->setSelected(controller, "AUTO");
453
454 updateControllerStates();
455 }
456 else
457 {
458 myLeftPort->clearSelection();
459 myRightPort->clearSelection();
460 myLeftPortDetected->setLabel("");
461 myRightPortDetected->setLabel("");
462 }
463}
464
465// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
466int StellaSettingsDialog::levelToValue(int level)
467{
468 const int NUM_LEVELS = 11;
469 uInt8 values[NUM_LEVELS] = { 0, 5, 11, 18, 26, 35, 45, 56, 68, 81, 95 };
470
471 return values[std::min(level, NUM_LEVELS - 1)];
472}
473
474// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
475int StellaSettingsDialog::valueToLevel(int value)
476{
477 const int NUM_LEVELS = 11;
478 uInt8 values[NUM_LEVELS] = { 0, 5, 11, 18, 26, 35, 45, 56, 68, 81, 95 };
479
480 for (uInt32 i = NUM_LEVELS - 1; i > 0; --i)
481 {
482 if (value >= values[i])
483 return i;
484 }
485 return 0;
486}
487
488// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
489void StellaSettingsDialog::openHelp()
490{
491 // Create an help dialog, similar to the in-game one
492 if (myHelpDialog == nullptr)
493 #if defined(RETRON77)
494 myHelpDialog = make_unique<R77HelpDialog>(instance(), parent(), _font);
495 #else
496 myHelpDialog = make_unique<HelpDialog>(instance(), parent(), _font);
497 #endif
498 myHelpDialog->open();
499}
500
501// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
502void StellaSettingsDialog::updateControllerStates()
503{
504 bool autoDetect = false;
505 ByteBuffer image;
506 string md5 = myGameProperties.get(PropType::Cart_MD5);
507 uInt32 size = 0;
508
509 // try to load the image for auto detection
510 if(!instance().hasConsole())
511 {
512 const FilesystemNode& node = FilesystemNode(instance().launcher().selectedRom());
513
514 autoDetect = node.exists() && !node.isDirectory() && (image = instance().openROM(node, md5, size)) != nullptr;
515 }
516 string label = "";
517 Controller::Type type = Controller::getType(myLeftPort->getSelectedTag().toString());
518
519 if(type == Controller::Type::Unknown)
520 {
521 if(instance().hasConsole())
522 label = (instance().console().leftController().name()) + " detected";
523 else if(autoDetect)
524 label = ControllerDetector::detectName(image.get(), size, type,
525 Controller::Jack::Left,
526 instance().settings()) + " detected";
527 }
528 myLeftPortDetected->setLabel(label);
529
530 label = "";
531 type = Controller::getType(myRightPort->getSelectedTag().toString());
532
533 if(type == Controller::Type::Unknown)
534 {
535 if(instance().hasConsole())
536 label = (instance().console().rightController().name()) + " detected";
537 else if(autoDetect)
538 label = ControllerDetector::detectName(image.get(), size, type,
539 Controller::Jack::Right,
540 instance().settings()) + " detected";
541 }
542 myRightPortDetected->setLabel(label);
543
544 // Compumate bankswitching scheme doesn't allow to select controllers
545 bool enableSelectControl = myGameProperties.get(PropType::Cart_Type) != "CM";
546
547 myLeftPortLabel->setEnabled(enableSelectControl);
548 myRightPortLabel->setEnabled(enableSelectControl);
549 myLeftPort->setEnabled(enableSelectControl);
550 myRightPort->setEnabled(enableSelectControl);
551}
552
553