From 954219f781a05b9ad9175ebcf578d40625af41bc Mon Sep 17 00:00:00 2001 From: TryTwo Date: Thu, 12 Feb 2026 02:01:58 -0700 Subject: [PATCH] Cheat Search: Add ability to delete items and fix duplicate commands when multiple row items are selected. Changes it to select rows then extracts one item from each row to operate on. The behavior and data across each item in a row was already identical, so using rows doesn't change anything. --- Source/Core/Core/CheatSearch.cpp | 9 +++++ Source/Core/Core/CheatSearch.h | 4 ++ Source/Core/DolphinQt/CheatSearchWidget.cpp | 45 ++++++++++++++++++--- Source/Core/DolphinQt/CheatSearchWidget.h | 1 + 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/CheatSearch.cpp b/Source/Core/Core/CheatSearch.cpp index 2cf509e880..063a09a582 100644 --- a/Source/Core/Core/CheatSearch.cpp +++ b/Source/Core/Core/CheatSearch.cpp @@ -283,6 +283,15 @@ void Cheats::CheatSearchSession::ResetResults() m_search_results.clear(); } +template +void Cheats::CheatSearchSession::RemoveResult(size_t index) +{ + if (index < m_search_results.size()) + { + m_search_results.erase(m_search_results.begin() + index); + } +} + template static std::function MakeCompareFunctionForSpecificValue(Cheats::CompareType op, const T& old_value) diff --git a/Source/Core/Core/CheatSearch.h b/Source/Core/Core/CheatSearch.h index 7df135d51a..7d1c089835 100644 --- a/Source/Core/Core/CheatSearch.h +++ b/Source/Core/Core/CheatSearch.h @@ -168,6 +168,9 @@ public: virtual bool WriteValue(const Core::CPUThreadGuard& guard, std::span addresses) const = 0; + // User can delete a search result. + virtual void RemoveResult(size_t index) = 0; + // Create a complete copy of this search session. virtual std::unique_ptr Clone() const = 0; @@ -195,6 +198,7 @@ public: bool SetValueFromString(const std::string& value_as_string, bool force_parse_as_hex) override; void ResetResults() override; + void RemoveResult(size_t index) override; SearchErrorCode RunSearch(const Core::CPUThreadGuard& guard) override; size_t GetMemoryRangeCount() const override; diff --git a/Source/Core/DolphinQt/CheatSearchWidget.cpp b/Source/Core/DolphinQt/CheatSearchWidget.cpp index 629d8dbafb..35468d5fcc 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.cpp +++ b/Source/Core/DolphinQt/CheatSearchWidget.cpp @@ -3,7 +3,9 @@ #include "DolphinQt/CheatSearchWidget.h" +#include #include +#include #include #include #include @@ -224,6 +226,7 @@ void CheatSearchWidget::CreateWidgets() m_address_table = new QTableWidget(); m_address_table->setContextMenuPolicy(Qt::CustomContextMenu); + m_address_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_info_label_1 = new QLabel(tr("Waiting for first scan...")); m_info_label_2 = new QLabel(); @@ -483,6 +486,8 @@ void CheatSearchWidget::OnAddressTableContextMenu() if (m_address_table->selectedItems().isEmpty()) return; + std::vector selected_items = GetSelectedAddressTableItems(); + QMenu* menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose, true); @@ -491,8 +496,8 @@ void CheatSearchWidget::OnAddressTableContextMenu() const u32 address = item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt(); emit ShowMemory(address); }); - menu->addAction(tr("Add to watch"), this, [this] { - for (auto* const item : m_address_table->selectedItems()) + menu->addAction(tr("Add to watch"), this, [this, selected_items] { + for (auto* const item : selected_items) { const u32 address = item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt(); const QString name = QStringLiteral("mem_%1").arg(address, 8, 16, QLatin1Char('0')); @@ -501,6 +506,15 @@ void CheatSearchWidget::OnAddressTableContextMenu() }); menu->addAction(tr("Generate Action Replay Code(s)"), this, &CheatSearchWidget::GenerateARCodes); menu->addAction(tr("Write value"), this, &CheatSearchWidget::WriteValue); + menu->addAction(tr("Delete Address"), this, [this, selected_items] { + // Process in reverse so removal won't change the index of items about to be processed. + for (auto* const item : selected_items | std::views::reverse) + { + const u32 index = item->data(ADDRESS_TABLE_RESULT_INDEX_ROLE).toUInt(); + m_last_value_session->RemoveResult(index); + } + RecreateGUITable(); + }); menu->exec(QCursor::pos()); } @@ -533,7 +547,7 @@ void CheatSearchWidget::GenerateARCodes() bool had_multiple_errors = false; std::optional error_code; - for (auto* const item : m_address_table->selectedItems()) + for (auto* const item : GetSelectedAddressTableItems()) { const u32 index = item->data(ADDRESS_TABLE_RESULT_INDEX_ROLE).toUInt(); const auto result = Cheats::GenerateActionReplayCode(*m_last_value_session, index); @@ -600,9 +614,9 @@ void CheatSearchWidget::WriteValue() return; } - auto items = m_address_table->selectedItems(); + auto items = GetSelectedAddressTableItems(); std::vector addresses(items.size()); - std::transform(items.begin(), items.end(), addresses.begin(), [](QTableWidgetItem* item) { + std::transform(items.begin(), items.end(), addresses.begin(), [](const QTableWidgetItem* item) { return item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt(); }); Core::CPUThreadGuard guard{m_system}; @@ -610,6 +624,7 @@ void CheatSearchWidget::WriteValue() { m_info_label_1->setText(tr("There was an error writing (some) values.")); } + UpdateTableAllCurrentValues(UpdateSource::User); } size_t CheatSearchWidget::GetTableRowCount() const @@ -638,6 +653,26 @@ void CheatSearchWidget::RefreshGUICurrentValues(const size_t begin_index, const } } +const std::vector CheatSearchWidget::GetSelectedAddressTableItems() const +{ + // Don't process each selectedItems(), as it can produce duplicate commands for one address when + // multiple items in the same row are selected. Instead, uses rows and gets one item from each + // row. All row items have identical data. + auto selected_rows = m_address_table->selectionModel()->selectedRows(); + + // Ascending address order. + std::sort(selected_rows.begin(), selected_rows.end(), + [](const QModelIndex& a, const QModelIndex& b) { return a.row() < b.row(); }); + + std::vector selected_items; + for (const auto& index : selected_rows) + { + const int row = index.row(); + selected_items.push_back(m_address_table->item(row, 0)); + } + return selected_items; +} + void CheatSearchWidget::RecreateGUITable() { const QSignalBlocker blocker(m_address_table); diff --git a/Source/Core/DolphinQt/CheatSearchWidget.h b/Source/Core/DolphinQt/CheatSearchWidget.h index 18aa5a14b0..5dec20c3ed 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.h +++ b/Source/Core/DolphinQt/CheatSearchWidget.h @@ -75,6 +75,7 @@ private: int GetVisibleRowsBeginIndex() const; int GetVisibleRowsEndIndex() const; size_t GetTableRowCount() const; + const std::vector GetSelectedAddressTableItems() const; Core::System& m_system;