| 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 | |