/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2022 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "common/FileSystem.h" #include "common/Path.h" #include "common/StringUtil.h" #include "pcsx2/Frontend/GameList.h" #include "pcsx2/HostSettings.h" #include "pcsx2/INISettingsInterface.h" #include "MainWindow.h" #include "QtHost.h" #include "QtUtils.h" #include "SettingsDialog.h" #include "AdvancedSystemSettingsWidget.h" #include "AudioSettingsWidget.h" #include "BIOSSettingsWidget.h" #include "EmulationSettingsWidget.h" #include "GameSummaryWidget.h" #include "GameFixSettingsWidget.h" #include "GameListSettingsWidget.h" #include "GraphicsSettingsWidget.h" #include "DEV9SettingsWidget.h" #include "FolderSettingsWidget.h" #include "HotkeySettingsWidget.h" #include "InterfaceSettingsWidget.h" #include "MemoryCardSettingsWidget.h" #include "SystemSettingsWidget.h" #ifdef ENABLE_ACHIEVEMENTS #include "AchievementSettingsWidget.h" #include "pcsx2/Frontend/Achievements.h" #endif #include #include static QList s_open_game_properties_dialogs; SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) , m_game_crc(0) { setupUi(nullptr); } SettingsDialog::SettingsDialog(QWidget* parent, std::unique_ptr sif, const GameList::Entry* game, u32 game_crc) : QDialog(parent) , m_sif(std::move(sif)) , m_game_crc(game_crc) { setupUi(game); s_open_game_properties_dialogs.push_back(this); } void SettingsDialog::setupUi(const GameList::Entry* game) { const bool show_advanced_settings = true; m_ui.setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); if (isPerGameSettings()) { if (game) { addWidget(new GameSummaryWidget(game, this, m_ui.settingsContainer), tr("Summary"), QStringLiteral("file-list-line"), tr("Summary
Eventually this will be where we can see patches and compute hashes/verify dumps/etc.")); } m_ui.restoreDefaultsButton->setVisible(false); } addWidget(m_interface_settings = new InterfaceSettingsWidget(this, m_ui.settingsContainer), tr("Interface"), QStringLiteral("settings-3-line"), tr("Interface Settings
These options control how the software looks and behaves.

