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 "Bankswitch.hxx" |
19 | #include "Console.hxx" |
20 | #include "Cart.hxx" |
21 | #include "MouseControl.hxx" |
22 | #include "SaveKey.hxx" |
23 | #include "Dialog.hxx" |
24 | #include "EditTextWidget.hxx" |
25 | #include "RadioButtonWidget.hxx" |
26 | #include "Launcher.hxx" |
27 | #include "OSystem.hxx" |
28 | #include "CartDetector.hxx" |
29 | #include "ControllerDetector.hxx" |
30 | #include "PopUpWidget.hxx" |
31 | #include "Props.hxx" |
32 | #include "PropsSet.hxx" |
33 | #include "TabWidget.hxx" |
34 | #include "TIAConstants.hxx" |
35 | #include "Widget.hxx" |
36 | #include "Font.hxx" |
37 | |
38 | #include "FrameBuffer.hxx" |
39 | #include "TIASurface.hxx" |
40 | #include "TIA.hxx" |
41 | #include "Switches.hxx" |
42 | #include "AudioSettings.hxx" |
43 | |
44 | #include "GameInfoDialog.hxx" |
45 | |
46 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
47 | GameInfoDialog::GameInfoDialog( |
48 | OSystem& osystem, DialogContainer& parent, const GUI::Font& font, |
49 | GuiObject* boss, int max_w, int max_h) |
50 | : Dialog(osystem, parent, font, "Game properties" ), |
51 | CommandSender(boss) |
52 | { |
53 | const GUI::Font& ifont = instance().frameBuffer().infoFont(); |
54 | const int lineHeight = font.getLineHeight(), |
55 | fontWidth = font.getMaxCharWidth(), |
56 | fontHeight = font.getFontHeight(), |
57 | buttonHeight = font.getLineHeight() + 4, |
58 | infoLineHeight = ifont.getLineHeight(); |
59 | const int VBORDER = 8; |
60 | const int HBORDER = 10; |
61 | const int VGAP = 4; |
62 | |
63 | int xpos, ypos, lwidth, fwidth, pwidth, tabID; |
64 | WidgetArray wid; |
65 | VariantList items, ports, ctrls; |
66 | StaticTextWidget* t; |
67 | |
68 | // Set real dimensions |
69 | setSize(53 * fontWidth + 8, |
70 | 8 * (lineHeight + VGAP) + 1 * (infoLineHeight + VGAP) + VBORDER * 2 + _th + |
71 | buttonHeight + fontHeight + ifont.getLineHeight() + 20, |
72 | max_w, max_h); |
73 | |
74 | // The tab widget |
75 | myTab = new TabWidget(this, font, 2, 4 + _th, _w - 2 * 2, |
76 | _h - (_th + buttonHeight + 20)); |
77 | addTabWidget(myTab); |
78 | |
79 | ////////////////////////////////////////////////////////////////////////////// |
80 | // 1) Emulation properties |
81 | tabID = myTab->addTab("Emulation" ); |
82 | |
83 | ypos = VBORDER; |
84 | |
85 | t = new StaticTextWidget(myTab, font, HBORDER, ypos + 1, "Type (*) " ); |
86 | pwidth = font.getStringWidth("CM (SpectraVideo CompuMate)" ); |
87 | items.clear(); |
88 | for(uInt32 i = 0; i < uInt32(Bankswitch::Type::NumSchemes); ++i) |
89 | VarList::push_back(items, Bankswitch::BSList[i].desc, Bankswitch::BSList[i].name); |
90 | myBSType = new PopUpWidget(myTab, font, t->getRight() + 8, ypos, |
91 | pwidth, lineHeight, items, "" ); |
92 | wid.push_back(myBSType); |
93 | ypos += lineHeight + VGAP; |
94 | |
95 | myTypeDetected = new StaticTextWidget(myTab, ifont, t->getRight() + 8, ypos, |
96 | "CM (SpectraVideo CompuMate) detected" ); |
97 | ypos += ifont.getLineHeight() + VGAP; |
98 | |
99 | // Start bank |
100 | myStartBankLabel = new StaticTextWidget(myTab, font, HBORDER, ypos + 1, "Start bank (*) " ); |
101 | items.clear(); |
102 | myStartBank = new PopUpWidget(myTab, font, myStartBankLabel->getRight(), ypos, |
103 | font.getStringWidth("AUTO" ), lineHeight, items, "" , 0, 0); |
104 | wid.push_back(myStartBank); |
105 | ypos += lineHeight + VGAP * 4; |
106 | |
107 | pwidth = font.getStringWidth("Auto-detect" ); |
108 | t = new StaticTextWidget(myTab, font, HBORDER, ypos + 1, "TV format " ); |
109 | items.clear(); |
110 | VarList::push_back(items, "Auto-detect" , "AUTO" ); |
111 | VarList::push_back(items, "NTSC" , "NTSC" ); |
112 | VarList::push_back(items, "PAL" , "PAL" ); |
113 | VarList::push_back(items, "SECAM" , "SECAM" ); |
114 | VarList::push_back(items, "NTSC50" , "NTSC50" ); |
115 | VarList::push_back(items, "PAL60" , "PAL60" ); |
116 | VarList::push_back(items, "SECAM60" , "SECAM60" ); |
117 | myFormat = new PopUpWidget(myTab, font, t->getRight(), ypos, |
118 | pwidth, lineHeight, items, "" , 0, 0); |
119 | wid.push_back(myFormat); |
120 | |
121 | myFormatDetected = new StaticTextWidget(myTab, ifont, myFormat->getRight() + 8, ypos + 4, "SECAM60 detected" ); |
122 | |
123 | // Phosphor |
124 | ypos += lineHeight + VGAP * 2; |
125 | myPhosphor = new CheckboxWidget(myTab, font, HBORDER, ypos + 1, "Phosphor (enabled for all ROMs)" , kPhosphorChanged); |
126 | wid.push_back(myPhosphor); |
127 | |
128 | ypos += lineHeight + VGAP; |
129 | myPPBlend = new SliderWidget(myTab, font, |
130 | HBORDER + 20, ypos, |
131 | "Blend " , 0, kPPBlendChanged, 4 * fontWidth, "%" ); |
132 | myPPBlend->setMinValue(0); myPPBlend->setMaxValue(100); |
133 | myPPBlend->setTickmarkIntervals(2); |
134 | wid.push_back(myPPBlend); |
135 | |
136 | ypos += lineHeight + VGAP * 4; |
137 | mySound = new CheckboxWidget(myTab, font, HBORDER, ypos + 1, "Stereo sound" ); |
138 | wid.push_back(mySound); |
139 | |
140 | // Add message concerning usage |
141 | ypos = myTab->getHeight() - 5 - fontHeight - ifont.getFontHeight() - 10; |
142 | new StaticTextWidget(myTab, ifont, HBORDER, ypos, |
143 | "(*) Changes require a ROM reload" ); |
144 | |
145 | // Add items for tab 0 |
146 | addToFocusList(wid, myTab, tabID); |
147 | |
148 | ////////////////////////////////////////////////////////////////////////////// |
149 | // 2) Console properties |
150 | wid.clear(); |
151 | tabID = myTab->addTab("Console" ); |
152 | |
153 | xpos = HBORDER; ypos = VBORDER; |
154 | lwidth = font.getStringWidth(GUI::RIGHT_DIFFICULTY + " " ); |
155 | |
156 | new StaticTextWidget(myTab, font, xpos, ypos + 1, "TV type" ); |
157 | myTVTypeGroup = new RadioButtonGroup(); |
158 | RadioButtonWidget* r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
159 | "Color" , myTVTypeGroup); |
160 | wid.push_back(r); |
161 | ypos += lineHeight; |
162 | r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
163 | "B/W" , myTVTypeGroup); |
164 | wid.push_back(r); |
165 | ypos += lineHeight + VGAP * 2; |
166 | |
167 | new StaticTextWidget(myTab, font, xpos, ypos+1, GUI::LEFT_DIFFICULTY); |
168 | myLeftDiffGroup = new RadioButtonGroup(); |
169 | r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
170 | "A (Expert)" , myLeftDiffGroup); |
171 | wid.push_back(r); |
172 | ypos += lineHeight; |
173 | r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
174 | "B (Novice)" , myLeftDiffGroup); |
175 | wid.push_back(r); |
176 | ypos += lineHeight + VGAP * 2; |
177 | |
178 | new StaticTextWidget(myTab, font, xpos, ypos+1, GUI::RIGHT_DIFFICULTY); |
179 | myRightDiffGroup = new RadioButtonGroup(); |
180 | r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
181 | "A (Expert)" , myRightDiffGroup); |
182 | wid.push_back(r); |
183 | ypos += lineHeight; |
184 | r = new RadioButtonWidget(myTab, font, xpos + lwidth, ypos + 1, |
185 | "B (Novice)" , myRightDiffGroup); |
186 | wid.push_back(r); |
187 | |
188 | // Add items for tab 1 |
189 | addToFocusList(wid, myTab, tabID); |
190 | |
191 | ////////////////////////////////////////////////////////////////////////////// |
192 | // 3) Controller properties |
193 | wid.clear(); |
194 | tabID = myTab->addTab("Controllers" ); |
195 | |
196 | ctrls.clear(); |
197 | VarList::push_back(ctrls, "Auto-detect" , "AUTO" ); |
198 | VarList::push_back(ctrls, "Joystick" , "JOYSTICK" ); |
199 | VarList::push_back(ctrls, "Paddles" , "PADDLES" ); |
200 | VarList::push_back(ctrls, "Paddles_IAxis" , "PADDLES_IAXIS" ); |
201 | VarList::push_back(ctrls, "Paddles_IAxDr" , "PADDLES_IAXDR" ); |
202 | VarList::push_back(ctrls, "BoosterGrip" , "BOOSTERGRIP" ); |
203 | VarList::push_back(ctrls, "Driving" , "DRIVING" ); |
204 | VarList::push_back(ctrls, "Keyboard" , "KEYBOARD" ); |
205 | VarList::push_back(ctrls, "AmigaMouse" , "AMIGAMOUSE" ); |
206 | VarList::push_back(ctrls, "AtariMouse" , "ATARIMOUSE" ); |
207 | VarList::push_back(ctrls, "Trakball" , "TRAKBALL" ); |
208 | VarList::push_back(ctrls, "AtariVox" , "ATARIVOX" ); |
209 | VarList::push_back(ctrls, "SaveKey" , "SAVEKEY" ); |
210 | VarList::push_back(ctrls, "Sega Genesis" , "GENESIS" ); |
211 | VarList::push_back(ctrls, "KidVid" , "KIDVID" ); |
212 | VarList::push_back(ctrls, "MindLink" , "MINDLINK" ); |
213 | |
214 | ypos = VBORDER; |
215 | pwidth = font.getStringWidth("Paddles_IAxis" ); |
216 | myLeftPortLabel = new StaticTextWidget(myTab, font, HBORDER, ypos+1, "Left port " ); |
217 | myLeftPort = new PopUpWidget(myTab, font, myLeftPortLabel->getRight(), |
218 | myLeftPortLabel->getTop()-1, |
219 | pwidth, lineHeight, ctrls, "" , 0, kLeftCChanged); |
220 | wid.push_back(myLeftPort); |
221 | ypos += lineHeight + VGAP; |
222 | |
223 | myLeftPortDetected = new StaticTextWidget(myTab, ifont, myLeftPort->getLeft(), ypos, |
224 | "Sega Genesis detected" ); |
225 | ypos += ifont.getLineHeight() + VGAP; |
226 | |
227 | myRightPortLabel = new StaticTextWidget(myTab, font, HBORDER, ypos+1, "Right port " ); |
228 | myRightPort = new PopUpWidget(myTab, font, myRightPortLabel->getRight(), |
229 | myRightPortLabel->getTop()-1, |
230 | pwidth, lineHeight, ctrls, "" , 0, kRightCChanged); |
231 | wid.push_back(myRightPort); |
232 | ypos += lineHeight + VGAP; |
233 | myRightPortDetected = new StaticTextWidget(myTab, ifont, myRightPort->getLeft(), ypos, |
234 | "Sega Genesis detected" ); |
235 | ypos += ifont.getLineHeight() + VGAP + 4; |
236 | |
237 | mySwapPorts = new CheckboxWidget(myTab, font, myLeftPort->getRight() + fontWidth*4, |
238 | myLeftPort->getTop()+1, "Swap ports" ); |
239 | wid.push_back(mySwapPorts); |
240 | mySwapPaddles = new CheckboxWidget(myTab, font, myRightPort->getRight() + fontWidth*4, |
241 | myRightPort->getTop()+1, "Swap paddles" ); |
242 | wid.push_back(mySwapPaddles); |
243 | |
244 | // EEPROM erase button for left/right controller |
245 | //ypos += lineHeight + VGAP + 4; |
246 | pwidth = myRightPort->getWidth(); //font.getStringWidth("Erase EEPROM ") + 23; |
247 | myEraseEEPROMLabel = new StaticTextWidget(myTab, font, HBORDER, ypos, "AtariVox/SaveKey " ); |
248 | myEraseEEPROMButton = new ButtonWidget(myTab, font, myEraseEEPROMLabel->getRight(), ypos - 4, |
249 | pwidth, buttonHeight, "Erase EEPROM" , kEEButtonPressed); |
250 | wid.push_back(myEraseEEPROMButton); |
251 | myEraseEEPROMInfo = new StaticTextWidget(myTab, ifont, myEraseEEPROMButton->getRight() + 4, |
252 | myEraseEEPROMLabel->getTop() + 3, "(for this game only)" ); |
253 | |
254 | ypos += lineHeight + VGAP * 4; |
255 | myMouseControl = new CheckboxWidget(myTab, font, xpos, ypos + 1, "Specific mouse axes" , kMCtrlChanged); |
256 | wid.push_back(myMouseControl); |
257 | |
258 | // Mouse controller specific axis |
259 | pwidth = font.getStringWidth("MindLink 0" ); |
260 | items.clear(); |
261 | VarList::push_back(items, "None" , static_cast<uInt32>(MouseControl::Type::NoControl)); |
262 | VarList::push_back(items, "Paddle 0" , static_cast<uInt32>(MouseControl::Type::Paddle0)); |
263 | VarList::push_back(items, "Paddle 1" , static_cast<uInt32>(MouseControl::Type::Paddle1)); |
264 | VarList::push_back(items, "Paddle 2" , static_cast<uInt32>(MouseControl::Type::Paddle2)); |
265 | VarList::push_back(items, "Paddle 3" , static_cast<uInt32>(MouseControl::Type::Paddle3)); |
266 | VarList::push_back(items, "Driving 0" , static_cast<uInt32>(MouseControl::Type::Driving0)); |
267 | VarList::push_back(items, "Driving 1" , static_cast<uInt32>(MouseControl::Type::Driving1)); |
268 | VarList::push_back(items, "MindLink 0" , static_cast<uInt32>(MouseControl::Type::MindLink0)); |
269 | VarList::push_back(items, "MindLink 1" , static_cast<uInt32>(MouseControl::Type::MindLink1)); |
270 | |
271 | xpos += 20; |
272 | ypos += lineHeight + VGAP; |
273 | myMouseX = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, |
274 | "X-Axis is " ); |
275 | wid.push_back(myMouseX); |
276 | |
277 | ypos += lineHeight + VGAP; |
278 | myMouseY = new PopUpWidget(myTab, font, myMouseX->getLeft(), ypos, pwidth, lineHeight, items, |
279 | "Y-Axis is " ); |
280 | wid.push_back(myMouseY); |
281 | |
282 | xpos = HBORDER; ypos += lineHeight + VGAP; |
283 | myMouseRange = new SliderWidget(myTab, font, HBORDER, ypos, |
284 | "Mouse axes range " , 0, 0, fontWidth * 4, "%" ); |
285 | myMouseRange->setMinValue(1); myMouseRange->setMaxValue(100); |
286 | myMouseRange->setTickmarkIntervals(4); |
287 | wid.push_back(myMouseRange); |
288 | |
289 | // Add items for tab 2 |
290 | addToFocusList(wid, myTab, tabID); |
291 | |
292 | ////////////////////////////////////////////////////////////////////////////// |
293 | // 4) Cartridge properties |
294 | wid.clear(); |
295 | tabID = myTab->addTab("Cartridge" ); |
296 | |
297 | xpos = HBORDER; ypos = VBORDER; |
298 | lwidth = font.getStringWidth("Manufacturer " ); |
299 | fwidth = _w - lwidth - HBORDER * 2 - 2; |
300 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Name" ); |
301 | myName = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
302 | fwidth, lineHeight, "" ); |
303 | wid.push_back(myName); |
304 | |
305 | ypos += lineHeight + VGAP; |
306 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "MD5" ); |
307 | myMD5 = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
308 | fwidth, lineHeight, "" ); |
309 | myMD5->setEditable(false); |
310 | |
311 | ypos += lineHeight + VGAP; |
312 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Manufacturer" ); |
313 | myManufacturer = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
314 | fwidth, lineHeight, "" ); |
315 | wid.push_back(myManufacturer); |
316 | |
317 | ypos += lineHeight + VGAP; |
318 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, |
319 | "Model" , TextAlign::Left); |
320 | myModelNo = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
321 | fwidth, lineHeight, "" ); |
322 | wid.push_back(myModelNo); |
323 | |
324 | ypos += lineHeight + VGAP; |
325 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Rarity" ); |
326 | myRarity = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
327 | fwidth, lineHeight, "" ); |
328 | wid.push_back(myRarity); |
329 | |
330 | ypos += lineHeight + VGAP; |
331 | new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Note" ); |
332 | myNote = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, |
333 | fwidth, lineHeight, "" ); |
334 | wid.push_back(myNote); |
335 | |
336 | // Add items for tab 3 |
337 | addToFocusList(wid, myTab, tabID); |
338 | |
339 | // Activate the first tab |
340 | myTab->setActiveTab(0); |
341 | |
342 | // Add Defaults, OK and Cancel buttons |
343 | wid.clear(); |
344 | addDefaultsOKCancelBGroup(wid, font); |
345 | addBGroupToFocusList(wid); |
346 | } |
347 | |
348 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
349 | void GameInfoDialog::loadConfig() |
350 | { |
351 | if(instance().hasConsole()) |
352 | { |
353 | myGameProperties = instance().console().properties(); |
354 | } |
355 | else |
356 | { |
357 | const string& md5 = instance().launcher().selectedRomMD5(); |
358 | instance().propSet().getMD5(md5, myGameProperties); |
359 | } |
360 | |
361 | loadEmulationProperties(myGameProperties); |
362 | loadConsoleProperties(myGameProperties); |
363 | loadControllerProperties(myGameProperties); |
364 | loadCartridgeProperties(myGameProperties); |
365 | |
366 | myTab->loadConfig(); |
367 | } |
368 | |
369 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
370 | void GameInfoDialog::loadEmulationProperties(const Properties& props) |
371 | { |
372 | string bsDetected = "" ; |
373 | |
374 | myBSType->setSelected(props.get(PropType::Cart_Type), "AUTO" ); |
375 | if(myBSType->getSelectedTag().toString() == "AUTO" ) |
376 | { |
377 | if(instance().hasConsole()) |
378 | { |
379 | string bs = instance().console().about().BankSwitch; |
380 | size_t pos = bs.find_first_of('*'); |
381 | // remove '*': |
382 | if(pos != string::npos) |
383 | bs = bs.substr(0, pos) + bs.substr(pos + 1); |
384 | bsDetected = bs + "detected" ; |
385 | } |
386 | else |
387 | { |
388 | const FilesystemNode& node = FilesystemNode(instance().launcher().selectedRom()); |
389 | ByteBuffer image; |
390 | string md5 = props.get(PropType::Cart_MD5); |
391 | uInt32 size = 0; |
392 | |
393 | // try to load the image for auto detection |
394 | if(!instance().hasConsole() && |
395 | node.exists() && !node.isDirectory() && (image = instance().openROM(node, md5, size)) != nullptr) |
396 | { |
397 | bsDetected = Bankswitch::typeToName(CartDetector::autodetectType(image, size)) + " detected" ; |
398 | } |
399 | } |
400 | } |
401 | myTypeDetected->setLabel(bsDetected); |
402 | |
403 | // Start bank |
404 | VariantList items; |
405 | |
406 | VarList::push_back(items, "Auto" , "AUTO" ); |
407 | if(instance().hasConsole()) |
408 | { |
409 | uInt16 numBanks = instance().console().cartridge().bankCount(); |
410 | |
411 | for(uInt16 i = 0; i < numBanks; ++i) |
412 | VarList::push_back(items, i, i); |
413 | myStartBank->setEnabled(true); |
414 | } |
415 | else |
416 | { |
417 | string startBank = props.get(PropType::Cart_StartBank); |
418 | |
419 | VarList::push_back(items, startBank, startBank); |
420 | myStartBank->setEnabled(false); |
421 | } |
422 | myStartBank->addItems(items); |
423 | myStartBank->setSelected(props.get(PropType::Cart_StartBank), "AUTO" ); |
424 | |
425 | myFormat->setSelected(props.get(PropType::Display_Format), "AUTO" ); |
426 | if(instance().hasConsole() && myFormat->getSelectedTag().toString() == "AUTO" ) |
427 | { |
428 | const string& format = instance().console().about().DisplayFormat; |
429 | string label = format.substr(0, format.length() - 1); |
430 | myFormatDetected->setLabel(label + " detected" ); |
431 | } |
432 | else |
433 | myFormatDetected->setLabel("" ); |
434 | |
435 | // if phosphor is always enabled, disable game specific phosphor settings |
436 | bool alwaysPhosphor = instance().settings().getString("tv.phosphor" ) == "always" ; |
437 | bool usePhosphor = props.get(PropType::Display_Phosphor) == "YES" ; |
438 | myPhosphor->setState(usePhosphor); |
439 | myPhosphor->setEnabled(!alwaysPhosphor); |
440 | if (alwaysPhosphor) |
441 | myPhosphor->setLabel("Phosphor (enabled for all ROMs)" ); |
442 | else |
443 | myPhosphor->setLabel("Phosphor" ); |
444 | myPPBlend->setEnabled(!alwaysPhosphor && usePhosphor); |
445 | |
446 | const string& blend = props.get(PropType::Display_PPBlend); |
447 | myPPBlend->setValue(atoi(blend.c_str())); |
448 | |
449 | mySound->setState(props.get(PropType::Cart_Sound) == "STEREO" ); |
450 | // if stereo is always enabled, disable game specific stereo setting |
451 | mySound->setEnabled(!instance().audioSettings().stereo()); |
452 | } |
453 | |
454 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
455 | void GameInfoDialog::loadConsoleProperties(const Properties& props) |
456 | { |
457 | myLeftDiffGroup->setSelected(props.get(PropType::Console_LeftDiff) == "A" ? 0 : 1); |
458 | myRightDiffGroup->setSelected(props.get(PropType::Console_RightDiff) == "A" ? 0 : 1); |
459 | myTVTypeGroup->setSelected(props.get(PropType::Console_TVType) == "BW" ? 1 : 0); |
460 | } |
461 | |
462 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
463 | void GameInfoDialog::loadControllerProperties(const Properties& props) |
464 | { |
465 | string controller = props.get(PropType::Controller_Left); |
466 | myLeftPort->setSelected(controller, "AUTO" ); |
467 | controller = props.get(PropType::Controller_Right); |
468 | myRightPort->setSelected(controller, "AUTO" ); |
469 | |
470 | mySwapPorts->setState(props.get(PropType::Console_SwapPorts) == "YES" ); |
471 | mySwapPaddles->setState(props.get(PropType::Controller_SwapPaddles) == "YES" ); |
472 | |
473 | // MouseAxis property (potentially contains 'range' information) |
474 | istringstream m_axis(props.get(PropType::Controller_MouseAxis)); |
475 | string m_control, m_range; |
476 | m_axis >> m_control; |
477 | bool autoAxis = BSPF::equalsIgnoreCase(m_control, "AUTO" ); |
478 | myMouseControl->setState(!autoAxis); |
479 | if(autoAxis) |
480 | { |
481 | myMouseX->setSelectedIndex(0); |
482 | myMouseY->setSelectedIndex(0); |
483 | } |
484 | else |
485 | { |
486 | myMouseX->setSelected(m_control[0] - '0'); |
487 | myMouseY->setSelected(m_control[1] - '0'); |
488 | } |
489 | myMouseX->setEnabled(!autoAxis); |
490 | myMouseY->setEnabled(!autoAxis); |
491 | if(m_axis >> m_range) |
492 | { |
493 | myMouseRange->setValue(atoi(m_range.c_str())); |
494 | } |
495 | else |
496 | { |
497 | myMouseRange->setValue(100); |
498 | } |
499 | |
500 | updateControllerStates(); |
501 | } |
502 | |
503 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
504 | void GameInfoDialog::loadCartridgeProperties(const Properties& props) |
505 | { |
506 | myName->setText(props.get(PropType::Cart_Name)); |
507 | myMD5->setText(props.get(PropType::Cart_MD5)); |
508 | myManufacturer->setText(props.get(PropType::Cart_Manufacturer)); |
509 | myModelNo->setText(props.get(PropType::Cart_ModelNo)); |
510 | myRarity->setText(props.get(PropType::Cart_Rarity)); |
511 | myNote->setText(props.get(PropType::Cart_Note)); |
512 | } |
513 | |
514 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
515 | void GameInfoDialog::saveConfig() |
516 | { |
517 | // Emulation properties |
518 | myGameProperties.set(PropType::Cart_Type, myBSType->getSelectedTag().toString()); |
519 | myGameProperties.set(PropType::Cart_StartBank, myStartBank->getSelectedTag().toString()); |
520 | myGameProperties.set(PropType::Display_Format, myFormat->getSelectedTag().toString()); |
521 | myGameProperties.set(PropType::Display_Phosphor, myPhosphor->getState() ? "YES" : "NO" ); |
522 | myGameProperties.set(PropType::Display_PPBlend, myPPBlend->getValueLabel() == "Off" ? "0" : |
523 | myPPBlend->getValueLabel()); |
524 | myGameProperties.set(PropType::Cart_Sound, mySound->getState() ? "STEREO" : "MONO" ); |
525 | |
526 | // Console properties |
527 | myGameProperties.set(PropType::Console_TVType, myTVTypeGroup->getSelected() ? "BW" : "COLOR" ); |
528 | myGameProperties.set(PropType::Console_LeftDiff, myLeftDiffGroup->getSelected() ? "B" : "A" ); |
529 | myGameProperties.set(PropType::Console_RightDiff, myRightDiffGroup->getSelected() ? "B" : "A" ); |
530 | |
531 | // Controller properties |
532 | myGameProperties.set(PropType::Controller_Left, myLeftPort->getSelectedTag().toString()); |
533 | myGameProperties.set(PropType::Controller_Right, myRightPort->getSelectedTag().toString()); |
534 | myGameProperties.set(PropType::Console_SwapPorts, (mySwapPorts->isEnabled() && mySwapPorts->getState()) ? "YES" : "NO" ); |
535 | myGameProperties.set(PropType::Controller_SwapPaddles, (/*mySwapPaddles->isEnabled() &&*/ mySwapPaddles->getState()) ? "YES" : "NO" ); |
536 | |
537 | // MouseAxis property (potentially contains 'range' information) |
538 | string mcontrol = "AUTO" ; |
539 | if(myMouseControl->getState()) |
540 | mcontrol = myMouseX->getSelectedTag().toString() + |
541 | myMouseY->getSelectedTag().toString(); |
542 | string range = myMouseRange->getValueLabel(); |
543 | if(range != "100" ) |
544 | mcontrol += " " + range; |
545 | myGameProperties.set(PropType::Controller_MouseAxis, mcontrol); |
546 | |
547 | // Cartridge properties |
548 | myGameProperties.set(PropType::Cart_Name, myName->getText()); |
549 | myGameProperties.set(PropType::Cart_Manufacturer, myManufacturer->getText()); |
550 | myGameProperties.set(PropType::Cart_ModelNo, myModelNo->getText()); |
551 | myGameProperties.set(PropType::Cart_Rarity, myRarity->getText()); |
552 | myGameProperties.set(PropType::Cart_Note, myNote->getText()); |
553 | |
554 | // Always insert; if the properties are already present, nothing will happen |
555 | instance().propSet().insert(myGameProperties); |
556 | instance().saveConfig(); |
557 | |
558 | // In any event, inform the Console |
559 | if(instance().hasConsole()) |
560 | { |
561 | instance().console().setProperties(myGameProperties); |
562 | |
563 | // update 'Emulation' tab settings immediately |
564 | instance().console().setFormat(myFormat->getSelected()); |
565 | instance().frameBuffer().tiaSurface().enablePhosphor(myPhosphor->getState(), myPPBlend->getValue()); |
566 | instance().console().initializeAudio(); |
567 | |
568 | // update 'Console' tab settings immediately |
569 | instance().console().switches().setTvColor(myTVTypeGroup->getSelected() == 0); |
570 | instance().console().switches().setLeftDifficultyA(myLeftDiffGroup->getSelected() == 0); |
571 | instance().console().switches().setRightDifficultyA(myRightDiffGroup->getSelected() == 0); |
572 | |
573 | // update 'Controllers' tab settings immediately |
574 | instance().console().setControllers(myGameProperties.get(PropType::Cart_MD5)); |
575 | } |
576 | } |
577 | |
578 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
579 | void GameInfoDialog::setDefaults() |
580 | { |
581 | // Load the default properties |
582 | Properties defaultProperties; |
583 | const string& md5 = myGameProperties.get(PropType::Cart_MD5); |
584 | |
585 | instance().propSet().getMD5(md5, defaultProperties, true); |
586 | |
587 | switch(myTab->getActiveTab()) |
588 | { |
589 | case 0: // Emulation properties |
590 | loadEmulationProperties(defaultProperties); |
591 | break; |
592 | |
593 | case 1: // Console properties |
594 | loadConsoleProperties(defaultProperties); |
595 | break; |
596 | |
597 | case 2: // Controller properties |
598 | loadControllerProperties(defaultProperties); |
599 | break; |
600 | |
601 | case 3: // Cartridge properties |
602 | loadCartridgeProperties(defaultProperties); |
603 | break; |
604 | |
605 | default: // make the compiler happy |
606 | break; |
607 | } |
608 | } |
609 | |
610 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
611 | void GameInfoDialog::updateControllerStates() |
612 | { |
613 | bool swapPorts = mySwapPorts->getState(); |
614 | bool autoDetect = false; |
615 | ByteBuffer image; |
616 | string md5 = myGameProperties.get(PropType::Cart_MD5); |
617 | uInt32 size = 0; |
618 | |
619 | // try to load the image for auto detection |
620 | if(!instance().hasConsole()) |
621 | { |
622 | const FilesystemNode& node = FilesystemNode(instance().launcher().selectedRom()); |
623 | |
624 | autoDetect = node.exists() && !node.isDirectory() && (image = instance().openROM(node, md5, size)) != nullptr; |
625 | } |
626 | string label = "" ; |
627 | Controller::Type type = Controller::getType(myLeftPort->getSelectedTag().toString()); |
628 | |
629 | if(type == Controller::Type::Unknown) |
630 | { |
631 | if(instance().hasConsole()) |
632 | label = (!swapPorts ? instance().console().leftController().name() |
633 | : instance().console().rightController().name()) + " detected" ; |
634 | else if(autoDetect) |
635 | label = ControllerDetector::detectName(image.get(), size, type, |
636 | !swapPorts ? Controller::Jack::Left : Controller::Jack::Right, |
637 | instance().settings()) + " detected" ; |
638 | } |
639 | myLeftPortDetected->setLabel(label); |
640 | |
641 | label = "" ; |
642 | type = Controller::getType(myRightPort->getSelectedTag().toString()); |
643 | |
644 | if(type == Controller::Type::Unknown) |
645 | { |
646 | if(instance().hasConsole()) |
647 | label = (!swapPorts ? instance().console().rightController().name() |
648 | : instance().console().leftController().name()) + " detected" ; |
649 | else if(autoDetect) |
650 | label = ControllerDetector::detectName(image.get(), size, type, |
651 | !swapPorts ? Controller::Jack::Right : Controller::Jack::Left, |
652 | instance().settings()) + " detected" ; |
653 | } |
654 | myRightPortDetected->setLabel(label); |
655 | |
656 | const string& contrLeft = myLeftPort->getSelectedTag().toString(); |
657 | const string& contrRight = myRightPort->getSelectedTag().toString(); |
658 | bool enableEEEraseButton = false; |
659 | |
660 | // Compumate bankswitching scheme doesn't allow to select controllers |
661 | bool enableSelectControl = myBSType->getSelectedTag() != "CM" ; |
662 | // Enable Swap Paddles checkbox only for paddle games |
663 | bool enableSwapPaddles = BSPF::startsWithIgnoreCase(contrLeft, "PADDLES" ) || |
664 | BSPF::startsWithIgnoreCase(contrRight, "PADDLES" ) || |
665 | BSPF::startsWithIgnoreCase(myLeftPortDetected->getLabel(), "Paddles" ) || |
666 | BSPF::startsWithIgnoreCase(myRightPortDetected->getLabel(), "Paddles" ); |
667 | |
668 | if(instance().hasConsole()) |
669 | { |
670 | const Controller& lport = instance().console().leftController(); |
671 | const Controller& rport = instance().console().rightController(); |
672 | |
673 | // we only enable the button if we have a valid previous and new controller. |
674 | bool enableBtnForLeft = |
675 | (contrLeft == "AUTO" || contrLeft == "SAVEKEY" || contrLeft == "ATARIVOX" ) && |
676 | (lport.type() == Controller::Type::SaveKey || lport.type() == Controller::Type::AtariVox); |
677 | bool enableBtnForRight = |
678 | (contrRight == "AUTO" || contrRight == "SAVEKEY" || contrRight == "ATARIVOX" ) && |
679 | (rport.type() == Controller::Type::SaveKey || rport.type() == Controller::Type::AtariVox); |
680 | enableEEEraseButton = enableBtnForLeft || enableBtnForRight; |
681 | } |
682 | |
683 | myLeftPortLabel->setEnabled(enableSelectControl); |
684 | myRightPortLabel->setEnabled(enableSelectControl); |
685 | myLeftPort->setEnabled(enableSelectControl); |
686 | myRightPort->setEnabled(enableSelectControl); |
687 | |
688 | mySwapPorts->setEnabled(enableSelectControl); |
689 | mySwapPaddles->setEnabled(enableSwapPaddles); |
690 | |
691 | myEraseEEPROMLabel->setEnabled(enableEEEraseButton); |
692 | myEraseEEPROMButton->setEnabled(enableEEEraseButton); |
693 | myEraseEEPROMInfo->setEnabled(enableEEEraseButton); |
694 | } |
695 | |
696 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
697 | void GameInfoDialog::eraseEEPROM() |
698 | { |
699 | Controller& lport = instance().console().leftController(); |
700 | Controller& rport = instance().console().rightController(); |
701 | |
702 | if(lport.type() == Controller::Type::SaveKey || lport.type() == Controller::Type::AtariVox) |
703 | { |
704 | SaveKey& skey = static_cast<SaveKey&>(lport); |
705 | skey.eraseCurrent(); |
706 | } |
707 | |
708 | if(rport.type() == Controller::Type::SaveKey || rport.type() == Controller::Type::AtariVox) |
709 | { |
710 | SaveKey& skey = static_cast<SaveKey&>(rport); |
711 | skey.eraseCurrent(); |
712 | } |
713 | } |
714 | |
715 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
716 | void GameInfoDialog::handleCommand(CommandSender* sender, int cmd, |
717 | int data, int id) |
718 | { |
719 | switch (cmd) |
720 | { |
721 | case GuiObject::kOKCmd: |
722 | saveConfig(); |
723 | close(); |
724 | break; |
725 | |
726 | case GuiObject::kDefaultsCmd: |
727 | setDefaults(); |
728 | break; |
729 | |
730 | case TabWidget::kTabChangedCmd: |
731 | if(data == 2) // 'Controllers' tab selected |
732 | updateControllerStates(); |
733 | |
734 | // The underlying dialog still needs access to this command |
735 | Dialog::handleCommand(sender, cmd, data, 0); |
736 | break; |
737 | |
738 | case kLeftCChanged: |
739 | case kRightCChanged: |
740 | updateControllerStates(); |
741 | break; |
742 | |
743 | case kEEButtonPressed: |
744 | eraseEEPROM(); |
745 | break; |
746 | |
747 | case kPhosphorChanged: |
748 | { |
749 | bool status = myPhosphor->getState(); |
750 | myPPBlend->setEnabled(status); |
751 | break; |
752 | } |
753 | |
754 | case kPPBlendChanged: |
755 | if(myPPBlend->getValue() == 0) |
756 | { |
757 | myPPBlend->setValueLabel("Off" ); |
758 | myPPBlend->setValueUnit("" ); |
759 | } |
760 | else |
761 | myPPBlend->setValueUnit("%" ); |
762 | break; |
763 | |
764 | case kMCtrlChanged: |
765 | { |
766 | bool state = myMouseControl->getState(); |
767 | myMouseX->setEnabled(state); |
768 | myMouseY->setEnabled(state); |
769 | break; |
770 | } |
771 | |
772 | default: |
773 | Dialog::handleCommand(sender, cmd, data, 0); |
774 | break; |
775 | } |
776 | } |
777 | |