Path: blob/master/src/duckstation-qt/advancedsettingswidget.cpp
4242 views
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "advancedsettingswidget.h"4#include "logwindow.h"5#include "mainwindow.h"6#include "qtutils.h"7#include "settingswindow.h"8#include "settingwidgetbinder.h"910#include "core/gpu_types.h"1112#include <QtGui/QCursor>13#include <QtWidgets/QMenu>1415#include "moc_advancedsettingswidget.cpp"1617static QCheckBox* addBooleanTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name, std::string section,18std::string key, bool default_value)19{20const int row = table->rowCount();2122table->insertRow(row);2324QTableWidgetItem* name_item = new QTableWidgetItem(name);25name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));26table->setItem(row, 0, name_item);2728QCheckBox* cb = new QCheckBox(table);29if (!section.empty() || !key.empty())30{31SettingWidgetBinder::BindWidgetToBoolSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),32default_value);33}3435table->setCellWidget(row, 1, cb);36return cb;37}3839static QCheckBox* setBooleanTweakOption(QTableWidget* table, int row, bool value)40{41QWidget* widget = table->cellWidget(row, 1);42QCheckBox* cb = qobject_cast<QCheckBox*>(widget);43Assert(cb);44cb->setChecked(value);45return cb;46}4748static QSpinBox* addIntRangeTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name, std::string section,49std::string key, int min_value, int max_value, int default_value,50const QString& suffix = QString())51{52const int row = table->rowCount();5354table->insertRow(row);5556QTableWidgetItem* name_item = new QTableWidgetItem(name);57name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));58table->setItem(row, 0, name_item);5960QSpinBox* cb = new QSpinBox(table);61cb->setMinimum(min_value);62cb->setMaximum(max_value);63if (!suffix.isEmpty())64cb->setSuffix(suffix);6566if (!section.empty() || !key.empty())67{68SettingWidgetBinder::BindWidgetToIntSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),69default_value);70}7172table->setCellWidget(row, 1, cb);73return cb;74}7576static QSpinBox* setIntRangeTweakOption(QTableWidget* table, int row, int value)77{78QWidget* widget = table->cellWidget(row, 1);79QSpinBox* cb = qobject_cast<QSpinBox*>(widget);80Assert(cb);81cb->setValue(value);82return cb;83}8485template<typename T>86static QComboBox* addChoiceTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name, std::string section,87std::string key, std::optional<T> (*parse_callback)(const char*),88const char* (*get_value_callback)(T), const char* (*get_display_callback)(T),89u32 num_values, T default_value)90{91const int row = table->rowCount();9293table->insertRow(row);9495QTableWidgetItem* name_item = new QTableWidgetItem(name);96name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));97table->setItem(row, 0, name_item);9899QComboBox* cb = new QComboBox(table);100for (u32 i = 0; i < num_values; i++)101cb->addItem(QString::fromUtf8(get_display_callback(static_cast<T>(i))));102103if (!section.empty() || !key.empty())104{105SettingWidgetBinder::BindWidgetToEnumSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),106parse_callback, get_value_callback, default_value);107}108109table->setCellWidget(row, 1, cb);110return cb;111}112113template<typename T>114static void setChoiceTweakOption(QTableWidget* table, int row, T value)115{116QWidget* widget = table->cellWidget(row, 1);117QComboBox* cb = qobject_cast<QComboBox*>(widget);118Assert(cb);119cb->setCurrentIndex(static_cast<int>(value));120}121122static void addDirectoryOption(SettingsWindow* dialog, QTableWidget* table, const QString& name, std::string section,123std::string key)124{125const int row = table->rowCount();126127table->insertRow(row);128129QTableWidgetItem* name_item = new QTableWidgetItem(name);130name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));131table->setItem(row, 0, name_item);132133QWidget* container = new QWidget(table);134135QHBoxLayout* layout = new QHBoxLayout(container);136layout->setContentsMargins(0, 0, 0, 0);137138QLineEdit* value = new QLineEdit(container);139value->setObjectName(QStringLiteral("value"));140SettingWidgetBinder::BindWidgetToStringSetting(dialog->getSettingsInterface(), value, std::move(section),141std::move(key));142layout->addWidget(value, 1);143144QPushButton* browse = new QPushButton(container);145browse->setText(QStringLiteral("..."));146browse->setMaximumWidth(32);147QObject::connect(browse, &QPushButton::clicked, browse, [browse, value, name]() {148const QString path(QDir::toNativeSeparators(QFileDialog::getExistingDirectory(149QtUtils::GetRootWidget(browse), qApp->translate("AdvancedSettingsWidget", "Select folder for %1").arg(name),150value->text())));151if (!path.isEmpty())152value->setText(path);153});154layout->addWidget(browse, 0);155156table->setCellWidget(row, 1, container);157}158159static void setDirectoryOption(QTableWidget* table, int row, const char* value)160{161QWidget* widget = table->cellWidget(row, 1);162Assert(widget);163QLineEdit* valuew = widget->findChild<QLineEdit*>(QStringLiteral("value"));164Assert(valuew);165valuew->setText(QString::fromUtf8(value));166}167168AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget* parent)169: QWidget(parent), m_dialog(dialog)170{171SettingsInterface* sif = dialog->getSettingsInterface();172173m_ui.setupUi(this);174175for (u32 i = 0; i < static_cast<u32>(Log::Level::MaxCount); i++)176m_ui.logLevel->addItem(QString::fromUtf8(Settings::GetLogLevelDisplayName(static_cast<Log::Level>(i))));177178SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.logLevel, "Logging", "LogLevel", &Settings::ParseLogLevelName,179&Settings::GetLogLevelName, Settings::DEFAULT_LOG_LEVEL);180SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToConsole, "Logging", "LogToConsole", false);181SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToDebug, "Logging", "LogToDebug", false);182SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToWindow, "Logging", "LogToWindow", false);183SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToFile, "Logging", "LogToFile", false);184SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logTimestamps, "Logging", "LogTimestamps", true);185SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logFileTimestamps, "Logging", "LogFileTimestamps", false);186connect(m_ui.logToConsole, &QCheckBox::checkStateChanged, this, &AdvancedSettingsWidget::onAnyLogSinksChanged);187connect(m_ui.logToWindow, &QCheckBox::checkStateChanged, this, &AdvancedSettingsWidget::onAnyLogSinksChanged);188connect(m_ui.logToFile, &QCheckBox::checkStateChanged, this, &AdvancedSettingsWidget::onAnyLogSinksChanged);189onAnyLogSinksChanged(); // initialize enabled/disabled state of checkboxes190191connect(m_ui.logChannels, &QToolButton::clicked, this, &AdvancedSettingsWidget::onLogChannelsButtonClicked);192193SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showDebugMenu, "Main", "ShowDebugMenu", false);194connect(m_ui.showDebugMenu, &QCheckBox::checkStateChanged, g_main_window, &MainWindow::updateDebugMenuVisibility,195Qt::QueuedConnection);196connect(m_ui.showDebugMenu, &QCheckBox::checkStateChanged, this,197&AdvancedSettingsWidget::onShowDebugOptionsStateChanged);198199connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);200201m_ui.tweakOptionTable->setColumnWidth(0, 380);202m_ui.tweakOptionTable->setColumnWidth(1, 170);203204addTweakOptions();205206dialog->registerWidgetHelp(m_ui.logLevel, tr("Log Level"), tr("Information"),207tr("Sets the verbosity of messages logged. Higher levels will log more messages."));208dialog->registerWidgetHelp(m_ui.logToConsole, tr("Log To System Console"), tr("User Preference"),209tr("Logs messages to the console window."));210dialog->registerWidgetHelp(m_ui.logToDebug, tr("Log To Debug Console"), tr("User Preference"),211tr("Logs messages to the debug console where supported."));212dialog->registerWidgetHelp(m_ui.logToWindow, tr("Log To Window"), tr("User Preference"),213tr("Logs messages to the window."));214dialog->registerWidgetHelp(m_ui.logToFile, tr("Log To File"), tr("User Preference"),215tr("Logs messages to duckstation.log in the user directory."));216dialog->registerWidgetHelp(m_ui.logTimestamps, tr("Log Timestamps"), tr("User Preference"),217tr("Includes the elapsed time since the application start in window and console logs."));218dialog->registerWidgetHelp(m_ui.logFileTimestamps, tr("Log File Timestamps"), tr("User Preference"),219tr("Includes the elapsed time since the application start in file logs."));220dialog->registerWidgetHelp(m_ui.showDebugMenu, tr("Show Debug Menu"), tr("Unchecked"),221tr("Shows a debug menu bar with additional statistics and quick settings."));222}223224AdvancedSettingsWidget::~AdvancedSettingsWidget() = default;225226void AdvancedSettingsWidget::onLogChannelsButtonClicked()227{228QMenu menu;229LogWindow::populateFilterMenu(&menu);230menu.exec(QCursor::pos());231}232233void AdvancedSettingsWidget::onAnyLogSinksChanged()234{235const bool log_to_console = m_dialog->getEffectiveBoolValue("Logging", "LogToConsole", false);236const bool log_to_window = m_dialog->getEffectiveBoolValue("Logging", "LogToWindow", false);237const bool log_to_file = m_dialog->getEffectiveBoolValue("Logging", "LogToFile", false);238239m_ui.logTimestamps->setEnabled(log_to_console || log_to_window);240m_ui.logFileTimestamps->setEnabled(log_to_file);241}242243void AdvancedSettingsWidget::onShowDebugOptionsStateChanged()244{245const bool enabled = QtHost::ShouldShowDebugOptions();246emit onShowDebugOptionsChanged(enabled);247}248249void AdvancedSettingsWidget::addTweakOptions()250{251if (!m_dialog->isPerGameSettings())252{253addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Apply Game Settings"), "Main", "ApplyGameSettings",254true);255}256257addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Apply Compatibility Settings"), "Main",258"ApplyCompatibilitySettings", true);259addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Load Devices From Save States"), "Main",260"LoadDevicesFromSaveStates", false);261addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Save State Compression"), "Main", "SaveStateCompression",262&Settings::ParseSaveStateCompressionModeName, &Settings::GetSaveStateCompressionModeName,263&Settings::GetSaveStateCompressionModeDisplayName,264static_cast<u32>(SaveStateCompressionMode::Count),265Settings::DEFAULT_SAVE_STATE_COMPRESSION_MODE);266267addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Window Rounded Corners"), "Main",268"DisableWindowRoundedCorners", false);269270if (m_dialog->isPerGameSettings())271{272addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Active Start Offset"), "Display",273"ActiveStartOffset", -5000, 5000, 0);274addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Active End Offset"), "Display",275"ActiveEndOffset", -5000, 5000, 0);276addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Line Start Offset"), "Display",277"LineStartOffset", -128, 127, 0);278addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Line End Offset"), "Display", "LineEndOffset",279-128, 127, 0);280}281282addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks", "DMAMaxSliceTicks", 1,28310000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS, tr(" cycles"));284addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 1, 10000,285Settings::DEFAULT_DMA_HALT_TICKS, tr(" cycles"));286addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,287Settings::DEFAULT_GPU_FIFO_SIZE, tr(" words"));288addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU Max Runahead"), "Hacks", "GPUMaxRunAhead", 0, 1000,289Settings::DEFAULT_GPU_MAX_RUN_AHEAD, tr(" cycles"));290291addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",292"RecompilerMemoryExceptions", false);293addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Block Linking"), "CPU",294"RecompilerBlockLinking", true);295addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Fast Memory Access"), "CPU",296"FastmemMode", Settings::ParseCPUFastmemMode, Settings::GetCPUFastmemModeName,297Settings::GetCPUFastmemModeDisplayName, static_cast<u32>(CPUFastmemMode::Count),298Settings::DEFAULT_CPU_FASTMEM_MODE);299300addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Mechacon Version"), "CDROM", "MechaconVersion",301Settings::ParseCDROMMechVersionName, Settings::GetCDROMMechVersionName,302Settings::GetCDROMMechVersionDisplayName, static_cast<u8>(CDROMMechaconVersion::Count),303Settings::DEFAULT_CDROM_MECHACON_VERSION);304addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Readahead Sectors"), "CDROM", "ReadaheadSectors",3050, 32, Settings::DEFAULT_CDROM_READAHEAD_SECTORS, tr(" sectors"));306addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Read Speedup Cycles"), "CDROM",307"MaxReadSpeedupCycles", 1, 1000000, Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES,308tr(" cycles"));309addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Seek Speedup Cycles"), "CDROM",310"MaxSeekSpeedupCycles", 1, 1000000, Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES,311tr(" cycles"));312addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Disable Speedup on MDEC"), "CDROM",313"DisableSpeedupOnMDEC", false);314addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Region Check"), "CDROM", "RegionCheck", false);315addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM SubQ Skew"), "CDROM", "SubQSkew", false);316addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",317"AllowBootingWithoutSBIFile", false);318319addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable GDB Server"), "Debug", "EnableGDBServer", false);320addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GDB Server Port"), "Debug", "GDBServerPort", 1, 65535,321Settings::DEFAULT_GDB_SERVER_PORT);322323addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Export Shared Memory"), "Hacks", "ExportSharedMemory",324false);325addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Redirect SIO to TTY"), "SIO", "RedirectToTTY", false);326addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv"), "PCDrv", "Enabled", false);327addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv Writes"), "PCDrv", "EnableWrites", false);328addDirectoryOption(m_dialog, m_ui.tweakOptionTable, tr("PCDrv Root Directory"), "PCDrv", "Root");329}330331void AdvancedSettingsWidget::onResetToDefaultClicked()332{333if (!m_dialog->isPerGameSettings())334{335int i = 0;336337setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Apply Game Settings338setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Apply Compatibility settings339setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Load Devices From Save States340setChoiceTweakOption(m_ui.tweakOptionTable, i++,341Settings::DEFAULT_SAVE_STATE_COMPRESSION_MODE); // Save State Compression342setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Disable Window Rounded Corners343setIntRangeTweakOption(m_ui.tweakOptionTable, i++,344static_cast<int>(Settings::DEFAULT_DMA_MAX_SLICE_TICKS)); // DMA max slice ticks345setIntRangeTweakOption(m_ui.tweakOptionTable, i++,346static_cast<int>(Settings::DEFAULT_DMA_HALT_TICKS)); // DMA halt ticks347setIntRangeTweakOption(m_ui.tweakOptionTable, i++,348static_cast<int>(Settings::DEFAULT_GPU_FIFO_SIZE)); // GPU FIFO size349setIntRangeTweakOption(m_ui.tweakOptionTable, i++,350static_cast<int>(Settings::DEFAULT_GPU_MAX_RUN_AHEAD)); // GPU max runahead351setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Recompiler memory exceptions352setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Recompiler block linking353setChoiceTweakOption(m_ui.tweakOptionTable, i++,354Settings::DEFAULT_CPU_FASTMEM_MODE); // Recompiler fastmem mode355setChoiceTweakOption(m_ui.tweakOptionTable, i++,356Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version357setIntRangeTweakOption(m_ui.tweakOptionTable, i++,358Settings::DEFAULT_CDROM_READAHEAD_SECTORS); // CD-ROM Readahead Sectors359setIntRangeTweakOption(m_ui.tweakOptionTable, i++,360Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES); // CD-ROM Max Speedup Read Cycles361setIntRangeTweakOption(m_ui.tweakOptionTable, i++,362Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES); // CD-ROM Max Speedup Seek Cycles363setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Disable Speedup on MDEC364setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check365setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM SubQ Skew366setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file367setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable GDB Server368setIntRangeTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_GDB_SERVER_PORT); // GDB Server Port369setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Export Shared Memory370setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Redirect SIO to TTY371setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV372setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV Writes373setDirectoryOption(m_ui.tweakOptionTable, i++, ""); // PCDrv Root Directory374375return;376}377378// for per-game it's easier to just clear and recreate379INISettingsInterface* sif = m_dialog->getSettingsInterface();380sif->DeleteValue("Main", "ApplyCompatibilitySettings");381sif->DeleteValue("Main", "LoadDevicesFromSaveStates");382sif->DeleteValue("Main", "CompressSaveStates");383sif->DeleteValue("Main", "DisableWindowRoundedCorners");384sif->DeleteValue("Display", "ActiveStartOffset");385sif->DeleteValue("Display", "ActiveEndOffset");386sif->DeleteValue("Display", "LineStartOffset");387sif->DeleteValue("Display", "LineEndOffset");388sif->DeleteValue("Hacks", "DMAMaxSliceTicks");389sif->DeleteValue("Hacks", "DMAHaltTicks");390sif->DeleteValue("Hacks", "GPUFIFOSize");391sif->DeleteValue("Hacks", "GPUMaxRunAhead");392sif->DeleteValue("Hacks", "ExportSharedMemory");393sif->DeleteValue("CPU", "RecompilerMemoryExceptions");394sif->DeleteValue("CPU", "RecompilerBlockLinking");395sif->DeleteValue("CPU", "FastmemMode");396sif->DeleteValue("CDROM", "MechaconVersion");397sif->DeleteValue("CDROM", "ReadaheadSectors");398sif->DeleteValue("CDROM", "MaxReadSpeedupCycles");399sif->DeleteValue("CDROM", "MaxSeekSpeedupCycles");400sif->DeleteValue("CDROM", "DisableSpeedupOnMDEC");401sif->DeleteValue("CDROM", "RegionCheck");402sif->DeleteValue("CDROM", "SubQSkew");403sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");404sif->DeleteValue("Debug", "EnableGDBServer");405sif->DeleteValue("Debug", "GDBServerPort");406sif->DeleteValue("SIO", "RedirectToTTY");407sif->DeleteValue("PCDrv", "Enabled");408sif->DeleteValue("PCDrv", "EnableWrites");409sif->DeleteValue("PCDrv", "Root");410sif->Save();411while (m_ui.tweakOptionTable->rowCount() > 0)412m_ui.tweakOptionTable->removeRow(m_ui.tweakOptionTable->rowCount() - 1);413addTweakOptions();414}415416417