Mouse over an option for " "additional information.")); // We don't include game list/bios settings in per-game settings. if (!isPerGameSettings()) { addWidget(m_game_list_settings = new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"), QStringLiteral("folder-settings-line"), tr("Game List Settings
The list above shows the directories which will be searched by PCSX2 to populate the game " "list. Search directories can be added, removed, and switched to recursive/non-recursive.")); addWidget(m_bios_settings = new BIOSSettingsWidget(this, m_ui.settingsContainer), tr("BIOS"), QStringLiteral("hard-drive-2-line"), tr("BIOS Settings
Configure your BIOS here.

Mouse over an option for additional information.")); } // Common to both per-game and global settings. addWidget(m_emulation_settings = new EmulationSettingsWidget(this, m_ui.settingsContainer), tr("Emulation"), QStringLiteral("dashboard-line"), tr("Emulation Settings
These options determine the configuration of frame pacing and game settings.

Mouse over an option for additional information.")); addWidget(m_system_settings = new SystemSettingsWidget(this, m_ui.settingsContainer), tr("System"), QStringLiteral("artboard-2-line"), tr("System Settings
These options determine the configuration of the simulated console.

Mouse over an option for additional information.")); if (show_advanced_settings) { addWidget(m_advanced_system_settings = new AdvancedSystemSettingsWidget(this, m_ui.settingsContainer), tr("Advanced System"), QStringLiteral("artboard-2-line"), tr("Advanced System Settings
These are Advanced options to determine the configuration of the simulated console.

Mouse over an option for additional information.")); // Only show the game fixes for per-game settings, there's really no reason to be setting them globally. if (isPerGameSettings()) { addWidget(m_game_fix_settings_widget = new GameFixSettingsWidget(this, m_ui.settingsContainer), tr("Game Fix"), QStringLiteral("close-line"), tr("Game Fix Settings
Gamefixes can work around incorrect emulation in some titles
however they can also cause problems in games if used incorrectly.
It is best to leave them all disabled unless advised otherwise.")); } } addWidget(m_graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"), QStringLiteral("brush-line"), tr("Graphics Settings
These options determine the configuration of the graphical output.

Mouse over an option for additional information.")); addWidget(m_audio_settings = new AudioSettingsWidget(this, m_ui.settingsContainer), tr("Audio"), QStringLiteral("volume-up-line"), tr("Audio Settings
These options control the audio output of the console.

Mouse over an option for additional information.")); // for now, memory cards aren't settable per-game if (!isPerGameSettings()) { addWidget(m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"), QStringLiteral("sd-card-line"), tr("Memory Card Settings
Create and configure Memory Cards here.

Mouse over an option for additional information.")); } addWidget(m_dev9_settings = new DEV9SettingsWidget(this, m_ui.settingsContainer), tr("Network & HDD"), QStringLiteral("dashboard-line"), tr("Network & HDD Settings
These options control the network connectivity and internal HDD storage of the console.

" "Mouse over an option for additional information.")); if (!isPerGameSettings()) { addWidget(m_folder_settings = new FolderSettingsWidget(this, m_ui.settingsContainer), tr("Folders"), QStringLiteral("folder-open-line"), tr("Folder Settings
These options control where PCSX2 will save runtime data files.")); } { QString title = tr("Achievements"); QString icon_text(QStringLiteral("trophy-line")); QString help_text = tr( "Achievements Settings
" "These options control the RetroAchievements implementation in PCSX2, allowing you to earn achievements in your games."); #ifdef ENABLE_ACHIEVEMENTS if (Achievements::IsUsingRAIntegration()) { QLabel* placeholder_label = new QLabel(tr("RAIntegration is being used, built-in RetroAchievements support is disabled."), m_ui.settingsContainer); placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text)); } else { addWidget((m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer)), std::move(title), std::move(icon_text), std::move(help_text)); } #else QLabel* placeholder_label = new QLabel(tr("This PCSX2 build was not compiled with RetroAchievements support."), m_ui.settingsContainer); placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text)); #endif } m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_ui.settingsCategory->setCurrentRow(0); m_ui.settingsContainer->setCurrentIndex(0); m_ui.helpText->setText(m_category_help_text[0]); connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsDialog::onCategoryCurrentRowChanged); connect(m_ui.closeButton, &QPushButton::clicked, this, &SettingsDialog::accept); connect(m_ui.restoreDefaultsButton, &QPushButton::clicked, this, &SettingsDialog::onRestoreDefaultsClicked); } SettingsDialog::~SettingsDialog() { if (isPerGameSettings()) s_open_game_properties_dialogs.removeOne(this); } void SettingsDialog::closeEvent(QCloseEvent*) { // we need to clean up ourselves, since we're not modal if (isPerGameSettings()) deleteLater(); } QString SettingsDialog::getCategory() const { return m_ui.settingsCategory->item(m_ui.settingsCategory->currentRow())->text(); } void SettingsDialog::setCategory(const char* category) { // the titles in the category list will be translated. const QString translated_category(qApp->translate("SettingsDialog", category)); for (int i = 0; i < m_ui.settingsCategory->count(); i++) { if (translated_category == m_ui.settingsCategory->item(i)->text()) { // will also update the visible widget m_ui.settingsCategory->setCurrentRow(i); break; } } } void SettingsDialog::onCategoryCurrentRowChanged(int row) { m_ui.settingsContainer->setCurrentIndex(row); m_ui.helpText->setText(m_category_help_text[row]); } void SettingsDialog::onRestoreDefaultsClicked() { QMessageBox msgbox(this); msgbox.setIcon(QMessageBox::Question); msgbox.setWindowTitle(tr("Confirm Restore Defaults")); msgbox.setText(tr("Are you sure you want to restore the default settings? Any preferences will be lost.")); QCheckBox* ui_cb = new QCheckBox(tr("Reset UI Settings"), &msgbox); msgbox.setCheckBox(ui_cb); msgbox.addButton(QMessageBox::Yes); msgbox.addButton(QMessageBox::No); msgbox.setDefaultButton(QMessageBox::Yes); if (msgbox.exec() != QMessageBox::Yes) return; g_main_window->resetSettings(ui_cb->isChecked()); } void SettingsDialog::addWidget(QWidget* widget, QString title, QString icon, QString help_text) { const int index = m_ui.settingsCategory->count(); QListWidgetItem* item = new QListWidgetItem(m_ui.settingsCategory); item->setText(title); if (!icon.isEmpty()) item->setIcon(QIcon::fromTheme(icon)); m_ui.settingsContainer->addWidget(widget); m_category_help_text[index] = std::move(help_text); } void SettingsDialog::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text) { if (!object) return; // construct rich text with formatted description QString full_text; full_text += "
"; full_text += title; full_text += ""; full_text += tr("Recommended Value"); full_text += ": "; full_text += recommended_value; full_text += "

"; full_text += text; m_widget_help_text_map[object] = std::move(full_text); object->installEventFilter(this); } bool SettingsDialog::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Enter) { auto iter = m_widget_help_text_map.constFind(object); if (iter != m_widget_help_text_map.end()) { m_current_help_widget = object; m_ui.helpText->setText(iter.value()); } } else if (event->type() == QEvent::Leave) { if (m_current_help_widget) { m_current_help_widget = nullptr; m_ui.helpText->setText(m_category_help_text[m_ui.settingsCategory->currentRow()]); } } return QDialog::eventFilter(object, event); } bool SettingsDialog::getEffectiveBoolValue(const char* section, const char* key, bool default_value) const { bool value; if (m_sif && m_sif->GetBoolValue(section, key, &value)) return value; else return Host::GetBaseBoolSettingValue(section, key, default_value); } int SettingsDialog::getEffectiveIntValue(const char* section, const char* key, int default_value) const { int value; if (m_sif && m_sif->GetIntValue(section, key, &value)) return value; else return Host::GetBaseIntSettingValue(section, key, default_value); } float SettingsDialog::getEffectiveFloatValue(const char* section, const char* key, float default_value) const { float value; if (m_sif && m_sif->GetFloatValue(section, key, &value)) return value; else return Host::GetBaseFloatSettingValue(section, key, default_value); } std::string SettingsDialog::getEffectiveStringValue(const char* section, const char* key, const char* default_value) const { std::string value; if (!m_sif || !m_sif->GetStringValue(section, key, &value)) value = Host::GetBaseStringSettingValue(section, key, default_value); return value; } std::optional SettingsDialog::getBoolValue(const char* section, const char* key, std::optional default_value) const { std::optional value; if (m_sif) { bool bvalue; if (m_sif->GetBoolValue(section, key, &bvalue)) value = bvalue; else value = default_value; } else { value = Host::GetBaseBoolSettingValue(section, key, default_value.value_or(false)); } return value; } std::optional SettingsDialog::getIntValue(const char* section, const char* key, std::optional default_value) const { std::optional value; if (m_sif) { int ivalue; if (m_sif->GetIntValue(section, key, &ivalue)) value = ivalue; else value = default_value; } else { value = Host::GetBaseIntSettingValue(section, key, default_value.value_or(0)); } return value; } std::optional SettingsDialog::getFloatValue(const char* section, const char* key, std::optional default_value) const { std::optional value; if (m_sif) { float fvalue; if (m_sif->GetFloatValue(section, key, &fvalue)) value = fvalue; else value = default_value; } else { value = Host::GetBaseFloatSettingValue(section, key, default_value.value_or(0.0f)); } return value; } std::optional SettingsDialog::getStringValue(const char* section, const char* key, std::optional default_value) const { std::optional value; if (m_sif) { std::string svalue; if (m_sif->GetStringValue(section, key, &svalue)) value = std::move(svalue); else if (default_value.has_value()) value = default_value.value(); } else { value = Host::GetBaseStringSettingValue(section, key, default_value.value_or("")); } return value; } void SettingsDialog::setBoolSettingValue(const char* section, const char* key, std::optional value) { if (m_sif) { value.has_value() ? m_sif->SetBoolValue(section, key, value.value()) : m_sif->DeleteValue(section, key); m_sif->Save(); g_emu_thread->reloadGameSettings(); } else { value.has_value() ? Host::SetBaseBoolSettingValue(section, key, value.value()) : Host::RemoveBaseSettingValue(section, key); Host::CommitBaseSettingChanges(); g_emu_thread->applySettings(); } } void SettingsDialog::setIntSettingValue(const char* section, const char* key, std::optional value) { if (m_sif) { value.has_value() ? m_sif->SetIntValue(section, key, value.value()) : m_sif->DeleteValue(section, key); m_sif->Save(); g_emu_thread->reloadGameSettings(); } else { value.has_value() ? Host::SetBaseIntSettingValue(section, key, value.value()) : Host::RemoveBaseSettingValue(section, key); Host::CommitBaseSettingChanges(); g_emu_thread->applySettings(); } } void SettingsDialog::setFloatSettingValue(const char* section, const char* key, std::optional value) { if (m_sif) { value.has_value() ? m_sif->SetFloatValue(section, key, value.value()) : m_sif->DeleteValue(section, key); m_sif->Save(); g_emu_thread->reloadGameSettings(); } else { value.has_value() ? Host::SetBaseFloatSettingValue(section, key, value.value()) : Host::RemoveBaseSettingValue(section, key); Host::CommitBaseSettingChanges(); g_emu_thread->applySettings(); } } void SettingsDialog::setStringSettingValue(const char* section, const char* key, std::optional value) { if (m_sif) { value.has_value() ? m_sif->SetStringValue(section, key, value.value()) : m_sif->DeleteValue(section, key); m_sif->Save(); g_emu_thread->reloadGameSettings(); } else { value.has_value() ? Host::SetBaseStringSettingValue(section, key, value.value()) : Host::RemoveBaseSettingValue(section, key); Host::CommitBaseSettingChanges(); g_emu_thread->applySettings(); } } void SettingsDialog::openGamePropertiesDialog(const GameList::Entry* game, const std::string_view& serial, u32 crc) { // check for an existing dialog with this crc for (SettingsDialog* dialog : s_open_game_properties_dialogs) { if (dialog->m_game_crc == crc) { dialog->show(); dialog->setFocus(); return; } } std::string filename(VMManager::GetGameSettingsPath(serial, crc)); std::unique_ptr sif = std::make_unique(std::move(filename)); if (FileSystem::FileExists(sif->GetFileName().c_str())) sif->Load(); const QString window_title(tr("%1 [%2]") .arg(game ? QtUtils::StringViewToQString(game->title) : QStringLiteral("")) .arg(QtUtils::StringViewToQString(Path::GetFileName(sif->GetFileName())))); SettingsDialog* dialog = new SettingsDialog(g_main_window, std::move(sif), game, crc); dialog->setWindowTitle(window_title); dialog->setModal(false); dialog->show(); }