summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorDentomologist <dentomologist@gmail.com>2026-02-19 14:36:54 -0800
committerGitHub <noreply@github.com>2026-02-19 14:36:54 -0800
commit02db73c8dcb67f78fb93087a1fc24b65591f25f0 (patch)
tree4718cd225976b62a8c404fc227e94fbdd2954bdc /Source
parent9323074ada4b1d372809dc71ed092efe8d0e4c8e (diff)
parent954219f781a05b9ad9175ebcf578d40625af41bc (diff)
Merge pull request #14344 from TryTwo/cheatsearch_work2
Cheat Search: Add ability to delete items and fix duplicate commands
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Core/CheatSearch.cpp9
-rw-r--r--Source/Core/Core/CheatSearch.h4
-rw-r--r--Source/Core/DolphinQt/CheatSearchWidget.cpp45
-rw-r--r--Source/Core/DolphinQt/CheatSearchWidget.h1
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
@@ -284,6 +284,15 @@ void Cheats::CheatSearchSession<T>::ResetResults()
}
template <typename T>
+void Cheats::CheatSearchSession<T>::RemoveResult(size_t index)
+{
+ if (index < m_search_results.size())
+ {
+ m_search_results.erase(m_search_results.begin() + index);
+ }
+}
+
+template <typename T>
static std::function<bool(const T& new_value)>
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<u32> 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<CheatSearchSessionBase> 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 <algorithm>
#include <optional>
+#include <ranges>
#include <string>
#include <unordered_map>
#include <utility>
@@ -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<const QTableWidgetItem*> 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<Cheats::GenerateActionReplayCodeErrorCode> 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<u32> 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<const QTableWidgetItem*> 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<const QTableWidgetItem*> 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<const QTableWidgetItem*> GetSelectedAddressTableItems() const;
Core::System& m_system;