Duckstation/src/duckstation-qt/settingswindow.cpp

581 lines
19 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "settingswindow.h"
2020-04-30 14:59:31 +00:00
#include "advancedsettingswidget.h"
2020-01-24 04:51:13 +00:00
#include "audiosettingswidget.h"
#include "biossettingswidget.h"
2019-12-31 06:17:17 +00:00
#include "consolesettingswidget.h"
2023-08-13 06:28:28 +00:00
2023-09-07 10:13:48 +00:00
#include "achievementsettingswidget.h"
#include "emulationsettingswidget.h"
#include "foldersettingswidget.h"
2019-12-31 06:17:17 +00:00
#include "gamelistsettingswidget.h"
#include "gamesummarywidget.h"
#include "graphicssettingswidget.h"
#include "interfacesettingswidget.h"
#include "mainwindow.h"
#include "memorycardsettingswidget.h"
#include "postprocessingsettingswidget.h"
#include "qthost.h"
2023-08-13 06:28:28 +00:00
2023-09-07 10:13:48 +00:00
#include "core/achievements.h"
2023-08-13 06:28:28 +00:00
#include "core/host.h"
#include "util/ini_settings_interface.h"
2023-08-13 06:28:28 +00:00
#include "common/assert.h"
#include "common/error.h"
2023-08-13 06:28:28 +00:00
#include "common/file_system.h"
#include "common/log.h"
2023-08-13 06:28:28 +00:00
#include <QtGui/QWheelEvent>
2021-07-09 03:05:52 +00:00
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QTextEdit>
2019-12-31 06:17:17 +00:00
Log_SetChannel(SettingsWindow);
static QList<SettingsWindow*> s_open_game_properties_dialogs;
SettingsWindow::SettingsWindow() : QWidget()
2019-12-31 06:17:17 +00:00
{
m_ui.setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
addPages();
}
2019-12-31 06:17:17 +00:00
SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region,
const GameDatabase::Entry* entry, std::unique_ptr<SettingsInterface> sif)
: QWidget(), m_sif(std::move(sif))
{
m_ui.setupUi(this);
2020-03-22 12:40:45 +00:00
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
addWidget(new GameSummaryWidget(path, serial, region, entry, this, m_ui.settingsContainer), tr("Summary"),
QStringLiteral("file-list-line"),
tr("<strong>Summary</strong><hr>This page shows information about the selected game, and allows you to "
"validate your disc was dumped correctly."));
addPages();
2019-12-31 06:17:17 +00:00
s_open_game_properties_dialogs.push_back(this);
}
2021-02-26 14:44:53 +00:00
SettingsWindow::~SettingsWindow()
{
if (isPerGameSettings())
s_open_game_properties_dialogs.removeOne(this);
}
2021-02-26 14:44:53 +00:00
2024-01-20 10:10:55 +00:00
void SettingsWindow::closeEvent(QCloseEvent* event)
{
// we need to clean up ourselves, since we're not modal
if (isPerGameSettings())
deleteLater();
}
void SettingsWindow::addPages()
{
addWidget(
m_general_settings = new InterfaceSettingsWidget(this, m_ui.settingsContainer), tr("Interface"),
QStringLiteral("settings-3-line"),
tr("<strong>Interface Settings</strong><hr>These options control how the emulator looks and "
"behaves.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
if (!isPerGameSettings())
2022-04-18 05:09:21 +00:00
{
addWidget(
m_game_list_settings = new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"),
2023-10-15 16:27:07 +00:00
QStringLiteral("folder-open-line"),
tr("<strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by "
"DuckStation to populate the game list. Search directories can be added, removed, and switched to "
"recursive/non-recursive."));
2022-04-18 05:09:21 +00:00
}
addWidget(
m_bios_settings = new BIOSSettingsWidget(this, m_ui.settingsContainer), tr("BIOS"), QStringLiteral("chip-line"),
tr("<strong>BIOS Settings</strong><hr>These options control which BIOS is used and how it will be "
"patched.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
addWidget(
m_console_settings = new ConsoleSettingsWidget(this, m_ui.settingsContainer), tr("Console"),
QStringLiteral("chip-2-line"),
tr("<strong>Console Settings</strong><hr>These options determine the configuration of the simulated "
"console.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
addWidget(
m_emulation_settings = new EmulationSettingsWidget(this, m_ui.settingsContainer), tr("Emulation"),
QStringLiteral("emulation-line"),
tr("<strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the "
"system.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
addWidget(
m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"),
QStringLiteral("memcard-line"),
tr("<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will "
"function in, and where the images for these cards will be stored on disk."));
addWidget(m_graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"),
QStringLiteral("image-fill"),
tr("<strong>Graphics Settings</strong><hr>These options control how the graphics of the emulated console "
"are rendered. Not all options are available for the software renderer. Mouse over each option for "
"additional information, and Shift+Wheel to scroll this panel."));
addWidget(
m_post_processing_settings = new PostProcessingSettingsWidget(this, m_ui.settingsContainer), tr("Post-Processing"),
QStringLiteral("sun-fill"),
tr("<strong>Post-Processing Settings</strong><hr>Post processing allows you to alter the appearance of the image "
"displayed on the screen with various filters. Shaders will be executed in sequence."));
addWidget(
m_audio_settings = new AudioSettingsWidget(this, m_ui.settingsContainer), tr("Audio"),
QStringLiteral("volume-up-line"),
tr("<strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option "
"for additional information."));
{
QString title(tr("Achievements"));
QString icon_text(QStringLiteral("trophy-line"));
QString help_text(tr("<strong>Achievement Settings</strong><hr>These options control RetroAchievements. Mouse over "
"an option for additional information, and Shift+Wheel to scroll this panel."));
if (!Achievements::IsUsingRAIntegration())
{
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(QStringLiteral("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));
}
}
2021-02-26 14:44:53 +00:00
if (!isPerGameSettings())
{
addWidget(
m_folder_settings = new FolderSettingsWidget(this, m_ui.settingsContainer), tr("Folders"),
QStringLiteral("folder-settings-line"),
2022-07-22 14:01:47 +00:00
tr("<strong>Folder Settings</strong><hr>These options control where DuckStation will save runtime data files."));
}
2019-12-31 06:17:17 +00:00
addWidget(m_advanced_settings = new AdvancedSettingsWidget(this, m_ui.settingsContainer), tr("Advanced"),
2024-02-05 13:34:34 +00:00
QStringLiteral("alert-line"),
tr("<strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the "
"emulator. Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
connect(m_advanced_settings, &AdvancedSettingsWidget::onShowDebugOptionsChanged, m_graphics_settings,
&GraphicsSettingsWidget::onShowDebugSettingsChanged);
if (isPerGameSettings())
{
m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->setVisible(false);
}
m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
2019-12-31 06:17:17 +00:00
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, &SettingsWindow::onCategoryCurrentRowChanged);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &SettingsWindow::close);
connect(m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this,
&SettingsWindow::onRestoreDefaultsClicked);
}
void SettingsWindow::addWidget(QWidget* widget, QString title, QString icon, QString help_text)
2019-12-31 06:17:17 +00:00
{
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);
2019-12-31 06:17:17 +00:00
m_category_help_text[index] = std::move(help_text);
}
void SettingsWindow::setCategory(const char* category)
{
// the titles in the category list will be translated.
const QString translated_category(tr(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;
}
}
2019-12-31 06:17:17 +00:00
}
void SettingsWindow::onCategoryCurrentRowChanged(int row)
2019-12-31 06:17:17 +00:00
{
2023-09-03 04:30:26 +00:00
DebugAssert(row < static_cast<int>(MAX_SETTINGS_WIDGETS));
2019-12-31 06:17:17 +00:00
m_ui.settingsContainer->setCurrentIndex(row);
m_ui.helpText->setText(m_category_help_text[row]);
2019-12-31 06:17:17 +00:00
}
void SettingsWindow::onRestoreDefaultsClicked()
2021-07-09 03:05:52 +00:00
{
if (QMessageBox::question(this, tr("Confirm Restore Defaults"),
tr("Are you sure you want to restore the default settings? Any preferences will be lost."),
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
2021-07-09 03:05:52 +00:00
{
return;
}
g_emu_thread->setDefaultSettings(true, false);
2021-07-09 03:05:52 +00:00
}
void SettingsWindow::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text)
{
// construct rich text with formatted description
QString full_text;
full_text += "<table width='100%' cellpadding='0' cellspacing='0'><tr><td><strong>";
full_text += title;
full_text += "</strong></td><td align='right'><strong>";
full_text += tr("Recommended Value");
full_text += ": </strong>";
full_text += recommended_value;
full_text += "</td></table><hr>";
full_text += text;
m_widget_help_text_map[object] = std::move(full_text);
object->installEventFilter(this);
}
bool SettingsWindow::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()]);
}
}
else if (event->type() == QEvent::Wheel)
{
if (handleWheelEvent(static_cast<QWheelEvent*>(event)))
return true;
}
return QWidget::eventFilter(object, event);
}
bool SettingsWindow::handleWheelEvent(QWheelEvent* event)
{
if (!(event->modifiers() & Qt::ShiftModifier))
return false;
const int amount = event->hasPixelDelta() ? event->pixelDelta().y() : (event->angleDelta().y() / 20);
QScrollBar* sb = m_ui.helpText->verticalScrollBar();
if (!sb)
return false;
sb->setSliderPosition(std::max(sb->sliderPosition() - amount, 0));
return true;
}
void SettingsWindow::wheelEvent(QWheelEvent* event)
{
if (handleWheelEvent(event))
return;
QWidget::wheelEvent(event);
}
bool SettingsWindow::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 SettingsWindow::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 SettingsWindow::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 SettingsWindow::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;
}
Qt::CheckState SettingsWindow::getCheckState(const char* section, const char* key, bool default_value)
{
bool value;
if (m_sif)
{
if (!m_sif->GetBoolValue(section, key, &value))
return Qt::PartiallyChecked;
}
else
{
value = Host::GetBaseBoolSettingValue(section, key, default_value);
}
return value ? Qt::Checked : Qt::Unchecked;
}
std::optional<bool> SettingsWindow::getBoolValue(const char* section, const char* key,
std::optional<bool> default_value) const
{
std::optional<bool> 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<int> SettingsWindow::getIntValue(const char* section, const char* key,
std::optional<int> default_value) const
{
std::optional<int> 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<float> SettingsWindow::getFloatValue(const char* section, const char* key,
std::optional<float> default_value) const
{
std::optional<float> 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<std::string> SettingsWindow::getStringValue(const char* section, const char* key,
std::optional<const char*> default_value) const
{
std::optional<std::string> 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 SettingsWindow::setBoolSettingValue(const char* section, const char* key, std::optional<bool> 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::DeleteBaseSettingValue(section, key);
2022-09-17 05:51:05 +00:00
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
}
void SettingsWindow::setIntSettingValue(const char* section, const char* key, std::optional<int> 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::DeleteBaseSettingValue(section, key);
2022-09-17 05:51:05 +00:00
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
}
void SettingsWindow::setFloatSettingValue(const char* section, const char* key, std::optional<float> 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::DeleteBaseSettingValue(section, key);
2022-09-17 05:51:05 +00:00
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
}
void SettingsWindow::setStringSettingValue(const char* section, const char* key, std::optional<const char*> 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::DeleteBaseSettingValue(section, key);
2022-09-17 05:51:05 +00:00
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
}
bool SettingsWindow::containsSettingValue(const char* section, const char* key) const
{
if (m_sif)
return m_sif->ContainsValue(section, key);
else
return Host::ContainsBaseSettingValue(section, key);
}
void SettingsWindow::removeSettingValue(const char* section, const char* key)
{
if (m_sif)
{
m_sif->DeleteValue(section, key);
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
Host::DeleteBaseSettingValue(section, key);
2022-09-17 05:51:05 +00:00
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
}
void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& serial, DiscRegion region)
{
const GameDatabase::Entry* dentry = nullptr;
if (!System::IsExeFileName(path) && !System::IsPsfFileName(path))
{
// Need to resolve hash games.
Error error;
std::unique_ptr<CDImage> image = CDImage::Open(path.c_str(), false, &error);
if (image)
dentry = GameDatabase::GetEntryForDisc(image.get());
else
Log_ErrorFmt("Failed to open '{}' for game properties: {}", path, error.GetDescription());
if (!dentry)
{
// Use the serial and hope for the best...
dentry = GameDatabase::GetEntryForSerial(serial);
}
}
const std::string& real_serial = dentry ? dentry->serial : serial;
std::string ini_filename = System::GetGameSettingsPath(real_serial);
// check for an existing dialog with this crc
for (SettingsWindow* dialog : s_open_game_properties_dialogs)
{
if (dialog->isPerGameSettings() &&
static_cast<INISettingsInterface*>(dialog->getSettingsInterface())->GetFileName() == ini_filename)
{
dialog->show();
dialog->raise();
dialog->activateWindow();
dialog->setFocus();
return;
}
}
std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(ini_filename));
if (FileSystem::FileExists(sif->GetFileName().c_str()))
sif->Load();
const QString window_title(tr("%1 [%2]")
.arg(dentry ? QtUtils::StringViewToQString(dentry->title) : QStringLiteral("<UNKNOWN>"))
.arg(QtUtils::StringViewToQString(real_serial)));
SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif));
dialog->setWindowTitle(window_title);
dialog->show();
2022-07-22 14:01:47 +00:00
}
void SettingsWindow::closeGamePropertiesDialogs()
{
for (SettingsWindow* dialog : s_open_game_properties_dialogs)
{
dialog->close();
dialog->deleteLater();
}
}