diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index 6e6749cb0db..a5b26d69248 100644 --- a/Source/Core/Common/IniFile.cpp +++ b/Source/Core/Common/IniFile.cpp @@ -352,6 +352,33 @@ bool IniFile::Save(const std::string& filename) return File::RenameSync(temp, filename); } +bool IniFile::CompareValues(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; + + 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..1592aa26a88 100644 --- a/Source/Core/Common/IniFile.h +++ b/Source/Core/Common/IniFile.h @@ -81,6 +81,8 @@ public: IniFile(); ~IniFile(); + bool CompareValues(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 53da173bba3..74b4ceaf11d 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 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 88888434479..e719c5ad546 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 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 24e7be9bf8f..9e614add949 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -92,6 +92,9 @@ 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, [this] { m_mapping_window->OnMappingChange(); }); + io.exec(); ConfigChanged(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 7e7d2b9f425..ba60b172f54 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->OnMappingChange(); } void UpdateInputDetectionStartTimer() 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(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 23ce80a489c..697c1a522ae 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -211,6 +211,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); @@ -221,7 +222,7 @@ void MappingWindow::ConnectWidgets() connect(m_profiles_open_folder, &QAction::triggered, 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. @@ -245,19 +246,66 @@ void MappingWindow::UpdateProfileIndex() void MappingWindow::UpdateProfileButtonState() { - // Make sure save/delete buttons are disabled for built-in profiles + // 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(); - bool builtin = false; - if (m_profiles_combo->findText(m_profiles_combo->currentText()) != -1) + // 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 + // Load memory mapping + Common::IniFile memoryIni; + m_controller->SaveConfig(memoryIni.GetOrCreateSection("Profile")); + + // 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.CompareValues(memoryIni); + if (mapping_is_the_same) { - 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)); + 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"); } - m_profiles_save->setEnabled(!builtin); - m_profiles_delete->setEnabled(!builtin); + // 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)); + 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"); + } + + // 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) @@ -335,6 +383,8 @@ void MappingWindow::OnLoadProfilePressed() m_controller->UpdateReferences(g_controller_interface); m_controller->GetConfig()->GenerateControllerTextures(); + UpdateProfileButtonState(); + const auto lock = GetController()->GetStateLock(); emit ConfigChanged(); } @@ -356,6 +406,8 @@ void MappingWindow::OnSaveProfilePressed() m_controller->SaveConfig(ini.GetOrCreateSection("Profile")); ini.Save(profile_path); + UpdateProfileButtonState(); + if (m_profiles_combo->findText(profile_name) == -1) { PopulateProfileSelection(); @@ -624,3 +676,8 @@ void MappingWindow::ActivateExtensionTab() { m_tab_widget->setCurrentIndex(3); } + +void MappingWindow::OnMappingChange() +{ + UpdateProfileButtonState(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 222d3e3a58a..c8be27bd73b 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 OnMappingChange(); signals: // Emitted when config has changed so widgets can update to reflect the change.