From 2a7dd35706e06526cc6083907f53f65697932b0d Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 1 Nov 2025 15:40:49 +0100 Subject: [PATCH 01/14] feat: adds "open profile folder" button in mapping window Co-Authored-By: iTrooz Co-Authored-By: Max Chateau Co-Authored-By: Damien R. --- .../Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 13 +++++++++++++ .../Core/DolphinQt/Config/Mapping/MappingWindow.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index e134902115c..a82ae8f67ee 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include "Core/HotkeyManager.h" @@ -144,6 +146,7 @@ void MappingWindow::CreateProfilesLayout() m_profiles_load = new NonDefaultQPushButton(tr("Load")); m_profiles_save = new NonDefaultQPushButton(tr("Save")); m_profiles_delete = new NonDefaultQPushButton(tr("Delete")); + m_profiles_open_folder = new NonDefaultQPushButton(tr("Open Folder")); auto* button_layout = new QHBoxLayout(); @@ -155,6 +158,7 @@ void MappingWindow::CreateProfilesLayout() button_layout->addWidget(m_profiles_load); button_layout->addWidget(m_profiles_save); button_layout->addWidget(m_profiles_delete); + button_layout->addWidget(m_profiles_open_folder); m_profiles_layout->addLayout(button_layout); m_profiles_box->setLayout(m_profiles_layout); @@ -206,6 +210,7 @@ void MappingWindow::ConnectWidgets() connect(m_profiles_save, &QPushButton::clicked, this, &MappingWindow::OnSaveProfilePressed); connect(m_profiles_load, &QPushButton::clicked, this, &MappingWindow::OnLoadProfilePressed); connect(m_profiles_delete, &QPushButton::clicked, this, &MappingWindow::OnDeleteProfilePressed); + connect(m_profiles_open_folder, &QPushButton::clicked, this, &MappingWindow::OnOpenProfileFolder); connect(m_profiles_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectProfile); connect(m_profiles_combo, &QComboBox::editTextChanged, this, @@ -350,6 +355,14 @@ void MappingWindow::OnSaveProfilePressed() } } +void MappingWindow::OnOpenProfileFolder() +{ + std::string path = m_config->GetUserProfileDirectoryPath(); + File::CreateDirs(path); + QUrl url = QUrl::fromLocalFile(QString::fromStdString(path)); + QDesktopServices::openUrl(url); +} + void MappingWindow::OnSelectDevice(int) { // Original string is stored in the "user-data". diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index f07aac7fd4c..cfd1474782a 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -85,6 +85,7 @@ private: void OnDeleteProfilePressed(); void OnLoadProfilePressed(); void OnSaveProfilePressed(); + void OnOpenProfileFolder(); void UpdateProfileIndex(); void UpdateProfileButtonState(); void PopulateProfileSelection(); @@ -116,6 +117,7 @@ private: QPushButton* m_profiles_load; QPushButton* m_profiles_save; QPushButton* m_profiles_delete; + QPushButton* m_profiles_open_folder; // Reset QGroupBox* m_reset_box; From ac4eaec777bc1b0c06688523b848d31efdd115a8 Mon Sep 17 00:00:00 2001 From: Max Chateau Date: Sat, 18 Oct 2025 22:57:26 +0200 Subject: [PATCH 02/14] Notify mapping window when changes occurs Co-Authored-By: iTrooz Co-Authored-By: Max Chateau Co-Authored-By: Damien R. --- Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp | 3 +++ Source/Core/DolphinQt/Config/Mapping/IOWindow.h | 1 + Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp | 6 ++++++ Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp | 1 + Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 5 +++++ Source/Core/DolphinQt/Config/Mapping/MappingWindow.h | 1 + 6 files changed, 17 insertions(+) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 53da173bba3..4d1634bc157 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -770,6 +770,9 @@ void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode) const auto status = m_reference->GetParseStatus(); m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); + // Emit signal that this IOWindow's expression changed. + emit ExpressionChanged(QString::fromStdString(m_reference->GetExpression())); + // This is the only place where we need to update the user variables. Keep the first 4 items. while (m_variables_combo->count() > 4) { diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 88888434479..c9feb83c653 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -71,6 +71,7 @@ public: signals: void DetectInputComplete(); void TestOutputComplete(); + void ExpressionChanged(const QString& expression); private: std::shared_ptr GetSelectedDevice() const; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index 24e7be9bf8f..2a721dc2223 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -92,6 +92,12 @@ void MappingButton::AdvancedPressed() IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference, m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output); + + // Connect to IOWindow's ExpressionChanged signal + connect(&io, &IOWindow::ExpressionChanged, m_mapping_window, [this](const QString& expr) { + m_mapping_window->ExpressionChanged(1); + }); + io.exec(); ConfigChanged(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 7e7d2b9f425..dc615718b5d 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -133,6 +133,7 @@ public: m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, control_reference); m_parent->GetController()->GetConfig()->GenerateControllerTextures(); + m_parent->ExpressionChanged(0); } void UpdateInputDetectionStartTimer() diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index a82ae8f67ee..49d957f33c3 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -616,3 +616,8 @@ void MappingWindow::ActivateExtensionTab() { m_tab_widget->setCurrentIndex(3); } + +void MappingWindow::ExpressionChanged(int source) +{ + printf("ExpressionChanged(%d)\n", source); +} \ No newline at end of file diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index cfd1474782a..559b9222417 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -56,6 +56,7 @@ public: bool IsIterativeMappingEnabled() const; void ShowExtensionMotionTabs(bool show); void ActivateExtensionTab(); + void ExpressionChanged(int source); signals: // Emitted when config has changed so widgets can update to reflect the change. From 919c53b3af942831ce49cc86549616a85cf5d3d2 Mon Sep 17 00:00:00 2001 From: Max Chateau Date: Sat, 18 Oct 2025 23:40:35 +0200 Subject: [PATCH 03/14] Handle enabling/disabling save/load button when profile change Co-Authored-By: iTrooz Co-Authored-By: Max Chateau Co-Authored-By: Damien R. --- Source/Core/Common/IniFile.cpp | 36 +++++++++++++++++++ Source/Core/Common/IniFile.h | 2 ++ .../DolphinQt/Config/Mapping/IOWindow.cpp | 2 +- .../Core/DolphinQt/Config/Mapping/IOWindow.h | 2 +- .../Config/Mapping/MappingButton.cpp | 5 ++- .../Config/Mapping/MappingCommon.cpp | 2 +- .../Config/Mapping/MappingWindow.cpp | 23 +++++++++--- .../DolphinQt/Config/Mapping/MappingWindow.h | 2 +- 8 files changed, 63 insertions(+), 11 deletions(-) diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index 6e6749cb0db..9c6dfa1d853 100644 --- a/Source/Core/Common/IniFile.cpp +++ b/Source/Core/Common/IniFile.cpp @@ -352,6 +352,42 @@ bool IniFile::Save(const std::string& filename) return File::RenameSync(temp, filename); } + +bool IniFile::CompareContent(IniFile& other) const { + if (sections.size() != other.sections.size()) + return false; + + for (const Section& s : sections) + { + const Section* os = other.GetSection(s.name); + if (!os) + return false; + + // Compare raw lines + if (s.m_lines != os->m_lines) + return false; + + // Compare key order + if (s.keys_order != os->keys_order) + return false; + + // Compare values + if (s.values.size() != os->values.size()) + return false; + + for (const auto& [key, val] : s.values) + { + const auto it = os->values.find(key); + if (it == os->values.end()) + return false; + if (it->second != val) + return false; + } + } + + return true; +} + // Unit test. TODO: Move to the real unit test framework. /* int main() diff --git a/Source/Core/Common/IniFile.h b/Source/Core/Common/IniFile.h index 31a7ee0e6b0..c002374b98b 100644 --- a/Source/Core/Common/IniFile.h +++ b/Source/Core/Common/IniFile.h @@ -81,6 +81,8 @@ public: IniFile(); ~IniFile(); + bool CompareContent(IniFile& other) const; + /** * Loads sections and keys. * @param filename filename of the ini file which should be loaded diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 4d1634bc157..6bb14b9928a 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -771,7 +771,7 @@ void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode) m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); // Emit signal that this IOWindow's expression changed. - emit ExpressionChanged(QString::fromStdString(m_reference->GetExpression())); + emit OnMappingChange(QString::fromStdString(m_reference->GetExpression())); // This is the only place where we need to update the user variables. Keep the first 4 items. while (m_variables_combo->count() > 4) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index c9feb83c653..d75b2878d55 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -71,7 +71,7 @@ public: signals: void DetectInputComplete(); void TestOutputComplete(); - void ExpressionChanged(const QString& expression); + void OnMappingChange(const QString& expression); private: std::shared_ptr GetSelectedDevice() const; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index 2a721dc2223..bc194fdcb88 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -93,9 +93,8 @@ void MappingButton::AdvancedPressed() IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference, m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output); - // Connect to IOWindow's ExpressionChanged signal - connect(&io, &IOWindow::ExpressionChanged, m_mapping_window, [this](const QString& expr) { - m_mapping_window->ExpressionChanged(1); + connect(&io, &IOWindow::OnMappingChange, m_mapping_window, [this](const QString& expr) { + m_mapping_window->OnMappingChange(1); }); io.exec(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index dc615718b5d..19c7e2e9c7e 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -133,7 +133,7 @@ public: m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, control_reference); m_parent->GetController()->GetConfig()->GenerateControllerTextures(); - m_parent->ExpressionChanged(0); + m_parent->OnMappingChange(0); } void UpdateInputDetectionStartTimer() diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 49d957f33c3..7cb4e0960ae 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -238,7 +238,6 @@ void MappingWindow::UpdateProfileIndex() void MappingWindow::UpdateProfileButtonState() { // Make sure save/delete buttons are disabled for built-in profiles - bool builtin = false; if (m_profiles_combo->findText(m_profiles_combo->currentText()) != -1) { @@ -248,7 +247,19 @@ void MappingWindow::UpdateProfileButtonState() builtin = profile_path.startsWith(QString::fromStdString(sys_dir)); } - m_profiles_save->setEnabled(!builtin); + // Check if mapping is modified + const QString profile_path = m_profiles_combo->currentData().toString(); + Common::IniFile diskIni; + diskIni.Load(profile_path.toStdString()); + + Common::IniFile memoryIni; + m_controller->SaveConfig(memoryIni.GetOrCreateSection("Profile")); + + bool mapping_modified = !diskIni.CompareContent(memoryIni); + + // Update button state + m_profiles_load->setEnabled(!builtin && mapping_modified); + m_profiles_save->setEnabled(!builtin && mapping_modified); m_profiles_delete->setEnabled(!builtin); } @@ -327,6 +338,8 @@ void MappingWindow::OnLoadProfilePressed() m_controller->UpdateReferences(g_controller_interface); m_controller->GetConfig()->GenerateControllerTextures(); + UpdateProfileButtonState(); + const auto lock = GetController()->GetStateLock(); emit ConfigChanged(); } @@ -348,6 +361,8 @@ void MappingWindow::OnSaveProfilePressed() m_controller->SaveConfig(ini.GetOrCreateSection("Profile")); ini.Save(profile_path); + UpdateProfileButtonState(); + if (m_profiles_combo->findText(profile_name) == -1) { PopulateProfileSelection(); @@ -617,7 +632,7 @@ void MappingWindow::ActivateExtensionTab() m_tab_widget->setCurrentIndex(3); } -void MappingWindow::ExpressionChanged(int source) +void MappingWindow::OnMappingChange(int source) { - printf("ExpressionChanged(%d)\n", source); + UpdateProfileButtonState(); } \ No newline at end of file diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 559b9222417..98771a47ca1 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -56,7 +56,7 @@ public: bool IsIterativeMappingEnabled() const; void ShowExtensionMotionTabs(bool show); void ActivateExtensionTab(); - void ExpressionChanged(int source); + void OnMappingChange(int source); signals: // Emitted when config has changed so widgets can update to reflect the change. From 73b454f99cc17e71e015740f402ba288c545bc01 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 26 Oct 2025 23:37:11 +0100 Subject: [PATCH 04/14] update load/save button when edition profile, before hitting enter Co-Authored-By: iTrooz Co-Authored-By: Max Chateau Co-Authored-By: Damien R. --- .../Config/Mapping/MappingWindow.cpp | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 7cb4e0960ae..5ebc5be84c6 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -213,7 +213,7 @@ void MappingWindow::ConnectWidgets() connect(m_profiles_open_folder, &QPushButton::clicked, this, &MappingWindow::OnOpenProfileFolder); connect(m_profiles_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectProfile); - connect(m_profiles_combo, &QComboBox::editTextChanged, this, + connect(m_profiles_combo, &QComboBox::currentTextChanged, this, &MappingWindow::OnProfileTextChanged); // We currently use the "Close" button as an "Accept" button so we must save on reject. @@ -237,18 +237,16 @@ void MappingWindow::UpdateProfileIndex() void MappingWindow::UpdateProfileButtonState() { - // Make sure save/delete buttons are disabled for built-in profiles - bool builtin = false; - if (m_profiles_combo->findText(m_profiles_combo->currentText()) != -1) - { - const QString profile_path = m_profiles_combo->currentData().toString(); - std::string sys_dir = File::GetSysDirectory(); - sys_dir = ReplaceAll(sys_dir, "\\", DIR_SEP); - builtin = profile_path.startsWith(QString::fromStdString(sys_dir)); - } + // currentData() is not up to date on a currentTextChanged() event, so find profile path from currentText() (which is up to date) + const int index = m_profiles_combo->findText(m_profiles_combo->currentText()); + QString profile_path; + if (index != -1) profile_path = m_profiles_combo->itemData(index).toString(); + // Make sure save/delete buttons are disabled for built-in profiles + std::string sys_dir = File::GetSysDirectory(); + sys_dir = ReplaceAll(sys_dir, "\\", DIR_SEP); + bool builtin = profile_path.startsWith(QString::fromStdString(sys_dir)); // Check if mapping is modified - const QString profile_path = m_profiles_combo->currentData().toString(); Common::IniFile diskIni; diskIni.Load(profile_path.toStdString()); From c26b98650f80004a4362dca4e21e32960f0709b1 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 26 Oct 2025 23:58:33 +0100 Subject: [PATCH 05/14] add tooltips to buttons when disabled --- .../Config/Mapping/MappingWindow.cpp | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 5ebc5be84c6..63c32ffc209 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -242,23 +242,46 @@ void MappingWindow::UpdateProfileButtonState() QString profile_path; if (index != -1) profile_path = m_profiles_combo->itemData(index).toString(); + // Init new buttons state (enabled by default) + bool load_enabled = true; + bool save_enabled = true; + bool delete_enabled = true; + QString load_tooltip; + QString save_tooltip; + QString delete_tooltip; + + // Check if mapping is modified + Common::IniFile diskIni; + diskIni.Load(profile_path.toStdString()); + Common::IniFile memoryIni; + m_controller->SaveConfig(memoryIni.GetOrCreateSection("Profile")); + bool mapping_modified = !diskIni.CompareContent(memoryIni); + if (!mapping_modified) { + load_enabled = false; + load_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); + save_enabled = false; + save_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); + } + // Make sure save/delete buttons are disabled for built-in profiles std::string sys_dir = File::GetSysDirectory(); sys_dir = ReplaceAll(sys_dir, "\\", DIR_SEP); bool builtin = profile_path.startsWith(QString::fromStdString(sys_dir)); - // Check if mapping is modified - Common::IniFile diskIni; - diskIni.Load(profile_path.toStdString()); + if (builtin) + { + save_enabled = false; + delete_enabled = false; + save_tooltip = tr("Cannot do this action. Reason: Built-in profile"); + delete_tooltip = tr("Cannot do this action. Reason: Built-in profile"); + } - Common::IniFile memoryIni; - m_controller->SaveConfig(memoryIni.GetOrCreateSection("Profile")); - - bool mapping_modified = !diskIni.CompareContent(memoryIni); - - // Update button state - m_profiles_load->setEnabled(!builtin && mapping_modified); - m_profiles_save->setEnabled(!builtin && mapping_modified); - m_profiles_delete->setEnabled(!builtin); + // Update buttons state + m_profiles_load->setEnabled(load_enabled); + m_profiles_load->setToolTip(load_tooltip); + m_profiles_save->setEnabled(save_enabled); + m_profiles_save->setToolTip(save_tooltip); + m_profiles_delete->setEnabled(delete_enabled); + m_profiles_delete->setToolTip(delete_tooltip); } void MappingWindow::OnSelectProfile(int) From 473a100122c8770e1362bf61830c150e6a554595 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 1 Nov 2025 22:57:13 +0100 Subject: [PATCH 06/14] fix comparison with system profiles not working --- .../DolphinQt/Config/Mapping/MappingWindow.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 63c32ffc209..e14da5025af 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -251,12 +251,22 @@ void MappingWindow::UpdateProfileButtonState() QString delete_tooltip; // Check if mapping is modified - Common::IniFile diskIni; - diskIni.Load(profile_path.toStdString()); + // Load memory mapping Common::IniFile memoryIni; m_controller->SaveConfig(memoryIni.GetOrCreateSection("Profile")); - bool mapping_modified = !diskIni.CompareContent(memoryIni); - if (!mapping_modified) { + + // Load disk mapping + Common::IniFile diskIni; + diskIni.Load(profile_path.toStdString()); + // Pass the disk mapping through the controller to normalize it + m_controller->LoadConfig(diskIni.GetOrCreateSection("Profile")); + m_controller->SaveConfig(diskIni.GetOrCreateSection("Profile")); + // Restore controller mapping from memory + m_controller->LoadConfig(memoryIni.GetOrCreateSection("Profile")); + + // Compare mappings + bool mapping_is_the_same = diskIni.CompareContent(memoryIni); + if (mapping_is_the_same) { load_enabled = false; load_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); save_enabled = false; From db3af5772b40e93dac031e63ce4e67c59dd98658 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 1 Nov 2025 23:41:45 +0100 Subject: [PATCH 07/14] remove OnMappingChange() arguments --- Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp | 2 +- Source/Core/DolphinQt/Config/Mapping/IOWindow.h | 2 +- Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp | 4 ++-- Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp | 2 +- Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 2 +- Source/Core/DolphinQt/Config/Mapping/MappingWindow.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 6bb14b9928a..74b4ceaf11d 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -771,7 +771,7 @@ void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode) m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); // Emit signal that this IOWindow's expression changed. - emit OnMappingChange(QString::fromStdString(m_reference->GetExpression())); + emit OnMappingChange(); // This is the only place where we need to update the user variables. Keep the first 4 items. while (m_variables_combo->count() > 4) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index d75b2878d55..e719c5ad546 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -71,7 +71,7 @@ public: signals: void DetectInputComplete(); void TestOutputComplete(); - void OnMappingChange(const QString& expression); + void OnMappingChange(); private: std::shared_ptr GetSelectedDevice() const; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index bc194fdcb88..e7ec5980042 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -93,8 +93,8 @@ void MappingButton::AdvancedPressed() IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference, m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output); - connect(&io, &IOWindow::OnMappingChange, m_mapping_window, [this](const QString& expr) { - m_mapping_window->OnMappingChange(1); + connect(&io, &IOWindow::OnMappingChange, m_mapping_window, [this] { + m_mapping_window->OnMappingChange(); }); io.exec(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 19c7e2e9c7e..ba60b172f54 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -133,7 +133,7 @@ public: m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, control_reference); m_parent->GetController()->GetConfig()->GenerateControllerTextures(); - m_parent->OnMappingChange(0); + m_parent->OnMappingChange(); } void UpdateInputDetectionStartTimer() diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index e14da5025af..ed4957d00a6 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -663,7 +663,7 @@ void MappingWindow::ActivateExtensionTab() m_tab_widget->setCurrentIndex(3); } -void MappingWindow::OnMappingChange(int source) +void MappingWindow::OnMappingChange() { UpdateProfileButtonState(); } \ No newline at end of file diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 98771a47ca1..d65fdb2bd07 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -56,7 +56,7 @@ public: bool IsIterativeMappingEnabled() const; void ShowExtensionMotionTabs(bool show); void ActivateExtensionTab(); - void OnMappingChange(int source); + void OnMappingChange(); signals: // Emitted when config has changed so widgets can update to reflect the change. From d5394d1f72f71269244aa041c9cfd1bfe7e7cdad Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 1 Nov 2025 23:59:09 +0100 Subject: [PATCH 08/14] Only compare profile values --- Source/Core/Common/IniFile.cpp | 13 ++----------- Source/Core/Common/IniFile.h | 2 +- .../Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 2 +- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index 9c6dfa1d853..dcc6a78f7eb 100644 --- a/Source/Core/Common/IniFile.cpp +++ b/Source/Core/Common/IniFile.cpp @@ -353,7 +353,7 @@ bool IniFile::Save(const std::string& filename) } -bool IniFile::CompareContent(IniFile& other) const { +bool IniFile::CompareValues(IniFile& other) const { if (sections.size() != other.sections.size()) return false; @@ -362,16 +362,7 @@ bool IniFile::CompareContent(IniFile& other) const { const Section* os = other.GetSection(s.name); if (!os) return false; - - // Compare raw lines - if (s.m_lines != os->m_lines) - return false; - - // Compare key order - if (s.keys_order != os->keys_order) - return false; - - // Compare values + if (s.values.size() != os->values.size()) return false; diff --git a/Source/Core/Common/IniFile.h b/Source/Core/Common/IniFile.h index c002374b98b..1592aa26a88 100644 --- a/Source/Core/Common/IniFile.h +++ b/Source/Core/Common/IniFile.h @@ -81,7 +81,7 @@ public: IniFile(); ~IniFile(); - bool CompareContent(IniFile& other) const; + bool CompareValues(IniFile& other) const; /** * Loads sections and keys. diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index ed4957d00a6..774bc2b6de2 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -265,7 +265,7 @@ void MappingWindow::UpdateProfileButtonState() m_controller->LoadConfig(memoryIni.GetOrCreateSection("Profile")); // Compare mappings - bool mapping_is_the_same = diskIni.CompareContent(memoryIni); + bool mapping_is_the_same = diskIni.CompareValues(memoryIni); if (mapping_is_the_same) { load_enabled = false; load_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); From d021706b1a163c910feb8e9976eaffe9138b9428 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 2 Nov 2025 00:08:43 +0100 Subject: [PATCH 09/14] fix linting --- Source/Core/Common/IniFile.cpp | 6 +++--- .../Config/Mapping/MappingButton.cpp | 9 ++++----- .../Config/Mapping/MappingWindow.cpp | 19 ++++++++++++------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index dcc6a78f7eb..a5b26d69248 100644 --- a/Source/Core/Common/IniFile.cpp +++ b/Source/Core/Common/IniFile.cpp @@ -352,8 +352,8 @@ bool IniFile::Save(const std::string& filename) return File::RenameSync(temp, filename); } - -bool IniFile::CompareValues(IniFile& other) const { +bool IniFile::CompareValues(IniFile& other) const +{ if (sections.size() != other.sections.size()) return false; @@ -362,7 +362,7 @@ bool IniFile::CompareValues(IniFile& other) const { const Section* os = other.GetSection(s.name); if (!os) return false; - + if (s.values.size() != os->values.size()) return false; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index e7ec5980042..a747fda5fe4 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -92,11 +92,10 @@ void MappingButton::AdvancedPressed() IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference, m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output); - - connect(&io, &IOWindow::OnMappingChange, m_mapping_window, [this] { - m_mapping_window->OnMappingChange(); - }); - + + connect(&io, &IOWindow::OnMappingChange, m_mapping_window, + [this] { m_mapping_window->OnMappingChange(); }); + io.exec(); ConfigChanged(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 774bc2b6de2..d6c0add8df7 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -237,10 +237,12 @@ void MappingWindow::UpdateProfileIndex() void MappingWindow::UpdateProfileButtonState() { - // currentData() is not up to date on a currentTextChanged() event, so find profile path from currentText() (which is up to date) + // currentData() is not up to date on a currentTextChanged() event, so find profile path from + // currentText() (which is up to date) const int index = m_profiles_combo->findText(m_profiles_combo->currentText()); QString profile_path; - if (index != -1) profile_path = m_profiles_combo->itemData(index).toString(); + if (index != -1) + profile_path = m_profiles_combo->itemData(index).toString(); // Init new buttons state (enabled by default) bool load_enabled = true; @@ -263,14 +265,17 @@ void MappingWindow::UpdateProfileButtonState() m_controller->SaveConfig(diskIni.GetOrCreateSection("Profile")); // Restore controller mapping from memory m_controller->LoadConfig(memoryIni.GetOrCreateSection("Profile")); - + // Compare mappings bool mapping_is_the_same = diskIni.CompareValues(memoryIni); - if (mapping_is_the_same) { + if (mapping_is_the_same) + { load_enabled = false; - load_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); + load_tooltip = + tr("Cannot do this action. Reason: No changes between profile file and current mappings"); save_enabled = false; - save_tooltip = tr("Cannot do this action. Reason: No changes between profile file and current mappings"); + save_tooltip = + tr("Cannot do this action. Reason: No changes between profile file and current mappings"); } // Make sure save/delete buttons are disabled for built-in profiles @@ -666,4 +671,4 @@ void MappingWindow::ActivateExtensionTab() void MappingWindow::OnMappingChange() { UpdateProfileButtonState(); -} \ No newline at end of file +} From 5201adc046b210224154691988f1f915b3522f63 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 2 Nov 2025 15:10:40 +0100 Subject: [PATCH 10/14] Put "Delete" and "Open folder" buttons in a drop-down Co-authored-by: Max Chateau --- .../Config/Mapping/MappingWindow.cpp | 19 +++++++++++++------ .../DolphinQt/Config/Mapping/MappingWindow.h | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index d6c0add8df7..21470da2935 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -145,8 +145,16 @@ void MappingWindow::CreateProfilesLayout() m_profiles_combo = new QComboBox(); m_profiles_load = new NonDefaultQPushButton(tr("Load")); m_profiles_save = new NonDefaultQPushButton(tr("Save")); - m_profiles_delete = new NonDefaultQPushButton(tr("Delete")); - m_profiles_open_folder = new NonDefaultQPushButton(tr("Open Folder")); + + // Other actions + m_profile_other_actions = new QToolButton(); + m_profile_other_actions->setPopupMode(QToolButton::InstantPopup); + m_profile_other_actions->setArrowType(Qt::DownArrow); + m_profile_other_actions->setStyleSheet(QStringLiteral("QToolButton::menu-indicator { image: none; }")); // remove other arrow + m_profiles_delete = new QAction(tr("Delete"), this); + m_profiles_open_folder = new QAction(tr("Open Folder"), this); + m_profile_other_actions->addAction(m_profiles_delete); + m_profile_other_actions->addAction(m_profiles_open_folder); auto* button_layout = new QHBoxLayout(); @@ -157,8 +165,7 @@ void MappingWindow::CreateProfilesLayout() m_profiles_layout->addWidget(m_profiles_combo); button_layout->addWidget(m_profiles_load); button_layout->addWidget(m_profiles_save); - button_layout->addWidget(m_profiles_delete); - button_layout->addWidget(m_profiles_open_folder); + button_layout->addWidget(m_profile_other_actions); m_profiles_layout->addLayout(button_layout); m_profiles_box->setLayout(m_profiles_layout); @@ -209,8 +216,8 @@ void MappingWindow::ConnectWidgets() connect(m_reset_default, &QPushButton::clicked, this, &MappingWindow::OnDefaultFieldsPressed); connect(m_profiles_save, &QPushButton::clicked, this, &MappingWindow::OnSaveProfilePressed); connect(m_profiles_load, &QPushButton::clicked, this, &MappingWindow::OnLoadProfilePressed); - connect(m_profiles_delete, &QPushButton::clicked, this, &MappingWindow::OnDeleteProfilePressed); - connect(m_profiles_open_folder, &QPushButton::clicked, this, &MappingWindow::OnOpenProfileFolder); + connect(m_profiles_delete, &QAction::triggered, this, &MappingWindow::OnDeleteProfilePressed); + connect(m_profiles_open_folder, &QAction::triggered, this, &MappingWindow::OnOpenProfileFolder); connect(m_profiles_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectProfile); connect(m_profiles_combo, &QComboBox::currentTextChanged, this, diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index d65fdb2bd07..c8be27bd73b 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -117,8 +117,9 @@ private: QComboBox* m_profiles_combo; QPushButton* m_profiles_load; QPushButton* m_profiles_save; - QPushButton* m_profiles_delete; - QPushButton* m_profiles_open_folder; + QToolButton* m_profile_other_actions; + QAction* m_profiles_delete; + QAction* m_profiles_open_folder; // Reset QGroupBox* m_reset_box; From 5ea51d352b5c8421bad5dce4911114613b094925 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 3 Nov 2025 08:24:54 +0100 Subject: [PATCH 11/14] cleanup --- Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index a747fda5fe4..9e614add949 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -93,8 +93,7 @@ void MappingButton::AdvancedPressed() IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference, m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output); - connect(&io, &IOWindow::OnMappingChange, m_mapping_window, - [this] { m_mapping_window->OnMappingChange(); }); + connect(&io, &IOWindow::OnMappingChange, [this] { m_mapping_window->OnMappingChange(); }); io.exec(); From 14a87da5683081df5e8f404e21932118f91446ef Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 3 Nov 2025 10:24:03 +0100 Subject: [PATCH 12/14] add events when editing numeric inputs --- Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp | 2 ++ Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp | 7 +++++++ Source/Core/DolphinQt/Config/Mapping/MappingWidget.h | 2 ++ 3 files changed, 11 insertions(+) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp index c6e05abd4e4..3b7e4c19c26 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp @@ -21,6 +21,7 @@ MappingDouble::MappingDouble(MappingWidget* parent, ControllerEmu::NumericSettin connect(this, &QDoubleSpinBox::valueChanged, this, [this, parent](double value) { m_setting.SetValue(value); ConfigChanged(); + parent->OnMappingChange(); parent->SaveSettings(); }); @@ -83,6 +84,7 @@ MappingBool::MappingBool(MappingWidget* parent, ControllerEmu::NumericSettingOnMappingChange(); parent->SaveSettings(); }); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 8a5f8c0e474..5474ec53ce0 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -392,6 +392,8 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB ConfigChanged(); IOWindow io(GetParent(), GetController(), &setting.GetInputReference(), IOWindow::Type::Input); + connect(&io, &IOWindow::OnMappingChange, [this] { OnMappingChange(); }); + io.exec(); setting.SimplifyIfPossible(); @@ -402,3 +404,8 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB return button; } + +void MappingWidget::OnMappingChange() +{ + this->m_parent->OnMappingChange(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index 5be8e99e33e..f4fab0357a2 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -37,6 +37,8 @@ public: virtual void SaveSettings() = 0; virtual InputConfig* GetConfig() = 0; + void OnMappingChange(); + signals: void Update(); void ConfigChanged(); From 9cac6aab6d96b6f55e0f04a7efb9354a7551642a Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 3 Nov 2025 11:38:37 +0100 Subject: [PATCH 13/14] also update buttons on MappingWindow::ConfigChange() This trigger e.g. when clearing all buttons --- Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 21470da2935..0fe8bee273c 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -210,6 +210,7 @@ void MappingWindow::ConnectWidgets() { connect(&Settings::Instance(), &Settings::DevicesChanged, this, &MappingWindow::ConfigChanged); connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::UpdateDeviceList); + connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::OnMappingChange); connect(m_devices_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectDevice); connect(m_reset_clear, &QPushButton::clicked, this, &MappingWindow::OnClearFieldsPressed); From 42d8ed7c82ea6e3754cb5bd8e8401bf0d54187cb Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 3 Nov 2025 12:32:12 +0100 Subject: [PATCH 14/14] fix linting --- Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 0fe8bee273c..697c1a522ae 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -150,7 +150,8 @@ void MappingWindow::CreateProfilesLayout() m_profile_other_actions = new QToolButton(); m_profile_other_actions->setPopupMode(QToolButton::InstantPopup); m_profile_other_actions->setArrowType(Qt::DownArrow); - m_profile_other_actions->setStyleSheet(QStringLiteral("QToolButton::menu-indicator { image: none; }")); // remove other arrow + m_profile_other_actions->setStyleSheet( + QStringLiteral("QToolButton::menu-indicator { image: none; }")); // remove other arrow m_profiles_delete = new QAction(tr("Delete"), this); m_profiles_open_folder = new QAction(tr("Open Folder"), this); m_profile_other_actions->addAction(m_profiles_delete);