From e4c11aa905f5c6f479504efa946c1aef6189d17c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 11 Jan 2025 20:04:19 +1000 Subject: [PATCH] Qt: Separate controller settings to global and profiles --- .../controllersettingswindow.cpp | 178 ++++++++++-------- src/duckstation-qt/controllersettingswindow.h | 14 +- .../controllersettingswindow.ui | 12 +- src/duckstation-qt/mainwindow.cpp | 34 ++-- src/duckstation-qt/mainwindow.h | 2 + src/duckstation-qt/mainwindow.ui | 9 + .../resources/duckstation-qt.qrc | 2 + .../icons/black/svg/controllers-line.svg | 10 + .../icons/white/svg/controllers-line.svg | 10 + 9 files changed, 166 insertions(+), 105 deletions(-) create mode 100644 src/duckstation-qt/resources/icons/black/svg/controllers-line.svg create mode 100644 src/duckstation-qt/resources/icons/white/svg/controllers-line.svg diff --git a/src/duckstation-qt/controllersettingswindow.cpp b/src/duckstation-qt/controllersettingswindow.cpp index a7008af3ec..0d73a6fd86 100644 --- a/src/duckstation-qt/controllersettingswindow.cpp +++ b/src/duckstation-qt/controllersettingswindow.cpp @@ -5,6 +5,7 @@ #include "controllerbindingwidgets.h" #include "controllerglobalsettingswidget.h" #include "hotkeysettingswidget.h" +#include "mainwindow.h" #include "qthost.h" #include "core/controller.h" @@ -22,8 +23,8 @@ #include ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /* = nullptr */, - QWidget* parent /* = nullptr */) - : QWidget(parent), m_editing_settings_interface(game_sif) + bool edit_profiles /* = false */, QWidget* parent /* = nullptr */) + : QWidget(parent), m_editing_settings_interface(game_sif), m_editing_input_profiles(edit_profiles) { m_ui.setupUi(this); @@ -35,23 +36,9 @@ ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif / &ControllerSettingsWindow::onCategoryCurrentRowChanged); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close); - if (!game_sif) - { - refreshProfileList(); - - m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings); - delete m_ui.copyGlobalSettings; - m_ui.copyGlobalSettings = nullptr; - - connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this, - &ControllerSettingsWindow::onCurrentProfileChanged); - connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked); - connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked); - connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked); - connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked); - } - else + if (!game_sif && !edit_profiles) { + // editing global settings m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel); delete m_ui.editProfileLabel; m_ui.editProfileLabel = nullptr; @@ -67,11 +54,51 @@ ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif / m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile); delete m_ui.deleteProfile; m_ui.deleteProfile = nullptr; + m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings); + delete m_ui.copyGlobalSettings; + m_ui.copyGlobalSettings = nullptr; + + if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)) + connect(button, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked); + } + else + { + if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)) + m_ui.buttonBox->removeButton(button); connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this, &ControllerSettingsWindow::onCopyGlobalSettingsClicked); - connect(m_ui.restoreDefaults, &QPushButton::clicked, this, - &ControllerSettingsWindow::onRestoreDefaultsForGameClicked); + + if (edit_profiles) + { + setWindowTitle(tr("DuckStation Controller Profiles")); + refreshProfileList(); + + connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this, + &ControllerSettingsWindow::onCurrentProfileChanged); + connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked); + connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked); + connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked); + } + else + { + // editing game settings + m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel); + delete m_ui.editProfileLabel; + m_ui.editProfileLabel = nullptr; + m_ui.editProfileLayout->removeWidget(m_ui.currentProfile); + delete m_ui.currentProfile; + m_ui.currentProfile = nullptr; + m_ui.editProfileLayout->removeWidget(m_ui.newProfile); + delete m_ui.newProfile; + m_ui.newProfile = nullptr; + m_ui.editProfileLayout->removeWidget(m_ui.applyProfile); + delete m_ui.applyProfile; + m_ui.applyProfile = nullptr; + m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile); + delete m_ui.deleteProfile; + m_ui.deleteProfile = nullptr; + } } createWidgets(); @@ -81,7 +108,7 @@ ControllerSettingsWindow::~ControllerSettingsWindow() = default; void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif) { - ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, parent); + ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, false, parent); dlg->setWindowFlag(Qt::Window); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowModality(Qt::WindowModality::WindowModal); @@ -207,9 +234,8 @@ void ControllerSettingsWindow::onNewProfileClicked() if (!temp_si.Save()) { - QMessageBox::critical( - this, tr("Error"), - tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetPath()))); + QMessageBox::critical(this, tr("Error"), + tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetPath()))); return; } @@ -238,14 +264,14 @@ void ControllerSettingsWindow::onApplyProfileClicked() } g_emu_thread->applySettings(); - // make it visible - switchProfile({}); + // Recreate global widget on profile apply + g_main_window->getControllerSettingsWindow()->createWidgets(); } void ControllerSettingsWindow::onDeleteProfileClicked() { - if (QMessageBox::question(this, tr("Delete Input Profile"), - tr("Are you sure you want to delete the input profile named '%1'?\n\n" + if (QMessageBox::question(this, tr("Delete Input Preset"), + tr("Are you sure you want to delete the input preset named '%1'?\n\n" "You cannot undo this action.") .arg(m_profile_name)) != QMessageBox::Yes) { @@ -266,11 +292,10 @@ void ControllerSettingsWindow::onDeleteProfileClicked() void ControllerSettingsWindow::onRestoreDefaultsClicked() { - if (QMessageBox::question( - this, tr("Restore Defaults"), - tr("Are you sure you want to restore the default controller configuration?\n\n" - "All shared bindings and configuration will be lost, but your input profiles will remain.\n\n" - "You cannot undo this action.")) != QMessageBox::Yes) + if (QMessageBox::question(this, tr("Restore Defaults"), + tr("Are you sure you want to restore the default controller configuration?\n\n" + "All bindings and configuration will be lost. You cannot undo this action.")) != + QMessageBox::Yes) { return; } @@ -279,12 +304,12 @@ void ControllerSettingsWindow::onRestoreDefaultsClicked() g_emu_thread->setDefaultSettings(false, true); // reload all settings - switchProfile({}); + createWidgets(); } void ControllerSettingsWindow::onCopyGlobalSettingsClicked() { - DebugAssert(isEditingGameSettings()); + DebugAssert(!isEditingGlobalSettings()); { const auto lock = Host::GetSettingsLock(); @@ -297,19 +322,8 @@ void ControllerSettingsWindow::onCopyGlobalSettingsClicked() createWidgets(); QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"), - tr("Per-game controller configuration reset to global settings.")); -} - -void ControllerSettingsWindow::onRestoreDefaultsForGameClicked() -{ - DebugAssert(isEditingGameSettings()); - Settings::SetDefaultControllerConfig(*m_editing_settings_interface); - m_editing_settings_interface->Save(); - g_emu_thread->reloadGameSettings(); - createWidgets(); - - QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"), - tr("Per-game controller configuration reset to default settings.")); + isEditingGameSettings() ? tr("Per-game controller configuration reset to global settings.") : + tr("Controller profile reset to global settings.")); } bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const @@ -472,11 +486,12 @@ void ControllerSettingsWindow::createWidgets() m_ui.settingsContainer->addWidget(m_hotkey_settings); } - if (!isEditingGameSettings()) + if (isEditingProfile()) { - m_ui.applyProfile->setEnabled(isEditingProfile()); - m_ui.deleteProfile->setEnabled(isEditingProfile()); - m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings()); + const bool enable_buttons = static_cast(m_profile_settings_interface); + m_ui.applyProfile->setEnabled(enable_buttons); + m_ui.deleteProfile->setEnabled(enable_buttons); + m_ui.copyGlobalSettings->setEnabled(enable_buttons); } } @@ -519,55 +534,64 @@ std::array ControllerSettingsWindow::getEnabledMultitaps() const return {{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts), (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}}; } + void ControllerSettingsWindow::refreshProfileList() { const std::vector names(InputManager::GetInputProfileNames()); QSignalBlocker sb(m_ui.currentProfile); m_ui.currentProfile->clear(); - m_ui.currentProfile->addItem(tr("Shared")); - if (isEditingGlobalSettings()) - m_ui.currentProfile->setCurrentIndex(0); + bool current_profile_found = false; for (const std::string& name : names) { const QString qname(QString::fromStdString(name)); m_ui.currentProfile->addItem(qname); if (qname == m_profile_name) + { m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1); + current_profile_found = true; + } } + + if (!current_profile_found) + switchProfile(names.empty() ? std::string_view() : std::string_view(names.front())); } void ControllerSettingsWindow::switchProfile(const std::string_view name) { - QSignalBlocker sb(m_ui.currentProfile); - - if (!name.empty()) + const QString name_qstr = QtUtils::StringViewToQString(name); { - const QString name_qstr = QtUtils::StringViewToQString(name); - - std::string path = System::GetInputProfilePath(name); - if (!FileSystem::FileExists(path.c_str())) - { - QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr)); - return; - } + QSignalBlocker sb(m_ui.currentProfile); + m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr)); + } + m_profile_name = name_qstr; + m_profile_settings_interface.reset(); + m_editing_settings_interface = nullptr; - std::unique_ptr sif = std::make_unique(std::move(path)); - sif->Load(); + // disable UI if there is no selection + const bool disable_ui = name.empty(); + m_ui.settingsCategory->setDisabled(disable_ui); + m_ui.settingsContainer->setDisabled(disable_ui); - m_profile_settings_interface = std::move(sif); - m_editing_settings_interface = m_profile_settings_interface.get(); - m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr)); - m_profile_name = name_qstr; + if (name_qstr.isEmpty()) + { + createWidgets(); + return; } - else + + std::string path = System::GetInputProfilePath(name); + if (!FileSystem::FileExists(path.c_str())) { - m_profile_settings_interface.reset(); - m_editing_settings_interface = nullptr; - m_ui.currentProfile->setCurrentIndex(0); - m_profile_name = QString(); + QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr)); + return; } + std::unique_ptr sif = std::make_unique(std::move(path)); + sif->Load(); + + m_profile_settings_interface = std::move(sif); + m_editing_settings_interface = m_profile_settings_interface.get(); + createWidgets(); } diff --git a/src/duckstation-qt/controllersettingswindow.h b/src/duckstation-qt/controllersettingswindow.h index 44574f37a6..fd6968ffbe 100644 --- a/src/duckstation-qt/controllersettingswindow.h +++ b/src/duckstation-qt/controllersettingswindow.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -41,7 +42,8 @@ class ControllerSettingsWindow final : public QWidget Count }; - ControllerSettingsWindow(SettingsInterface* game_sif = nullptr, QWidget* parent = nullptr); + ControllerSettingsWindow(SettingsInterface* game_sif = nullptr, bool edit_profiles = false, + QWidget* parent = nullptr); ~ControllerSettingsWindow(); static void editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif); @@ -50,13 +52,13 @@ class ControllerSettingsWindow final : public QWidget ALWAYS_INLINE bool isEditingGlobalSettings() const { - return (m_profile_name.isEmpty() && !m_editing_settings_interface); + return (!m_editing_input_profiles && !m_editing_settings_interface); } ALWAYS_INLINE bool isEditingGameSettings() const { - return (m_profile_name.isEmpty() && m_editing_settings_interface); + return (!m_editing_input_profiles && m_editing_settings_interface); } - ALWAYS_INLINE bool isEditingProfile() const { return !m_profile_name.isEmpty(); } + ALWAYS_INLINE bool isEditingProfile() const { return m_editing_input_profiles; } ALWAYS_INLINE SettingsInterface* getEditingSettingsInterface() { return m_editing_settings_interface; } Category getCurrentCategory() const; @@ -90,7 +92,6 @@ private Q_SLOTS: void onDeleteProfileClicked(); void onRestoreDefaultsClicked(); void onCopyGlobalSettingsClicked(); - void onRestoreDefaultsForGameClicked(); void createWidgets(); @@ -113,4 +114,5 @@ private Q_SLOTS: QString m_profile_name; std::unique_ptr m_profile_settings_interface; + bool m_editing_input_profiles = false; }; diff --git a/src/duckstation-qt/controllersettingswindow.ui b/src/duckstation-qt/controllersettingswindow.ui index d077eee8fb..4f2864347f 100644 --- a/src/duckstation-qt/controllersettingswindow.ui +++ b/src/duckstation-qt/controllersettingswindow.ui @@ -126,22 +126,12 @@ - - - - Restore Defaults - - - - - - - QDialogButtonBox::StandardButton::Close + QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 5f9986ec51..892ce6eb1c 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -797,6 +797,7 @@ void MainWindow::destroySubWindows() QtUtils::CloseAndDeleteWindow(m_debugger_window); QtUtils::CloseAndDeleteWindow(m_memory_card_editor_window); QtUtils::CloseAndDeleteWindow(m_controller_settings_window); + QtUtils::CloseAndDeleteWindow(m_input_profile_editor_window); QtUtils::CloseAndDeleteWindow(m_settings_window); SettingsWindow::closeGamePropertiesDialogs(); @@ -2021,6 +2022,7 @@ void MainWindow::connectSignals() connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); }); connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); }); connect(m_ui.actionAdvancedSettings, &QAction::triggered, [this]() { doSettings("Advanced"); }); + connect(m_ui.actionControllerProfiles, &QAction::triggered, this, &MainWindow::onSettingsControllerProfilesTriggered); connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled); connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled); connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled); @@ -2336,11 +2338,29 @@ void MainWindow::doControllerSettings( dlg->setCategory(category); } +void MainWindow::onSettingsTriggeredFromToolbar() +{ + if (s_system_valid) + m_settings_toolbar_menu->exec(QCursor::pos()); + else + doSettings(); +} + +void MainWindow::onSettingsControllerProfilesTriggered() +{ + if (!m_input_profile_editor_window) + m_input_profile_editor_window = new ControllerSettingsWindow(nullptr, true); + + QtUtils::ShowOrRaiseWindow(m_input_profile_editor_window); +} + void MainWindow::openInputProfileEditor(const std::string_view name) { - ControllerSettingsWindow* dlg = getControllerSettingsWindow(); - QtUtils::ShowOrRaiseWindow(dlg); - dlg->switchProfile(name); + if (!m_input_profile_editor_window) + m_input_profile_editor_window = new ControllerSettingsWindow(nullptr, true); + + QtUtils::ShowOrRaiseWindow(m_input_profile_editor_window); + m_input_profile_editor_window->switchProfile(name); } void MainWindow::showEvent(QShowEvent* event) @@ -2794,14 +2814,6 @@ void MainWindow::onToolsOpenTextureDirectoryTriggered() QtUtils::OpenURL(this, QUrl::fromLocalFile(dir)); } -void MainWindow::onSettingsTriggeredFromToolbar() -{ - if (s_system_valid) - m_settings_toolbar_menu->exec(QCursor::pos()); - else - doSettings(); -} - void MainWindow::checkForUpdates(bool display_message) { if (!AutoUpdaterDialog::isSupported()) diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 17daa67000..49b890d575 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -192,6 +192,7 @@ private Q_SLOTS: void onToolsOpenDataDirectoryTriggered(); void onToolsOpenTextureDirectoryTriggered(); void onSettingsTriggeredFromToolbar(); + void onSettingsControllerProfilesTriggered(); void onGameListRefreshComplete(); void onGameListRefreshProgress(const QString& status, int current, int total); @@ -313,6 +314,7 @@ private Q_SLOTS: SettingsWindow* m_settings_window = nullptr; ControllerSettingsWindow* m_controller_settings_window = nullptr; + ControllerSettingsWindow* m_input_profile_editor_window = nullptr; AutoUpdaterDialog* m_auto_updater_dialog = nullptr; MemoryCardEditorWindow* m_memory_card_editor_window = nullptr; diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index ac8a0ebbb3..69fcfbaaed 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -115,6 +115,7 @@ + @@ -985,6 +986,14 @@ Controller Test + + + + + + Controller Profiles + + diff --git a/src/duckstation-qt/resources/duckstation-qt.qrc b/src/duckstation-qt/resources/duckstation-qt.qrc index b827a818fe..a95d68213b 100644 --- a/src/duckstation-qt/resources/duckstation-qt.qrc +++ b/src/duckstation-qt/resources/duckstation-qt.qrc @@ -37,6 +37,7 @@ icons/black/svg/chip-2-line.svg icons/black/svg/chip-line.svg icons/black/svg/close-line.svg + icons/black/svg/controllers-line.svg icons/black/svg/controller-digital-line.svg icons/black/svg/controller-line.svg icons/black/svg/controller-strike-line.svg @@ -254,6 +255,7 @@ icons/white/svg/chip-2-line.svg icons/white/svg/chip-line.svg icons/white/svg/close-line.svg + icons/white/svg/controllers-line.svg icons/white/svg/controller-digital-line.svg icons/white/svg/controller-line.svg icons/white/svg/controller-strike-line.svg diff --git a/src/duckstation-qt/resources/icons/black/svg/controllers-line.svg b/src/duckstation-qt/resources/icons/black/svg/controllers-line.svg new file mode 100644 index 0000000000..8c6e66dc98 --- /dev/null +++ b/src/duckstation-qt/resources/icons/black/svg/controllers-line.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/duckstation-qt/resources/icons/white/svg/controllers-line.svg b/src/duckstation-qt/resources/icons/white/svg/controllers-line.svg new file mode 100644 index 0000000000..8665971407 --- /dev/null +++ b/src/duckstation-qt/resources/icons/white/svg/controllers-line.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file