settingswindow.cpp (23436B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "settingswindow.h" 5 #include "advancedsettingswidget.h" 6 #include "audiosettingswidget.h" 7 #include "biossettingswidget.h" 8 #include "consolesettingswidget.h" 9 10 #include "achievementsettingswidget.h" 11 #include "emulationsettingswidget.h" 12 #include "foldersettingswidget.h" 13 #include "gamelistsettingswidget.h" 14 #include "gamesummarywidget.h" 15 #include "graphicssettingswidget.h" 16 #include "interfacesettingswidget.h" 17 #include "mainwindow.h" 18 #include "memorycardsettingswidget.h" 19 #include "postprocessingsettingswidget.h" 20 #include "qthost.h" 21 22 #include "core/achievements.h" 23 #include "core/host.h" 24 25 #include "util/ini_settings_interface.h" 26 27 #include "common/assert.h" 28 #include "common/error.h" 29 #include "common/file_system.h" 30 #include "common/log.h" 31 32 #include <QtGui/QWheelEvent> 33 #include <QtWidgets/QMessageBox> 34 #include <QtWidgets/QScrollBar> 35 #include <QtWidgets/QTextEdit> 36 37 Log_SetChannel(SettingsWindow); 38 39 static QList<SettingsWindow*> s_open_game_properties_dialogs; 40 41 SettingsWindow::SettingsWindow() : QWidget() 42 { 43 m_ui.setupUi(this); 44 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 45 addPages(); 46 connectUi(); 47 } 48 49 SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region, 50 const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif) 51 : QWidget(), m_sif(std::move(sif)), m_database_entry(entry) 52 { 53 m_ui.setupUi(this); 54 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 55 56 addWidget(new GameSummaryWidget(path, serial, region, entry, this, m_ui.settingsContainer), tr("Summary"), 57 QStringLiteral("file-list-line"), 58 tr("<strong>Summary</strong><hr>This page shows information about the selected game, and allows you to " 59 "validate your disc was dumped correctly.")); 60 addPages(); 61 connectUi(); 62 63 s_open_game_properties_dialogs.push_back(this); 64 } 65 66 SettingsWindow::~SettingsWindow() 67 { 68 if (isPerGameSettings()) 69 s_open_game_properties_dialogs.removeOne(this); 70 } 71 72 void SettingsWindow::closeEvent(QCloseEvent* event) 73 { 74 // we need to clean up ourselves, since we're not modal 75 if (isPerGameSettings()) 76 deleteLater(); 77 } 78 79 void SettingsWindow::addPages() 80 { 81 addWidget( 82 m_interface_settings = new InterfaceSettingsWidget(this, m_ui.settingsContainer), tr("Interface"), 83 QStringLiteral("settings-3-line"), 84 tr("<strong>Interface Settings</strong><hr>These options control how the emulator looks and " 85 "behaves.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel.")); 86 87 if (!isPerGameSettings()) 88 { 89 addWidget( 90 m_game_list_settings = new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"), 91 QStringLiteral("folder-open-line"), 92 tr("<strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by " 93 "DuckStation to populate the game list. Search directories can be added, removed, and switched to " 94 "recursive/non-recursive.")); 95 } 96 97 addWidget( 98 m_bios_settings = new BIOSSettingsWidget(this, m_ui.settingsContainer), tr("BIOS"), QStringLiteral("chip-line"), 99 tr("<strong>BIOS Settings</strong><hr>These options control which BIOS is used and how it will be " 100 "patched.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel.")); 101 addWidget( 102 m_console_settings = new ConsoleSettingsWidget(this, m_ui.settingsContainer), tr("Console"), 103 QStringLiteral("chip-2-line"), 104 tr("<strong>Console Settings</strong><hr>These options determine the configuration of the simulated " 105 "console.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel.")); 106 addWidget( 107 m_emulation_settings = new EmulationSettingsWidget(this, m_ui.settingsContainer), tr("Emulation"), 108 QStringLiteral("emulation-line"), 109 tr("<strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the " 110 "system.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel.")); 111 addWidget( 112 m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"), 113 QStringLiteral("memcard-line"), 114 tr("<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will " 115 "function in, and where the images for these cards will be stored on disk.")); 116 addWidget(m_graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"), 117 QStringLiteral("image-fill"), 118 tr("<strong>Graphics Settings</strong><hr>These options control how the graphics of the emulated console " 119 "are rendered. Not all options are available for the software renderer. Mouse over each option for " 120 "additional information, and Shift+Wheel to scroll this panel.")); 121 addWidget( 122 m_post_processing_settings = new PostProcessingSettingsWidget(this, m_ui.settingsContainer), tr("Post-Processing"), 123 QStringLiteral("sun-fill"), 124 tr("<strong>Post-Processing Settings</strong><hr>Post processing allows you to alter the appearance of the image " 125 "displayed on the screen with various filters. Shaders will be executed in sequence.")); 126 addWidget( 127 m_audio_settings = new AudioSettingsWidget(this, m_ui.settingsContainer), tr("Audio"), 128 QStringLiteral("volume-up-line"), 129 tr("<strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option " 130 "for additional information.")); 131 { 132 QString title(tr("Achievements")); 133 QString icon_text(QStringLiteral("trophy-line")); 134 QString help_text( 135 tr("<strong>Achievement Settings</strong><hr>DuckStation uses RetroAchievements as an achievement database and " 136 "for tracking progress. To use achievements, please sign up for an account at retroachievements.org. To view " 137 "the achievement list in-game, press the hotkey for <strong>Open Pause Menu</strong> and select " 138 "<strong>Achievements</strong> from the menu. Mouse over an option for additional information, and " 139 "Shift+Wheel to scroll this panel.")); 140 141 if (!Achievements::IsUsingRAIntegration()) 142 { 143 addWidget(m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer), std::move(title), 144 std::move(icon_text), std::move(help_text)); 145 } 146 else 147 { 148 QLabel* placeholder_label = 149 new QLabel(QStringLiteral("RAIntegration is being used, built-in RetroAchievements support is disabled."), 150 m_ui.settingsContainer); 151 placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); 152 153 addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text)); 154 } 155 } 156 157 if (!isPerGameSettings()) 158 { 159 addWidget( 160 m_folder_settings = new FolderSettingsWidget(this, m_ui.settingsContainer), tr("Folders"), 161 QStringLiteral("folder-settings-line"), 162 tr("<strong>Folder Settings</strong><hr>These options control where DuckStation will save runtime data files.")); 163 } 164 165 addWidget(m_advanced_settings = new AdvancedSettingsWidget(this, m_ui.settingsContainer), tr("Advanced"), 166 QStringLiteral("alert-line"), 167 tr("<strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the " 168 "emulator. Mouse over an option for additional information, and Shift+Wheel to scroll this panel.")); 169 170 connect(m_advanced_settings, &AdvancedSettingsWidget::onShowDebugOptionsChanged, m_graphics_settings, 171 &GraphicsSettingsWidget::onShowDebugSettingsChanged); 172 } 173 174 void SettingsWindow::reloadPages() 175 { 176 const int min_count = isPerGameSettings() ? 1 : 0; 177 while (m_ui.settingsContainer->count() > min_count) 178 { 179 const int row = m_ui.settingsContainer->count() - 1; 180 181 delete m_ui.settingsCategory->takeItem(row); 182 183 QWidget* widget = m_ui.settingsContainer->widget(row); 184 m_ui.settingsContainer->removeWidget(widget); 185 delete widget; 186 } 187 188 addPages(); 189 } 190 191 void SettingsWindow::connectUi() 192 { 193 if (isPerGameSettings()) 194 { 195 m_ui.footerLayout->removeWidget(m_ui.restoreDefaults); 196 m_ui.restoreDefaults->deleteLater(); 197 m_ui.restoreDefaults = nullptr; 198 } 199 else 200 { 201 m_ui.footerLayout->removeWidget(m_ui.copyGlobalSettings); 202 m_ui.copyGlobalSettings->deleteLater(); 203 m_ui.copyGlobalSettings = nullptr; 204 m_ui.footerLayout->removeWidget(m_ui.clearGameSettings); 205 m_ui.clearGameSettings->deleteLater(); 206 m_ui.clearGameSettings = nullptr; 207 } 208 209 m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); 210 m_ui.settingsCategory->setCurrentRow(0); 211 m_ui.settingsContainer->setCurrentIndex(0); 212 m_ui.helpText->setText(m_category_help_text[0]); 213 connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsWindow::onCategoryCurrentRowChanged); 214 connect(m_ui.close, &QPushButton::clicked, this, &SettingsWindow::close); 215 if (m_ui.restoreDefaults) 216 connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &SettingsWindow::onRestoreDefaultsClicked); 217 if (m_ui.copyGlobalSettings) 218 connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this, &SettingsWindow::onCopyGlobalSettingsClicked); 219 if (m_ui.clearGameSettings) 220 connect(m_ui.clearGameSettings, &QPushButton::clicked, this, &SettingsWindow::onClearSettingsClicked); 221 } 222 223 void SettingsWindow::addWidget(QWidget* widget, QString title, QString icon, QString help_text) 224 { 225 const int index = m_ui.settingsCategory->count(); 226 227 QListWidgetItem* item = new QListWidgetItem(m_ui.settingsCategory); 228 item->setText(title); 229 if (!icon.isEmpty()) 230 item->setIcon(QIcon::fromTheme(icon)); 231 232 m_ui.settingsContainer->addWidget(widget); 233 234 m_category_help_text[index] = std::move(help_text); 235 } 236 237 void SettingsWindow::setCategory(const char* category) 238 { 239 // the titles in the category list will be translated. 240 const QString translated_category(tr(category)); 241 242 for (int i = 0; i < m_ui.settingsCategory->count(); i++) 243 { 244 if (translated_category == m_ui.settingsCategory->item(i)->text()) 245 { 246 // will also update the visible widget 247 m_ui.settingsCategory->setCurrentRow(i); 248 break; 249 } 250 } 251 } 252 253 int SettingsWindow::getCategoryRow() const 254 { 255 return m_ui.settingsCategory->currentRow(); 256 } 257 258 void SettingsWindow::setCategoryRow(int index) 259 { 260 m_ui.settingsCategory->setCurrentRow(index); 261 } 262 263 void SettingsWindow::onCategoryCurrentRowChanged(int row) 264 { 265 DebugAssert(row < static_cast<int>(MAX_SETTINGS_WIDGETS)); 266 m_ui.settingsContainer->setCurrentIndex(row); 267 m_ui.helpText->setText(m_category_help_text[row]); 268 } 269 270 void SettingsWindow::onRestoreDefaultsClicked() 271 { 272 if (QMessageBox::question(this, tr("Confirm Restore Defaults"), 273 tr("Are you sure you want to restore the default settings? Any preferences will be lost."), 274 QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) 275 { 276 return; 277 } 278 279 g_emu_thread->setDefaultSettings(true, false); 280 } 281 282 void SettingsWindow::onCopyGlobalSettingsClicked() 283 { 284 if (!isPerGameSettings()) 285 return; 286 287 if (QMessageBox::question( 288 this, tr("DuckStation Settings"), 289 tr("The configuration for this game will be replaced by the current global settings.\n\nAny current setting " 290 "values will be overwritten.\n\nDo you want to continue?"), 291 QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) 292 { 293 return; 294 } 295 296 { 297 auto lock = Host::GetSettingsLock(); 298 Settings temp; 299 temp.Load(*Host::Internal::GetBaseSettingsLayer(), *Host::Internal::GetBaseSettingsLayer()); 300 temp.Save(*m_sif.get(), true); 301 } 302 saveAndReloadGameSettings(); 303 304 reloadPages(); 305 306 QMessageBox::information(this, tr("DuckStation Settings"), tr("Per-game configuration copied from global settings.")); 307 } 308 309 void SettingsWindow::onClearSettingsClicked() 310 { 311 if (!isPerGameSettings()) 312 return; 313 314 if (QMessageBox::question(this, tr("DuckStation Settings"), 315 tr("The configuration for this game will be cleared.\n\nAny current setting values will be " 316 "lost.\n\nDo you want to continue?"), 317 QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) 318 { 319 return; 320 } 321 322 Settings::Clear(*m_sif.get()); 323 saveAndReloadGameSettings(); 324 325 reloadPages(); 326 327 QMessageBox::information(this, tr("DuckStation Settings"), tr("Per-game configuration cleared.")); 328 } 329 330 void SettingsWindow::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text) 331 { 332 // construct rich text with formatted description 333 QString full_text; 334 full_text += "<table width='100%' cellpadding='0' cellspacing='0'><tr><td><strong>"; 335 full_text += title; 336 full_text += "</strong></td><td align='right'><strong>"; 337 full_text += tr("Recommended Value"); 338 full_text += ": </strong>"; 339 full_text += recommended_value; 340 full_text += "</td></table><hr>"; 341 full_text += text; 342 343 m_widget_help_text_map[object] = std::move(full_text); 344 object->installEventFilter(this); 345 } 346 347 bool SettingsWindow::eventFilter(QObject* object, QEvent* event) 348 { 349 if (event->type() == QEvent::Enter) 350 { 351 auto iter = m_widget_help_text_map.constFind(object); 352 if (iter != m_widget_help_text_map.end()) 353 { 354 m_current_help_widget = object; 355 m_ui.helpText->setText(iter.value()); 356 } 357 } 358 else if (event->type() == QEvent::Leave) 359 { 360 if (m_current_help_widget) 361 { 362 m_current_help_widget = nullptr; 363 m_ui.helpText->setText(m_category_help_text[m_ui.settingsCategory->currentRow()]); 364 } 365 } 366 else if (event->type() == QEvent::Wheel) 367 { 368 if (handleWheelEvent(static_cast<QWheelEvent*>(event))) 369 return true; 370 } 371 372 return QWidget::eventFilter(object, event); 373 } 374 375 bool SettingsWindow::handleWheelEvent(QWheelEvent* event) 376 { 377 if (!(event->modifiers() & Qt::ShiftModifier)) 378 return false; 379 380 const int amount = event->hasPixelDelta() ? event->pixelDelta().y() : (event->angleDelta().y() / 20); 381 382 QScrollBar* sb = m_ui.helpText->verticalScrollBar(); 383 if (!sb) 384 return false; 385 386 sb->setSliderPosition(std::max(sb->sliderPosition() - amount, 0)); 387 return true; 388 } 389 390 void SettingsWindow::wheelEvent(QWheelEvent* event) 391 { 392 if (handleWheelEvent(event)) 393 return; 394 395 QWidget::wheelEvent(event); 396 } 397 398 bool SettingsWindow::getEffectiveBoolValue(const char* section, const char* key, bool default_value) const 399 { 400 bool value; 401 if (m_sif && m_sif->GetBoolValue(section, key, &value)) 402 return value; 403 else 404 return Host::GetBaseBoolSettingValue(section, key, default_value); 405 } 406 407 int SettingsWindow::getEffectiveIntValue(const char* section, const char* key, int default_value) const 408 { 409 int value; 410 if (m_sif && m_sif->GetIntValue(section, key, &value)) 411 return value; 412 else 413 return Host::GetBaseIntSettingValue(section, key, default_value); 414 } 415 416 float SettingsWindow::getEffectiveFloatValue(const char* section, const char* key, float default_value) const 417 { 418 float value; 419 if (m_sif && m_sif->GetFloatValue(section, key, &value)) 420 return value; 421 else 422 return Host::GetBaseFloatSettingValue(section, key, default_value); 423 } 424 425 std::string SettingsWindow::getEffectiveStringValue(const char* section, const char* key, 426 const char* default_value) const 427 { 428 std::string value; 429 if (!m_sif || !m_sif->GetStringValue(section, key, &value)) 430 value = Host::GetBaseStringSettingValue(section, key, default_value); 431 return value; 432 } 433 434 Qt::CheckState SettingsWindow::getCheckState(const char* section, const char* key, bool default_value) 435 { 436 bool value; 437 if (m_sif) 438 { 439 if (!m_sif->GetBoolValue(section, key, &value)) 440 return Qt::PartiallyChecked; 441 } 442 else 443 { 444 value = Host::GetBaseBoolSettingValue(section, key, default_value); 445 } 446 447 return value ? Qt::Checked : Qt::Unchecked; 448 } 449 450 std::optional<bool> SettingsWindow::getBoolValue(const char* section, const char* key, 451 std::optional<bool> default_value) const 452 { 453 std::optional<bool> value; 454 if (m_sif) 455 { 456 bool bvalue; 457 if (m_sif->GetBoolValue(section, key, &bvalue)) 458 value = bvalue; 459 else 460 value = default_value; 461 } 462 else 463 { 464 value = Host::GetBaseBoolSettingValue(section, key, default_value.value_or(false)); 465 } 466 467 return value; 468 } 469 470 std::optional<int> SettingsWindow::getIntValue(const char* section, const char* key, 471 std::optional<int> default_value) const 472 { 473 std::optional<int> value; 474 if (m_sif) 475 { 476 int ivalue; 477 if (m_sif->GetIntValue(section, key, &ivalue)) 478 value = ivalue; 479 else 480 value = default_value; 481 } 482 else 483 { 484 value = Host::GetBaseIntSettingValue(section, key, default_value.value_or(0)); 485 } 486 487 return value; 488 } 489 490 std::optional<float> SettingsWindow::getFloatValue(const char* section, const char* key, 491 std::optional<float> default_value) const 492 { 493 std::optional<float> value; 494 if (m_sif) 495 { 496 float fvalue; 497 if (m_sif->GetFloatValue(section, key, &fvalue)) 498 value = fvalue; 499 else 500 value = default_value; 501 } 502 else 503 { 504 value = Host::GetBaseFloatSettingValue(section, key, default_value.value_or(0.0f)); 505 } 506 507 return value; 508 } 509 510 std::optional<std::string> SettingsWindow::getStringValue(const char* section, const char* key, 511 std::optional<const char*> default_value) const 512 { 513 std::optional<std::string> value; 514 if (m_sif) 515 { 516 std::string svalue; 517 if (m_sif->GetStringValue(section, key, &svalue)) 518 value = std::move(svalue); 519 else if (default_value.has_value()) 520 value = default_value.value(); 521 } 522 else 523 { 524 value = Host::GetBaseStringSettingValue(section, key, default_value.value_or("")); 525 } 526 527 return value; 528 } 529 530 void SettingsWindow::setBoolSettingValue(const char* section, const char* key, std::optional<bool> value) 531 { 532 if (m_sif) 533 { 534 value.has_value() ? m_sif->SetBoolValue(section, key, value.value()) : m_sif->DeleteValue(section, key); 535 saveAndReloadGameSettings(); 536 } 537 else 538 { 539 value.has_value() ? Host::SetBaseBoolSettingValue(section, key, value.value()) : 540 Host::DeleteBaseSettingValue(section, key); 541 Host::CommitBaseSettingChanges(); 542 g_emu_thread->applySettings(); 543 } 544 } 545 546 void SettingsWindow::setIntSettingValue(const char* section, const char* key, std::optional<int> value) 547 { 548 if (m_sif) 549 { 550 value.has_value() ? m_sif->SetIntValue(section, key, value.value()) : m_sif->DeleteValue(section, key); 551 saveAndReloadGameSettings(); 552 } 553 else 554 { 555 value.has_value() ? Host::SetBaseIntSettingValue(section, key, value.value()) : 556 Host::DeleteBaseSettingValue(section, key); 557 Host::CommitBaseSettingChanges(); 558 g_emu_thread->applySettings(); 559 } 560 } 561 562 void SettingsWindow::setFloatSettingValue(const char* section, const char* key, std::optional<float> value) 563 { 564 if (m_sif) 565 { 566 value.has_value() ? m_sif->SetFloatValue(section, key, value.value()) : m_sif->DeleteValue(section, key); 567 saveAndReloadGameSettings(); 568 } 569 else 570 { 571 value.has_value() ? Host::SetBaseFloatSettingValue(section, key, value.value()) : 572 Host::DeleteBaseSettingValue(section, key); 573 Host::CommitBaseSettingChanges(); 574 g_emu_thread->applySettings(); 575 } 576 } 577 578 void SettingsWindow::setStringSettingValue(const char* section, const char* key, std::optional<const char*> value) 579 { 580 if (m_sif) 581 { 582 value.has_value() ? m_sif->SetStringValue(section, key, value.value()) : m_sif->DeleteValue(section, key); 583 saveAndReloadGameSettings(); 584 } 585 else 586 { 587 value.has_value() ? Host::SetBaseStringSettingValue(section, key, value.value()) : 588 Host::DeleteBaseSettingValue(section, key); 589 Host::CommitBaseSettingChanges(); 590 g_emu_thread->applySettings(); 591 } 592 } 593 594 bool SettingsWindow::containsSettingValue(const char* section, const char* key) const 595 { 596 if (m_sif) 597 return m_sif->ContainsValue(section, key); 598 else 599 return Host::ContainsBaseSettingValue(section, key); 600 } 601 602 void SettingsWindow::removeSettingValue(const char* section, const char* key) 603 { 604 if (m_sif) 605 { 606 m_sif->DeleteValue(section, key); 607 saveAndReloadGameSettings(); 608 } 609 else 610 { 611 Host::DeleteBaseSettingValue(section, key); 612 Host::CommitBaseSettingChanges(); 613 g_emu_thread->applySettings(); 614 } 615 } 616 617 void SettingsWindow::saveAndReloadGameSettings() 618 { 619 DebugAssert(m_sif); 620 QtHost::SaveGameSettings(m_sif.get(), true); 621 g_emu_thread->reloadGameSettings(false); 622 } 623 624 bool SettingsWindow::hasGameTrait(GameDatabase::Trait trait) 625 { 626 return (m_database_entry && m_database_entry->HasTrait(trait) && 627 m_sif->GetBoolValue("Main", "ApplyCompatibilitySettings", true)); 628 } 629 630 void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& title, 631 const std::string& serial, DiscRegion region) 632 { 633 const GameDatabase::Entry* dentry = nullptr; 634 if (!System::IsExeFileName(path) && !System::IsPsfFileName(path)) 635 { 636 // Need to resolve hash games. 637 Error error; 638 std::unique_ptr<CDImage> image = CDImage::Open(path.c_str(), false, &error); 639 if (image) 640 dentry = GameDatabase::GetEntryForDisc(image.get()); 641 else 642 ERROR_LOG("Failed to open '{}' for game properties: {}", path, error.GetDescription()); 643 644 if (!dentry) 645 { 646 // Use the serial and hope for the best... 647 dentry = GameDatabase::GetEntryForSerial(serial); 648 } 649 } 650 651 const std::string& real_serial = dentry ? dentry->serial : serial; 652 std::string ini_filename = System::GetGameSettingsPath(real_serial); 653 654 // check for an existing dialog with this crc 655 for (SettingsWindow* dialog : s_open_game_properties_dialogs) 656 { 657 if (dialog->isPerGameSettings() && 658 static_cast<INISettingsInterface*>(dialog->getSettingsInterface())->GetFileName() == ini_filename) 659 { 660 dialog->show(); 661 dialog->raise(); 662 dialog->activateWindow(); 663 dialog->setFocus(); 664 return; 665 } 666 } 667 668 std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(ini_filename)); 669 if (FileSystem::FileExists(sif->GetFileName().c_str())) 670 sif->Load(); 671 672 SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif)); 673 dialog->show(); 674 } 675 676 void SettingsWindow::closeGamePropertiesDialogs() 677 { 678 for (SettingsWindow* dialog : s_open_game_properties_dialogs) 679 { 680 dialog->close(); 681 dialog->deleteLater(); 682 } 683 } 684 685 bool SettingsWindow::setGameSettingsBoolForSerial(const std::string& serial, const char* section, const char* key, 686 bool value) 687 { 688 std::string ini_filename = System::GetGameSettingsPath(serial); 689 if (ini_filename.empty()) 690 return false; 691 692 INISettingsInterface sif(std::move(ini_filename)); 693 if (FileSystem::FileExists(sif.GetFileName().c_str())) 694 sif.Load(); 695 696 sif.SetBoolValue(section, key, value); 697 return sif.Save(); 698 }