diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index b92e3c6b4a..806ad2e65c 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -160,6 +160,7 @@ target_sources(pcsx2-qt PRIVATE Debugger/AnalysisOptionsDialog.ui Debugger/DebuggerSettingsManager.cpp Debugger/DebuggerSettingsManager.h + Debugger/DebuggerEvents.h Debugger/DebuggerWidget.cpp Debugger/DebuggerWidget.h Debugger/DebuggerWindow.cpp diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp index dffa63d7ef..26cf591454 100644 --- a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp @@ -32,10 +32,14 @@ int BreakpointModel::columnCount(const QModelIndex&) const QVariant BreakpointModel::data(const QModelIndex& index, int role) const { + size_t row = static_cast(index.row()); + if (row >= m_breakpoints.size()) + return QVariant(); + + BreakpointMemcheck bp_mc = m_breakpoints[row]; + if (role == Qt::DisplayRole) { - auto bp_mc = m_breakpoints.at(index.row()); - if (const auto* bp = std::get_if(&bp_mc)) { switch (index.column()) @@ -87,8 +91,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const } else if (role == BreakpointModel::DataRole) { - auto bp_mc = m_breakpoints.at(index.row()); - if (const auto* bp = std::get_if(&bp_mc)) { switch (index.column()) @@ -133,8 +135,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const } else if (role == BreakpointModel::ExportRole) { - auto bp_mc = m_breakpoints.at(index.row()); - if (const auto* bp = std::get_if(&bp_mc)) { switch (index.column()) @@ -181,8 +181,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const { if (index.column() == 0) { - auto bp_mc = m_breakpoints.at(index.row()); - if (const auto* bp = std::get_if(&bp_mc)) { return bp->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked; diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp index 4617a6e3e1..679fa26c74 100644 --- a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp @@ -8,99 +8,110 @@ #include "BreakpointDialog.h" #include "BreakpointModel.h" -#include +#include -BreakpointWidget::BreakpointWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) - , m_model(cpu) +BreakpointWidget::BreakpointWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) + , m_model(new BreakpointModel(cpu())) { m_ui.setupUi(this); - connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointWidget::onContextMenu); + if (cpu().getCpuType() == BREAKPOINT_EE) + { + connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) { + if (title.isEmpty()) + return; + + if (m_model->rowCount() == 0) + DebuggerSettingsManager::loadGameSettings(m_model); + }); + + DebuggerSettingsManager::loadGameSettings(m_model); + } + + m_ui.breakpointList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointWidget::openContextMenu); connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &BreakpointWidget::onDoubleClicked); - m_ui.breakpointList->setModel(&m_model); + m_ui.breakpointList->setModel(m_model); for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes) { m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode); i++; } - connect(&m_model, &BreakpointModel::dataChanged, &m_model, &BreakpointModel::refreshData); + connect(m_model, &BreakpointModel::dataChanged, m_model, &BreakpointModel::refreshData); + + receiveEvent([this](const DebuggerEvents::BreakpointsChanged& event) -> bool { + m_model->refreshData(); + return true; + }); } void BreakpointWidget::onDoubleClicked(const QModelIndex& index) { if (index.isValid() && index.column() == BreakpointModel::OFFSET) - { - not_yet_implemented(); - //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_model.data(index, BreakpointModel::DataRole).toUInt()); - } + goToInDisassembler(m_model->data(index, BreakpointModel::DataRole).toUInt(), true); } -void BreakpointWidget::onContextMenu(QPoint pos) +void BreakpointWidget::openContextMenu(QPoint pos) { - QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList); + QMenu* menu = new QMenu(m_ui.breakpointList); + menu->setAttribute(Qt::WA_DeleteOnClose); + if (cpu().isAlive()) { - - QAction* newAction = new QAction(tr("New"), m_ui.breakpointList); + QAction* newAction = menu->addAction(tr("New")); connect(newAction, &QAction::triggered, this, &BreakpointWidget::contextNew); - contextMenu->addAction(newAction); const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); if (selModel->hasSelection()) { - QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList); + QAction* editAction = menu->addAction(tr("Edit")); connect(editAction, &QAction::triggered, this, &BreakpointWidget::contextEdit); - contextMenu->addAction(editAction); if (selModel->selectedIndexes().count() == 1) { - QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList); + QAction* copyAction = menu->addAction(tr("Copy")); connect(copyAction, &QAction::triggered, this, &BreakpointWidget::contextCopy); - contextMenu->addAction(copyAction); } - QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList); + QAction* deleteAction = menu->addAction(tr("Delete")); connect(deleteAction, &QAction::triggered, this, &BreakpointWidget::contextDelete); - contextMenu->addAction(deleteAction); } } - contextMenu->addSeparator(); - if (m_model.rowCount() > 0) + menu->addSeparator(); + if (m_model->rowCount() > 0) { - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList); + QAction* actionExport = menu->addAction(tr("Copy all as CSV")); connect(actionExport, &QAction::triggered, [this]() { // It's important to use the Export Role here to allow pasting to be translation agnostic QGuiApplication::clipboard()->setText( - QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), - BreakpointModel::ExportRole, true)); + QtUtils::AbstractItemModelToCSV(m_model, BreakpointModel::ExportRole, true)); }); - contextMenu->addAction(actionExport); } if (cpu().isAlive()) { - QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList); + QAction* actionImport = menu->addAction(tr("Paste from CSV")); connect(actionImport, &QAction::triggered, this, &BreakpointWidget::contextPasteCSV); - contextMenu->addAction(actionImport); - QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.breakpointList); - connect(actionLoad, &QAction::triggered, [this]() { - m_model.clear(); - DebuggerSettingsManager::loadGameSettings(&m_model); - }); - contextMenu->addAction(actionLoad); + if (cpu().getCpuType() == BREAKPOINT_EE) + { + QAction* actionLoad = menu->addAction(tr("Load from Settings")); + connect(actionLoad, &QAction::triggered, [this]() { + m_model->clear(); + DebuggerSettingsManager::loadGameSettings(m_model); + }); - QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.breakpointList); - connect(actionSave, &QAction::triggered, this, &BreakpointWidget::saveBreakpointsToDebuggerSettings); - contextMenu->addAction(actionSave); + QAction* actionSave = menu->addAction(tr("Save to Settings")); + connect(actionSave, &QAction::triggered, this, &BreakpointWidget::saveBreakpointsToDebuggerSettings); + } } - contextMenu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos)); + menu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos)); } void BreakpointWidget::contextCopy() @@ -110,7 +121,7 @@ void BreakpointWidget::contextCopy() if (!selModel->hasSelection()) return; - QGuiApplication::clipboard()->setText(m_model.data(selModel->currentIndex()).toString()); + QGuiApplication::clipboard()->setText(m_model->data(selModel->currentIndex()).toString()); } void BreakpointWidget::contextDelete() @@ -128,13 +139,13 @@ void BreakpointWidget::contextDelete() for (const QModelIndex& index : rows) { - m_model.removeRows(index.row(), 1); + m_model->removeRows(index.row(), 1); } } void BreakpointWidget::contextNew() { - BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), m_model); + BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model); bpDialog->show(); } @@ -147,9 +158,9 @@ void BreakpointWidget::contextEdit() const int selectedRow = selModel->selectedIndexes().first().row(); - auto bpObject = m_model.at(selectedRow); + auto bpObject = m_model->at(selectedRow); - BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), m_model, bpObject, selectedRow); + BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model, bpObject, selectedRow); bpDialog->show(); } @@ -173,11 +184,11 @@ void BreakpointWidget::contextPasteCSV() QString matchedValue = match.captured(0); fields << matchedValue.mid(1, matchedValue.length() - 2); } - m_model.loadBreakpointFromFieldList(fields); + m_model->loadBreakpointFromFieldList(fields); } } void BreakpointWidget::saveBreakpointsToDebuggerSettings() { - DebuggerSettingsManager::saveGameSettings(&m_model); + DebuggerSettingsManager::saveGameSettings(m_model); } diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h index 4bb750ccbc..479bdf247f 100644 --- a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h @@ -21,10 +21,10 @@ class BreakpointWidget : public DebuggerWidget Q_OBJECT public: - BreakpointWidget(DebugInterface& cpu, QWidget* parent = nullptr); + BreakpointWidget(const DebuggerWidgetParameters& parameters); void onDoubleClicked(const QModelIndex& index); - void onContextMenu(QPoint pos); + void openContextMenu(QPoint pos); void contextCopy(); void contextDelete(); @@ -37,5 +37,5 @@ public: private: Ui::BreakpointWidget m_ui; - BreakpointModel m_model; + BreakpointModel* m_model; }; diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui index 7dc2bf069c..0d906df4a5 100644 --- a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui @@ -30,11 +30,7 @@ 0 - - - Qt::CustomContextMenu - - + diff --git a/pcsx2-qt/Debugger/DebuggerEvents.h b/pcsx2-qt/Debugger/DebuggerEvents.h new file mode 100644 index 0000000000..5f42375cc8 --- /dev/null +++ b/pcsx2-qt/Debugger/DebuggerEvents.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include + +#include + +namespace DebuggerEvents +{ + struct Event + { + virtual ~Event() = default; + }; + + // Sent when a debugger widget is first created, and subsequently broadcast + // to all debugger widgets at regular intervals. + struct Refresh : Event + { + }; + + // Go to the address in a disassembly or memory view and switch to that tab. + struct GoToAddress : Event + { + enum Filter + { + NONE, + DISASSEMBLER, + MEMORY_VIEW + }; + + u32 address = 0; + + // Prevent the memory view from handling events for jumping to functions + // and vice versa. + Filter filter = NONE; + + bool switch_to_tab = true; + + static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in"); + }; + + // The state of the VM has changed and widgets should be updated to reflect + // the new state (e.g. the VM has been paused). + struct VMUpdate : Event + { + }; + + struct BreakpointsChanged : Event + { + }; + + // Add the address to the saved addresses list and switch to that tab. + struct AddToSavedAddresses : Event + { + u32 address = 0; + bool switch_to_tab = true; + + static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to"); + }; +} // namespace DebuggerEvents diff --git a/pcsx2-qt/Debugger/DebuggerWidget.cpp b/pcsx2-qt/Debugger/DebuggerWidget.cpp index aead513086..b4c8eccade 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.cpp +++ b/pcsx2-qt/Debugger/DebuggerWidget.cpp @@ -3,7 +3,10 @@ #include "DebuggerWidget.h" -#include "JsonValueWrapper.h" +#include "Debugger/DebuggerWindow.h" +#include "Debugger/JsonValueWrapper.h" +#include "Debugger/Docking/DockManager.h" +#include "Debugger/Docking/DockTables.h" #include "DebugTools/DebugInterface.h" @@ -18,6 +21,15 @@ DebugInterface& DebuggerWidget::cpu() const return *m_cpu; } +QString DebuggerWidget::displayName() +{ + auto description = DockTables::DEBUGGER_WIDGETS.find(metaObject()->className()); + if (description == DockTables::DEBUGGER_WIDGETS.end()) + return QString(); + + return QCoreApplication::translate("DebuggerWidget", description->second.display_name); +} + bool DebuggerWidget::setCpu(DebugInterface& new_cpu) { BreakPointCpu before = cpu().getCpuType(); @@ -39,28 +51,31 @@ bool DebuggerWidget::setCpuOverride(std::optional new_cpu) return before == after; } +bool DebuggerWidget::handleEvent(const DebuggerEvents::Event& event) +{ + auto [begin, end] = m_event_handlers.equal_range(typeid(event).name()); + for (auto handler = begin; handler != end; handler++) + if (handler->second(event)) + { + return true; + } + + return false; +} + +bool DebuggerWidget::acceptsEventType(const char* event_type) +{ + auto [begin, end] = m_event_handlers.equal_range(event_type); + return begin != end; +} + + void DebuggerWidget::toJson(JsonValueWrapper& json) { - if (m_cpu_override.has_value()) - { - const char* cpu_name = DebugInterface::cpuName(*m_cpu_override); - - rapidjson::Value target; - target.SetString(cpu_name, strlen(cpu_name)); - json.value().AddMember("target", target, json.allocator()); - } } bool DebuggerWidget::fromJson(JsonValueWrapper& json) { - auto target = json.value().FindMember("target"); - if (target != json.value().MemberEnd() && target->value.IsString()) - { - for (BreakPointCpu cpu : DEBUG_CPUS) - if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0) - m_cpu_override = cpu; - } - return true; } @@ -77,8 +92,109 @@ void DebuggerWidget::applyMonospaceFont() #endif } -DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent) - : QWidget(parent) - , m_cpu(cpu) +void DebuggerWidget::switchToThisTab() +{ + g_debugger_window->dockManager().switchToDebuggerWidget(this); +} + +void DebuggerWidget::goToInDisassembler(u32 address, bool switch_to_tab) +{ + DebuggerEvents::GoToAddress event; + event.address = address; + event.filter = DebuggerEvents::GoToAddress::DISASSEMBLER; + event.switch_to_tab = switch_to_tab; + DebuggerWidget::sendEvent(std::move(event)); +} + +void DebuggerWidget::goToInMemoryView(u32 address, bool switch_to_tab) +{ + DebuggerEvents::GoToAddress event; + event.address = address; + event.filter = DebuggerEvents::GoToAddress::MEMORY_VIEW; + event.switch_to_tab = switch_to_tab; + DebuggerWidget::sendEvent(std::move(event)); +} + +DebuggerWidget::DebuggerWidget(const DebuggerWidgetParameters& parameters) + : QWidget(parameters.parent) + , m_cpu(parameters.cpu) + , m_cpu_override(parameters.cpu_override) { } + +void DebuggerWidget::sendEventImplementation(const DebuggerEvents::Event& event) +{ + if (!g_debugger_window) + return; + + for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets()) + if (widget->handleEvent(event)) + return; +} + +void DebuggerWidget::broadcastEventImplementation(const DebuggerEvents::Event& event) +{ + if (!g_debugger_window) + return; + + for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets()) + widget->handleEvent(event); +} + +std::vector DebuggerWidget::createEventActionsImplementation( + QMenu* menu, + u32 max_top_level_actions, + bool skip_self, + const char* event_type, + const char* action_prefix, + std::function event_func) +{ + if (!g_debugger_window) + return {}; + + std::vector receivers; + for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets()) + if ((!skip_self || widget != this) && widget->acceptsEventType(event_type)) + receivers.emplace_back(widget); + + QMenu* submenu = nullptr; + if (receivers.size() > max_top_level_actions) + { + QString title_format = QCoreApplication::translate("DebuggerEvent", "%1..."); + submenu = new QMenu(title_format.arg(QCoreApplication::translate("DebuggerEvent", action_prefix)), menu); + } + + std::vector actions; + for (size_t i = 0; i < receivers.size(); i++) + { + DebuggerWidget* receiver = receivers[i]; + + QAction* action; + if (!submenu || i + 1 < max_top_level_actions) + { + QString title_format = QCoreApplication::translate("DebuggerEvent", "%1 %2"); + QString event_title = QCoreApplication::translate("DebuggerEvent", action_prefix); + QString title = title_format.arg(event_title).arg(receiver->displayName()); + action = new QAction(title, menu); + menu->addAction(action); + } + else + { + action = new QAction(receiver->displayName(), submenu); + submenu->addAction(action); + } + + connect(action, &QAction::triggered, receiver, [receiver, event_func]() { + const DebuggerEvents::Event* event = event_func(); + if (event) + receiver->handleEvent(*event); + }); + + actions.emplace_back(action); + } + + if (submenu) + menu->addMenu(submenu); + + return actions; +} diff --git a/pcsx2-qt/Debugger/DebuggerWidget.h b/pcsx2-qt/Debugger/DebuggerWidget.h index c2d9636dd2..d6b0ab132e 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.h +++ b/pcsx2-qt/Debugger/DebuggerWidget.h @@ -3,23 +3,32 @@ #pragma once +#include "QtHost.h" +#include "Debugger/DebuggerEvents.h" + #include "DebugTools/DebugInterface.h" #include -inline void not_yet_implemented() -{ - abort(); -} - class JsonValueWrapper; +// Container for variables to be passed to the constructor of DebuggerWidget. +struct DebuggerWidgetParameters +{ + DebugInterface* cpu = nullptr; + std::optional cpu_override; + QWidget* parent = nullptr; +}; + // The base class for the contents of the dock widgets in the debugger. class DebuggerWidget : public QWidget { Q_OBJECT public: + // Get the translated name that should be displayed for this widget. + QString displayName(); + // Get the effective debug interface associated with this particular widget // if it's set, otherwise return the one associated with the layout that // contains this widget. @@ -36,15 +45,113 @@ public: // returned, we have to recreate the object. bool setCpuOverride(std::optional new_cpu); + // Send each open debugger widget an event in turn, until one handles it. + template + static void sendEvent(Event event) + { + if (!QtHost::IsOnUIThread()) + { + QtHost::RunOnUIThread([event = std::move(event)]() { + DebuggerWidget::sendEventImplementation(event); + }); + return; + } + + sendEventImplementation(event); + } + + // Send all open debugger widgets an event. + template + static void broadcastEvent(Event event) + { + if (!QtHost::IsOnUIThread()) + { + QtHost::RunOnUIThread([event = std::move(event)]() { + DebuggerWidget::broadcastEventImplementation(event); + }); + return; + } + + broadcastEventImplementation(event); + } + + // Register a handler callback for the specified type of event. + template + void receiveEvent(std::function callback) + { + m_event_handlers.emplace( + typeid(Event).name(), + [callback](const DebuggerEvents::Event& event) -> bool { + return callback(static_cast(event)); + }); + } + + // Register a handler member function for the specified type of event. + template + void receiveEvent(bool (SubClass::*function)(const Event& event)) + { + m_event_handlers.emplace( + typeid(Event).name(), + [this, function](const DebuggerEvents::Event& event) -> bool { + return (*static_cast(this).*function)(static_cast(event)); + }); + } + + // Call the handler callback for the specified event. + bool handleEvent(const DebuggerEvents::Event& event); + + // Check if this debugger widget can receive the specified type of event. + bool acceptsEventType(const char* event_type); + + // Generates context menu actions to send an event to each debugger widget + // that can receive it. A submenu is generated if the number of possible + // receivers exceeds max_top_level_actions. If skip_self is true, actions + // are only generated if the sender and receiver aren't the same object. + template + std::vector createEventActions( + QMenu* menu, + std::function()> event_func, + bool skip_self = true, + u32 max_top_level_actions = 5) + { + return createEventActionsImplementation( + menu, max_top_level_actions, skip_self, typeid(Event).name(), Event::ACTION_PREFIX, + [event_func]() -> DebuggerEvents::Event* { + static std::optional event; + event = event_func(); + if (!event.has_value()) + return nullptr; + + return static_cast(&(*event)); + }); + } + virtual void toJson(JsonValueWrapper& json); virtual bool fromJson(JsonValueWrapper& json); void applyMonospaceFont(); + void switchToThisTab(); + + static void goToInDisassembler(u32 address, bool switch_to_tab); + static void goToInMemoryView(u32 address, bool switch_to_tab); + protected: - DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr); + DebuggerWidget(const DebuggerWidgetParameters& parameters); private: + static void sendEventImplementation(const DebuggerEvents::Event& event); + static void broadcastEventImplementation(const DebuggerEvents::Event& event); + + std::vector createEventActionsImplementation( + QMenu* menu, + u32 max_top_level_actions, + bool skip_self, + const char* event_type, + const char* action_prefix, + std::function event_func); + DebugInterface* m_cpu; std::optional m_cpu_override; + std::multimap> m_event_handlers; }; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp index 4ab710d902..32283908ec 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.cpp +++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp @@ -3,6 +3,7 @@ #include "DebuggerWindow.h" +#include "Debugger/DebuggerWidget.h" #include "Debugger/Docking/DockManager.h" #include "DebugTools/DebugInterface.h" @@ -61,6 +62,10 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) m_dock_manager->resetDefaultLayouts(); }); + connect(g_emu_thread, &EmuThread::onVMPaused, this, []() { + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); + }); + connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMStateChanged); connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMStateChanged); @@ -75,6 +80,12 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) Host::RunOnCPUThread([]() { R5900SymbolImporter.OnDebuggerOpened(); }); + + QTimer* refresh_timer = new QTimer(this); + connect(refresh_timer, &QTimer::timeout, this, []() { + DebuggerWidget::broadcastEvent(DebuggerEvents::Refresh()); + }); + refresh_timer->start(1000); } DebuggerWindow* DebuggerWindow::getInstance() @@ -330,7 +341,6 @@ void DebuggerWindow::setupDefaultToolBarState() m_default_toolbar_state = saveState(); - for (QToolBar* toolbar : findChildren()) connect(toolbar, &QToolBar::topLevelChanged, m_dock_manager, &DockManager::updateToolBarLockState); } diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index fb8f9b63f4..5549c6db3c 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -19,16 +19,39 @@ using namespace QtUtils; -DisassemblyWidget::DisassemblyWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) +DisassemblyWidget::DisassemblyWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) { m_ui.setupUi(this); - m_disassemblyManager.setCpu(&cpu); + m_disassemblyManager.setCpu(&cpu()); - connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested); + setFocusPolicy(Qt::FocusPolicy::ClickFocus); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::openContextMenu); applyMonospaceFont(); + + connect(g_emu_thread, &EmuThread::onVMPaused, this, &DisassemblyWidget::gotoProgramCounterOnPause); + + receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool { + update(); + return true; + }); + + receiveEvent([this](const DebuggerEvents::GoToAddress& event) -> bool { + if (event.filter != DebuggerEvents::GoToAddress::NONE && + event.filter != DebuggerEvents::GoToAddress::DISASSEMBLER) + return false; + + gotoAddress(event.address, event.switch_to_tab); + + if (event.switch_to_tab) + switchToThisTab(); + + return true; + }); } DisassemblyWidget::~DisassemblyWidget() = default; @@ -82,7 +105,7 @@ void DisassemblyWidget::contextAssembleInstruction() this->m_nopedInstructions.insert({i, cpu->read32(i)}); cpu->write32(i, val); } - emit VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); }); } } @@ -95,7 +118,7 @@ void DisassemblyWidget::contextNoopInstruction() this->m_nopedInstructions.insert({i, cpu->read32(i)}); cpu->write32(i, 0x00); } - emit VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); }); } @@ -110,7 +133,7 @@ void DisassemblyWidget::contextRestoreInstruction() this->m_nopedInstructions.erase(i); } } - emit VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); }); } @@ -145,7 +168,7 @@ void DisassemblyWidget::contextToggleBreakpoint() Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::AddBreakPoint(cpuType, selectedAddressStart); }); } - breakpointsChanged(); + broadcastEvent(DebuggerEvents::BreakpointsChanged()); this->repaint(); } @@ -254,7 +277,7 @@ void DisassemblyWidget::contextStubFunction() this->m_stubbedFunctions.insert({address, {cpu->read32(address), cpu->read32(address + 4)}}); cpu->write32(address, 0x03E00008); // jr ra cpu->write32(address + 4, 0x00000000); // nop - emit VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); }); } @@ -275,7 +298,7 @@ void DisassemblyWidget::contextRestoreFunction() cpu->write32(address, first_instruction); cpu->write32(address + 4, second_instruction); this->m_stubbedFunctions.erase(address); - emit VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); }); } else @@ -517,7 +540,7 @@ void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event) { Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::AddBreakPoint(cpuType, selectedAddress); }); } - breakpointsChanged(); + broadcastEvent(DebuggerEvents::BreakpointsChanged()); this->repaint(); } @@ -606,87 +629,111 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent* event) this->repaint(); } -void DisassemblyWidget::customMenuRequested(QPoint pos) +void DisassemblyWidget::openContextMenu(QPoint pos) { if (!cpu().isAlive()) return; - QMenu* contextMenu = new QMenu(this); + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + QAction* copy_address_action = menu->addAction(tr("Copy Address")); + connect(copy_address_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress); + + QAction* copy_instruction_hex_action = menu->addAction(tr("Copy Instruction Hex")); + connect(copy_instruction_hex_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex); + + QAction* copy_instruction_text_action = menu->addAction(tr("&Copy Instruction Text")); + copy_instruction_text_action->setShortcut(QKeySequence(Qt::Key_C)); + connect(copy_instruction_text_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText); - QAction* action = 0; - contextMenu->addAction(action = new QAction(tr("Copy Address"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress); - contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex); - contextMenu->addAction(action = new QAction(tr("&Copy Instruction Text"), this)); - action->setShortcut(QKeySequence(Qt::Key_C)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText); if (cpu().GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart)) { - contextMenu->addAction(action = new QAction(tr("Copy Function Name"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName); + QAction* copy_function_name_action = menu->addAction(tr("Copy Function Name")); + connect(copy_function_name_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName); } - contextMenu->addSeparator(); + + menu->addSeparator(); + if (AddressCanRestore(m_selectedAddressStart, m_selectedAddressEnd)) { - contextMenu->addAction(action = new QAction(tr("Restore Instruction(s)"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction); + QAction* restore_instruction_action = menu->addAction(tr("Restore Instruction(s)")); + connect(restore_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction); } - contextMenu->addAction(action = new QAction(tr("Asse&mble new Instruction(s)"), this)); - action->setShortcut(QKeySequence(Qt::Key_M)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction); - contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction); - contextMenu->addSeparator(); - contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor); - contextMenu->addAction(action = new QAction(tr("&Jump to Cursor"), this)); - action->setShortcut(QKeySequence(Qt::Key_J)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor); - contextMenu->addAction(action = new QAction(tr("Toggle &Breakpoint"), this)); - action->setShortcut(QKeySequence(Qt::Key_B)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint); - contextMenu->addAction(action = new QAction(tr("Follow Branch"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch); - contextMenu->addSeparator(); - contextMenu->addAction(action = new QAction(tr("&Go to Address"), this)); - action->setShortcut(QKeySequence(Qt::Key_G)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress); - contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this)); - connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); }); - contextMenu->addAction(action = new QAction(tr("Go to PC on Pause"), this)); - action->setCheckable(true); - action->setChecked(m_goToProgramCounterOnPause); - connect(action, &QAction::triggered, this, [this](bool value) { m_goToProgramCounterOnPause = value; }); + QAction* assemble_new_instruction = menu->addAction(tr("Asse&mble new Instruction(s)")); + assemble_new_instruction->setShortcut(QKeySequence(Qt::Key_M)); + connect(assemble_new_instruction, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction); + + QAction* nop_instruction_action = menu->addAction(tr("NOP Instruction(s)")); + connect(nop_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction); + + menu->addSeparator(); + + QAction* run_to_cursor_action = menu->addAction(tr("Run to Cursor")); + connect(run_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor); + + QAction* jump_to_cursor_action = menu->addAction(tr("&Jump to Cursor")); + jump_to_cursor_action->setShortcut(QKeySequence(Qt::Key_J)); + connect(jump_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor); + + QAction* toggle_breakpoint_action = menu->addAction(tr("Toggle &Breakpoint")); + toggle_breakpoint_action->setShortcut(QKeySequence(Qt::Key_B)); + connect(toggle_breakpoint_action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint); + + QAction* follow_branch_action = menu->addAction(tr("Follow Branch")); + connect(follow_branch_action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch); + + menu->addSeparator(); + + QAction* go_to_address_action = menu->addAction(tr("&Go to Address")); + go_to_address_action->setShortcut(QKeySequence(Qt::Key_G)); + connect(go_to_address_action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress); + + createEventActions(menu, [this]() { + DebuggerEvents::GoToAddress event; + event.address = m_selectedAddressStart; + return std::optional(event); + }); + + QAction* go_to_pc_on_pause = menu->addAction(tr("Go to PC on Pause")); + go_to_pc_on_pause->setCheckable(true); + go_to_pc_on_pause->setChecked(m_goToProgramCounterOnPause); + connect(go_to_pc_on_pause, &QAction::triggered, this, + [this](bool value) { m_goToProgramCounterOnPause = value; }); + + menu->addSeparator(); + + QAction* add_function_action = menu->addAction(tr("Add Function")); + connect(add_function_action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction); + + QAction* rename_function_action = menu->addAction(tr("Rename Function")); + connect(rename_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction); + + QAction* remove_function_action = menu->addAction(tr("Remove Function")); + menu->addAction(remove_function_action); + connect(remove_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction); - contextMenu->addSeparator(); - contextMenu->addAction(action = new QAction(tr("Add Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction); - contextMenu->addAction(action = new QAction(tr("Rename Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction); - contextMenu->addAction(action = new QAction(tr("Remove Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction); if (FunctionCanRestore(m_selectedAddressStart)) { - contextMenu->addAction(action = new QAction(tr("Restore Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction); + QAction* restore_action = menu->addAction(tr("Restore Function")); + connect(restore_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction); } else { - contextMenu->addAction(action = new QAction(tr("Stub (NOP) Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction); + QAction* stub_action = menu->addAction(tr("Stub (NOP) Function")); + connect(stub_action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction); } - contextMenu->addSeparator(); - contextMenu->addAction(action = new QAction(tr("Show &Opcode"), this)); - action->setShortcut(QKeySequence(Qt::Key_O)); - action->setCheckable(true); - action->setChecked(m_showInstructionOpcode); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextShowOpcode); + menu->addSeparator(); - contextMenu->setAttribute(Qt::WA_DeleteOnClose); - contextMenu->popup(this->mapToGlobal(pos)); + QAction* show_opcode_action = menu->addAction(tr("Show &Opcode")); + show_opcode_action->setShortcut(QKeySequence(Qt::Key_O)); + show_opcode_action->setCheckable(true); + show_opcode_action->setChecked(m_showInstructionOpcode); + connect(show_opcode_action, &QAction::triggered, this, &DisassemblyWidget::contextShowOpcode); + + menu->popup(this->mapToGlobal(pos)); } inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected) diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.h b/pcsx2-qt/Debugger/DisassemblyWidget.h index 85a51f692e..666b5668f0 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.h +++ b/pcsx2-qt/Debugger/DisassemblyWidget.h @@ -18,7 +18,7 @@ class DisassemblyWidget final : public DebuggerWidget Q_OBJECT public: - DisassemblyWidget(DebugInterface& cpu, QWidget* parent = nullptr); + DisassemblyWidget(const DebuggerWidgetParameters& parameters); ~DisassemblyWidget(); // Required for the breakpoint list (ugh wtf) @@ -32,7 +32,7 @@ protected: void keyPressEvent(QKeyEvent* event); public slots: - void customMenuRequested(QPoint pos); + void openContextMenu(QPoint pos); // Context menu actions // When called, m_selectedAddressStart will be the 'selected' instruction @@ -60,12 +60,6 @@ public slots: void gotoProgramCounterOnPause(); void gotoAddress(u32 address, bool should_set_focus); - void setDemangle(bool demangle) { m_demangleFunctions = demangle; }; -signals: - void gotoInMemory(u32 address); - void breakpointsChanged(); - void VMUpdate(); - private: Ui::DisassemblyWidget m_ui; @@ -78,7 +72,6 @@ private: std::map m_nopedInstructions; std::map> m_stubbedFunctions; - bool m_demangleFunctions = true; bool m_showInstructionOpcode = true; bool m_goToProgramCounterOnPause = true; DisassemblyManager m_disassemblyManager; diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.cpp b/pcsx2-qt/Debugger/Docking/DockLayout.cpp index 7093cd3d9d..3a1a24e142 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.cpp +++ b/pcsx2-qt/Debugger/Docking/DockLayout.cpp @@ -45,15 +45,15 @@ DockLayout::DockLayout( , m_is_default(is_default) , m_base_layout(default_layout.name) { - DebugInterface& debug_interface = DebugInterface::get(cpu); - for (size_t i = 0; i < default_layout.widgets.size(); i++) { auto iterator = DockTables::DEBUGGER_WIDGETS.find(QString::fromStdString(default_layout.widgets[i].type)); pxAssertRel(iterator != DockTables::DEBUGGER_WIDGETS.end(), "Invalid default layout."); const DockTables::DebuggerWidgetDescription& dock_description = iterator->second; - DebuggerWidget* widget = dock_description.create_widget(debug_interface); + DebuggerWidgetParameters parameters; + parameters.cpu = &DebugInterface::get(cpu); + DebuggerWidget* widget = dock_description.create_widget(parameters); m_widgets.emplace(QString::fromStdString(default_layout.widgets[i].type), widget); } @@ -91,7 +91,10 @@ DockLayout::DockLayout( if (widget_description == DockTables::DEBUGGER_WIDGETS.end()) continue; - DebuggerWidget* new_widget = widget_description->second.create_widget(DebugInterface::get(cpu)); + DebuggerWidgetParameters parameters; + parameters.cpu = &DebugInterface::get(cpu); + parameters.cpu_override = widget_to_clone->cpuOverride(); + DebuggerWidget* new_widget = widget_description->second.create_widget(parameters); m_widgets.emplace(unique_name, new_widget); } @@ -155,7 +158,8 @@ void DockLayout::freeze() pxAssert(!m_is_frozen); m_is_frozen = true; - m_toolbars = g_debugger_window->saveState(); + if (g_debugger_window) + m_toolbars = g_debugger_window->saveState(); // Store the geometry of all the dock widgets as JSON. KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow); @@ -177,6 +181,9 @@ void DockLayout::thaw() pxAssert(m_is_frozen); m_is_frozen = false; + if (!g_debugger_window) + return; + // Restore the state of the toolbars. if (m_toolbars.isEmpty()) { @@ -278,24 +285,17 @@ void DockLayout::retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_wid DebuggerWidget* widget = widget_iterator->second.get(); if (!widget) return; - - auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(widget->metaObject()->className()); - if (description_iterator == DockTables::DEBUGGER_WIDGETS.end()) - return; - - const DockTables::DebuggerWidgetDescription& description = description_iterator->second; - - QString translated_title = QCoreApplication::translate("DebuggerWidget", description.title); + ; std::optional cpu_override = widget->cpuOverride(); if (cpu_override.has_value()) { const char* cpu_name = DebugInterface::cpuName(*cpu_override); - dock_widget->setTitle(QString("%1 (%2)").arg(translated_title).arg(cpu_name)); + dock_widget->setTitle(QString("%1 (%2)").arg(widget->displayName()).arg(cpu_name)); } else { - dock_widget->setTitle(std::move(translated_title)); + dock_widget->setTitle(std::move(widget->displayName())); } } @@ -314,6 +314,11 @@ void DockLayout::dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget) dock_widget->deleteLater(); } +const std::map>& DockLayout::debuggerWidgets() +{ + return m_widgets; +} + bool DockLayout::hasDebuggerWidget(QString unique_name) { return m_widgets.find(unique_name) != m_widgets.end(); @@ -323,6 +328,9 @@ void DockLayout::toggleDebuggerWidget(QString unique_name) { pxAssert(!m_is_frozen); + if (!g_debugger_window) + return; + auto debugger_widget_iterator = m_widgets.find(unique_name); auto [controller, view] = DockUtils::dockWidgetFromName(unique_name); @@ -338,7 +346,9 @@ void DockLayout::toggleDebuggerWidget(QString unique_name) const DockTables::DebuggerWidgetDescription& description = description_iterator->second; - DebuggerWidget* widget = description.create_widget(DebugInterface::get(m_cpu)); + DebuggerWidgetParameters parameters; + parameters.cpu = &DebugInterface::get(m_cpu); + DebuggerWidget* widget = description.create_widget(parameters); m_widgets.emplace(unique_name, widget); auto view = static_cast( @@ -387,8 +397,10 @@ void DockLayout::recreateDebuggerWidget(QString unique_name) const DockTables::DebuggerWidgetDescription& description = description_iterator->second; - DebuggerWidget* new_debugger_widget = description.create_widget(DebugInterface::get(m_cpu)); - new_debugger_widget->setCpuOverride(old_debugger_widget->cpuOverride()); + DebuggerWidgetParameters parameters; + parameters.cpu = &DebugInterface::get(m_cpu); + parameters.cpu_override = old_debugger_widget->cpuOverride(); + DebuggerWidget* new_debugger_widget = description.create_widget(parameters); debugger_widget_iterator->second = new_debugger_widget; view->setWidget(new_debugger_widget); @@ -406,6 +418,9 @@ void DockLayout::deleteFile() } bool DockLayout::save(DockLayout::Index layout_index) { + if (!g_debugger_window) + return false; + if (!m_is_frozen) { m_toolbars = g_debugger_window->saveState(); @@ -469,6 +484,15 @@ bool DockLayout::save(DockLayout::Index layout_index) type.SetString(type_str, strlen(type_str), json.GetAllocator()); object.AddMember("type", type, json.GetAllocator()); + if (widget->cpuOverride().has_value()) + { + const char* cpu_name = DebugInterface::cpuName(*widget->cpuOverride()); + + rapidjson::Value target; + target.SetString(cpu_name, strlen(cpu_name)); + object.AddMember("target", target, json.GetAllocator()); + } + JsonValueWrapper wrapper(object, json.GetAllocator()); widget->toJson(wrapper); @@ -632,7 +656,20 @@ void DockLayout::load( if (description == DockTables::DEBUGGER_WIDGETS.end()) continue; - DebuggerWidget* widget = description->second.create_widget(DebugInterface::get(m_cpu)); + std::optional cpu_override; + + auto target = object.FindMember("target"); + if (target != object.MemberEnd() && target->value.IsString()) + { + for (BreakPointCpu cpu : DEBUG_CPUS) + if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0) + cpu_override = cpu; + } + + DebuggerWidgetParameters parameters; + parameters.cpu = &DebugInterface::get(m_cpu); + parameters.cpu_override = cpu_override; + DebuggerWidget* widget = description->second.create_widget(parameters); JsonValueWrapper wrapper(object, json.GetAllocator()); if (!widget->fromJson(wrapper)) @@ -662,7 +699,7 @@ void DockLayout::setupDefaultLayout() { pxAssert(!m_is_frozen); - if (m_base_layout.empty()) + if (m_base_layout.empty() || !g_debugger_window) return; const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout); diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.h b/pcsx2-qt/Debugger/Docking/DockLayout.h index 6c7c16f649..e851d0ffb7 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.h +++ b/pcsx2-qt/Debugger/Docking/DockLayout.h @@ -96,6 +96,7 @@ public: void retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget); void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget); + const std::map>& debuggerWidgets(); bool hasDebuggerWidget(QString unique_name); void toggleDebuggerWidget(QString unique_name); void recreateDebuggerWidget(QString unique_name); diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp index 75f8481f6a..91e97a95f5 100644 --- a/pcsx2-qt/Debugger/Docking/DockManager.cpp +++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp @@ -3,6 +3,7 @@ #include "DockManager.h" +#include "Debugger/DebuggerWidget.h" #include "Debugger/DebuggerWindow.h" #include "Debugger/Docking/DockTables.h" #include "Debugger/Docking/DockViews.h" @@ -78,7 +79,7 @@ bool DockManager::deleteLayout(DockLayout::Index layout_index) if (m_current_layout > layout_index && m_current_layout != DockLayout::INVALID_INDEX) m_current_layout--; - if (m_layouts.empty()) + if (m_layouts.empty() && g_debugger_window) { NoLayoutsWidget* widget = new NoLayoutsWidget; connect(widget->createDefaultLayoutsButton(), &QPushButton::clicked, this, &DockManager::resetAllLayouts); @@ -106,7 +107,8 @@ void DockManager::switchToLayout(DockLayout::Index layout_index) // Clear out the existing positions of toolbars so they don't affect where // new toolbars appear for other layouts. - g_debugger_window->clearToolBarState(); + if (g_debugger_window) + g_debugger_window->clearToolBarState(); updateToolBarLockState(); m_current_layout = layout_index; @@ -288,12 +290,12 @@ void DockManager::createToolsMenu(QMenu* menu) { menu->clear(); - if (m_current_layout == DockLayout::INVALID_INDEX) + if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window) return; for (QToolBar* widget : g_debugger_window->findChildren()) { - QAction* action = new QAction(menu); + QAction* action = menu->addAction(widget->windowTitle()); action->setText(widget->windowTitle()); action->setCheckable(true); action->setChecked(widget->isVisible()); @@ -316,7 +318,7 @@ void DockManager::createWindowsMenu(QMenu* menu) for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS) { QAction* action = new QAction(menu); - action->setText(QCoreApplication::translate("DebuggerWidget", desc.title)); + action->setText(QCoreApplication::translate("DebuggerWidget", desc.display_name)); action->setCheckable(true); action->setChecked(layout.hasDebuggerWidget(type)); connect(action, &QAction::triggered, this, [&layout, type]() { @@ -502,9 +504,10 @@ void DockManager::layoutSwitcherContextMenu(QPoint pos) if (tab_index < 0 || tab_index >= m_plus_tab_index) return; - QMenu* menu = new QMenu(tr("Layout Switcher Context Menu"), m_switcher); + QMenu* menu = new QMenu(m_switcher); + menu->setAttribute(Qt::WA_DeleteOnClose); - QAction* edit_action = new QAction(tr("Edit Layout"), menu); + QAction* edit_action = menu->addAction(tr("Edit Layout")); connect(edit_action, &QAction::triggered, [this, tab_index]() { DockLayout::Index layout_index = static_cast(tab_index); if (layout_index >= m_layouts.size()) @@ -529,9 +532,8 @@ void DockManager::layoutSwitcherContextMenu(QPoint pos) updateLayoutSwitcher(); } }); - menu->addAction(edit_action); - QAction* delete_action = new QAction(tr("Delete Layout"), menu); + QAction* delete_action = menu->addAction(tr("Delete Layout")); connect(delete_action, &QAction::triggered, [this, tab_index]() { DockLayout::Index layout_index = static_cast(tab_index); if (layout_index >= m_layouts.size()) @@ -548,7 +550,6 @@ void DockManager::layoutSwitcherContextMenu(QPoint pos) updateLayoutSwitcher(); } }); - menu->addAction(delete_action); menu->popup(m_switcher->mapToGlobal(pos)); } @@ -579,6 +580,15 @@ void DockManager::dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget) m_layouts.at(m_current_layout).dockWidgetClosed(dock_widget); } +const std::map>& DockManager::debuggerWidgets() +{ + static std::map> dummy; + if (m_current_layout == DockLayout::INVALID_INDEX) + return dummy; + + return m_layouts.at(m_current_layout).debuggerWidgets(); +} + void DockManager::recreateDebuggerWidget(QString unique_name) { if (m_current_layout == DockLayout::INVALID_INDEX) @@ -587,6 +597,22 @@ void DockManager::recreateDebuggerWidget(QString unique_name) m_layouts.at(m_current_layout).recreateDebuggerWidget(unique_name); } +void DockManager::switchToDebuggerWidget(DebuggerWidget* widget) +{ + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + for (const auto& [unique_name, test_widget] : m_layouts.at(m_current_layout).debuggerWidgets()) + { + if (widget == test_widget) + { + auto [controller, view] = DockUtils::dockWidgetFromName(unique_name); + controller->setAsCurrentTab(); + break; + } + } +} + bool DockManager::isLayoutLocked() { return m_layout_locked; @@ -611,6 +637,9 @@ void DockManager::setLayoutLocked(bool locked) void DockManager::updateToolBarLockState() { + if (!g_debugger_window) + return; + for (QToolBar* toolbar : g_debugger_window->findChildren()) toolbar->setMovable(!m_layout_locked || toolbar->isFloating()); } diff --git a/pcsx2-qt/Debugger/Docking/DockManager.h b/pcsx2-qt/Debugger/Docking/DockManager.h index a82ff4f1da..93c860833a 100644 --- a/pcsx2-qt/Debugger/Docking/DockManager.h +++ b/pcsx2-qt/Debugger/Docking/DockManager.h @@ -74,7 +74,9 @@ public: void retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget); void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget); + const std::map>& debuggerWidgets(); void recreateDebuggerWidget(QString unique_name); + void switchToDebuggerWidget(DebuggerWidget* widget); bool isLayoutLocked(); void setLayoutLocked(bool locked); diff --git a/pcsx2-qt/Debugger/Docking/DockTables.cpp b/pcsx2-qt/Debugger/Docking/DockTables.cpp index aff8e5f3f8..534b04478f 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.cpp +++ b/pcsx2-qt/Debugger/Docking/DockTables.cpp @@ -3,6 +3,7 @@ #include "DockTables.h" +#include "Debugger/DebuggerEvents.h" #include "Debugger/DisassemblyWidget.h" #include "Debugger/RegisterWidget.h" #include "Debugger/StackWidget.h" @@ -19,12 +20,16 @@ using namespace DockUtils; -#define DEBUGGER_WIDGET(type, title, preferred_location) \ +#define DEBUGGER_WIDGET(type, display_name, preferred_location) \ { \ #type, \ { \ - [](DebugInterface& cpu) -> DebuggerWidget* { return new type(cpu); }, \ - title, \ + [](const DebuggerWidgetParameters& parameters) -> DebuggerWidget* { \ + DebuggerWidget* widget = new type(parameters); \ + widget->handleEvent(DebuggerEvents::Refresh()); \ + return widget; \ + }, \ + display_name, \ preferred_location \ } \ } diff --git a/pcsx2-qt/Debugger/Docking/DockTables.h b/pcsx2-qt/Debugger/Docking/DockTables.h index cae001d8ec..55fe955351 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.h +++ b/pcsx2-qt/Debugger/Docking/DockTables.h @@ -11,17 +11,17 @@ class MD5Digest; -class DebugInterface; class DebuggerWidget; +struct DebuggerWidgetParameters; namespace DockTables { struct DebuggerWidgetDescription { - DebuggerWidget* (*create_widget)(DebugInterface& cpu); + DebuggerWidget* (*create_widget)(const DebuggerWidgetParameters& parameters); // The untranslated string displayed as the dock widget tab text. - const char* title; + const char* display_name; // This is used to determine which group dock widgets of this type are // added to when they're opened from the Windows menu. diff --git a/pcsx2-qt/Debugger/Docking/DockViews.cpp b/pcsx2-qt/Debugger/Docking/DockViews.cpp index 3a57c16ec1..5570963ef8 100644 --- a/pcsx2-qt/Debugger/Docking/DockViews.cpp +++ b/pcsx2-qt/Debugger/Docking/DockViews.cpp @@ -133,9 +133,9 @@ void DockTabBar::contextMenu(QPoint pos) for (BreakPointCpu cpu : DEBUG_CPUS) { - const char* cpu_name = DebugInterface::cpuName(cpu); const char* long_cpu_name = DebugInterface::longCpuName(cpu); - QString text = QString("%1 (%2)").arg(cpu_name).arg(long_cpu_name); + const char* cpu_name = DebugInterface::cpuName(cpu); + QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name); QAction* action = new QAction(text, menu); connect(action, &QAction::triggered, this, [tab_bar, tab_index, cpu]() { KDDockWidgets::Core::TabBar* tab_bar_controller = tab_bar->asController(); diff --git a/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp index cca7a8ab7a..c83edc6629 100644 --- a/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp +++ b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp @@ -62,9 +62,9 @@ void LayoutEditorDialog::setupInputWidgets(BreakPointCpu cpu, bool can_clone_cur for (BreakPointCpu cpu : DEBUG_CPUS) { - const char* cpu_name = DebugInterface::cpuName(cpu); const char* long_cpu_name = DebugInterface::longCpuName(cpu); - QString text = QString("%1 (%2)").arg(cpu_name).arg(long_cpu_name); + const char* cpu_name = DebugInterface::cpuName(cpu); + QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name); m_ui.cpuEditor->addItem(text, cpu); } diff --git a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp index cf629f2cca..6a36b745b8 100644 --- a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp @@ -23,8 +23,8 @@ using SearchResult = MemorySearchWidget::SearchResult; using namespace QtUtils; -MemorySearchWidget::MemorySearchWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) +MemorySearchWidget::MemorySearchWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) { m_ui.setupUi(this); this->repaint(); @@ -32,9 +32,8 @@ MemorySearchWidget::MemorySearchWidget(DebugInterface& cpu, QWidget* parent) m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); - connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { - emit switchToMemoryViewTab(); - emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16)); + connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [](QListWidgetItem* item) { + goToInMemoryView(item->text().toUInt(nullptr, 16), true); }); connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll); connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu); @@ -45,16 +44,11 @@ MemorySearchWidget::MemorySearchWidget(DebugInterface& cpu, QWidget* parent) m_resultsLoadTimer.setInterval(100); m_resultsLoadTimer.setSingleShot(true); connect(&m_resultsLoadTimer, &QTimer::timeout, this, &MemorySearchWidget::loadSearchResults); -} -void MemorySearchWidget::contextSearchResultGoToDisassembly() -{ - const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel(); - if (!selModel->hasSelection()) - return; - - u32 selectedAddress = m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt(); - emit goToAddressInDisassemblyView(selectedAddress); + receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool { + update(); + return true; + }); } void MemorySearchWidget::contextRemoveSearchResult() @@ -86,33 +80,36 @@ void MemorySearchWidget::contextCopySearchResultAddress() void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos) { - QMenu* contextMenu = new QMenu(tr("Search Results List Context Menu"), m_ui.listSearchResults); - const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel(); - const auto listSearchResults = m_ui.listSearchResults; + const QItemSelectionModel* selection_model = m_ui.listSearchResults->selectionModel(); + const QListWidget* list_search_results = m_ui.listSearchResults; - if (selModel->hasSelection()) + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + if (selection_model->hasSelection()) { - QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults); - connect(copyAddressAction, &QAction::triggered, this, &MemorySearchWidget::contextCopySearchResultAddress); - contextMenu->addAction(copyAddressAction); + connect(menu->addAction(tr("Copy Address")), &QAction::triggered, + this, &MemorySearchWidget::contextCopySearchResultAddress); - QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults); - connect(goToDisassemblyAction, &QAction::triggered, this, &MemorySearchWidget::contextSearchResultGoToDisassembly); - contextMenu->addAction(goToDisassemblyAction); - - QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults); - connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() { - u32 selectedAddress = listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt(); - emit addAddressToSavedAddressesList(selectedAddress); + createEventActions(menu, [list_search_results]() { + u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt(); + DebuggerEvents::GoToAddress event; + event.address = selected_address; + return std::optional(event); }); - contextMenu->addAction(addToSavedAddressesAction); - QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults); - connect(removeResultAction, &QAction::triggered, this, &MemorySearchWidget::contextRemoveSearchResult); - contextMenu->addAction(removeResultAction); + createEventActions(menu, [list_search_results]() { + u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt(); + DebuggerEvents::AddToSavedAddresses event; + event.address = selected_address; + return std::optional(event); + }); + + connect(menu->addAction(tr("Remove Result")), &QAction::triggered, + this, &MemorySearchWidget::contextRemoveSearchResult); } - contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos)); + menu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos)); } template @@ -504,9 +501,9 @@ void MemorySearchWidget::onSearchButtonClicked() const bool isFilterSearch = sender() == m_ui.btnFilterSearch; unsigned long long value; - if(searchComparison != SearchComparison::UnknownValue) + if (searchComparison != SearchComparison::UnknownValue) { - if(doesSearchComparisonTakeInput(searchComparison)) + if (doesSearchComparisonTakeInput(searchComparison)) { switch (searchType) { @@ -559,17 +556,27 @@ void MemorySearchWidget::onSearchButtonClicked() } } - if (!isFilterSearch && (searchComparison == SearchComparison::Changed || searchComparison == SearchComparison::ChangedBy - || searchComparison == SearchComparison::Decreased || searchComparison == SearchComparison::DecreasedBy - || searchComparison == SearchComparison::Increased || searchComparison == SearchComparison::IncreasedBy - || searchComparison == SearchComparison::NotChanged)) + if (!isFilterSearch && + (searchComparison == SearchComparison::Changed || + searchComparison == SearchComparison::ChangedBy || + searchComparison == SearchComparison::Decreased || + searchComparison == SearchComparison::DecreasedBy || + searchComparison == SearchComparison::Increased || + searchComparison == SearchComparison::IncreasedBy || + searchComparison == SearchComparison::NotChanged)) { QMessageBox::critical(this, tr("Debugger"), tr("This search comparison can only be used with filter searches.")); return; } } - if (!isFilterSearch && (searchComparison == SearchComparison::Changed || searchComparison == SearchComparison::ChangedBy || searchComparison == SearchComparison::Decreased || searchComparison == SearchComparison::DecreasedBy || searchComparison == SearchComparison::Increased || searchComparison == SearchComparison::IncreasedBy || searchComparison == SearchComparison::NotChanged)) + if (!isFilterSearch && (searchComparison == SearchComparison::Changed || + searchComparison == SearchComparison::ChangedBy || + searchComparison == SearchComparison::Decreased || + searchComparison == SearchComparison::DecreasedBy || + searchComparison == SearchComparison::Increased || + searchComparison == SearchComparison::IncreasedBy || + searchComparison == SearchComparison::NotChanged)) { QMessageBox::critical(this, tr("Debugger"), tr("This search comparison can only be used with filter searches.")); return; @@ -649,7 +656,8 @@ SearchComparison MemorySearchWidget::getCurrentSearchComparison() bool MemorySearchWidget::doesSearchComparisonTakeInput(const SearchComparison comparison) { - switch (comparison) { + switch (comparison) + { case SearchComparison::Equals: case SearchComparison::NotEquals: case SearchComparison::GreaterThan: @@ -736,8 +744,8 @@ std::vector MemorySearchWidget::getValidSearchComparisonsForSt comparisons.push_back(SearchComparison::ChangedBy); comparisons.push_back(SearchComparison::NotChanged); } - - if(!hasResults) + + if (!hasResults) { comparisons.push_back(SearchComparison::UnknownValue); } diff --git a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h index 38bf6ba6db..84e2fe3fd7 100644 --- a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h +++ b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h @@ -18,7 +18,7 @@ class MemorySearchWidget final : public DebuggerWidget Q_OBJECT public: - MemorySearchWidget(DebugInterface& cpu, QWidget* parent = nullptr); + MemorySearchWidget(const DebuggerWidgetParameters& parameters); ~MemorySearchWidget() = default; enum class SearchType @@ -129,17 +129,10 @@ public slots: void onSearchTypeChanged(int newIndex); void onSearchComparisonChanged(int newIndex); void loadSearchResults(); - void contextSearchResultGoToDisassembly(); void contextRemoveSearchResult(); void contextCopySearchResultAddress(); void onListSearchResultsContextMenu(QPoint pos); -signals: - void addAddressToSavedAddressesList(u32 address); - void goToAddressInDisassemblyView(u32 address); - void goToAddressInMemoryView(u32 address); - void switchToMemoryViewTab(); - private: std::vector m_searchResults; SearchComparisonLabelMap m_searchComparisonLabelMap; diff --git a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp index 46f3f048df..5469444747 100644 --- a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp @@ -451,17 +451,38 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu) /* MemoryViewWidget */ -MemoryViewWidget::MemoryViewWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu) +MemoryViewWidget::MemoryViewWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) , m_table(this) { ui.setupUi(this); - this->setFocusPolicy(Qt::FocusPolicy::ClickFocus); - connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::customMenuRequested); + + setFocusPolicy(Qt::FocusPolicy::ClickFocus); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::openContextMenu); m_table.UpdateStartAddress(0x480000); applyMonospaceFont(); + + receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool { + update(); + return true; + }); + + receiveEvent([this](const DebuggerEvents::GoToAddress& event) -> bool { + if (event.filter != DebuggerEvents::GoToAddress::NONE && + event.filter != DebuggerEvents::GoToAddress::MEMORY_VIEW) + return false; + + gotoAddress(event.address); + + if (event.switch_to_tab) + switchToThisTab(); + + return true; + }); } MemoryViewWidget::~MemoryViewWidget() = default; @@ -487,86 +508,74 @@ void MemoryViewWidget::mousePressEvent(QMouseEvent* event) repaint(); } -void MemoryViewWidget::customMenuRequested(QPoint pos) +void MemoryViewWidget::openContextMenu(QPoint pos) { if (!cpu().isAlive()) return; - if (!m_contextMenu) - { - m_contextMenu = new QMenu(this); + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); - QAction* action = new QAction(tr("Copy Address")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper()); }); + QAction* copy_action = menu->addAction(tr("Copy Address")); + connect(copy_action, &QAction::triggered, this, [this]() { + QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper()); + }); - action = new QAction(tr("Go to in Disassembly")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { emit gotoInDisasm(m_table.selectedAddress); }); + createEventActions(menu, [this]() { + DebuggerEvents::GoToAddress event; + event.address = m_table.selectedAddress; + return std::optional(event); + }); - action = new QAction(tr("Go to address")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { contextGoToAddress(); }); + QAction* go_to_address_action = menu->addAction(tr("Go to address")); + connect(go_to_address_action, &QAction::triggered, this, [this]() { contextGoToAddress(); }); - m_contextMenu->addSeparator(); + menu->addSeparator(); - m_actionLittleEndian = new QAction(tr("Show as Little Endian")); - m_actionLittleEndian->setCheckable(true); - m_contextMenu->addAction(m_actionLittleEndian); - connect(m_actionLittleEndian, &QAction::triggered, this, [this]() { m_table.SetLittleEndian(m_actionLittleEndian->isChecked()); }); + QAction* endian_action = menu->addAction(tr("Show as Little Endian")); + endian_action->setCheckable(true); + endian_action->setChecked(m_table.GetLittleEndian()); + connect(endian_action, &QAction::triggered, this, [this, endian_action]() { + m_table.SetLittleEndian(endian_action->isChecked()); + }); - // View Types - m_actionBYTE = new QAction(tr("Show as 1 byte")); - m_actionBYTE->setCheckable(true); - m_contextMenu->addAction(m_actionBYTE); - connect(m_actionBYTE, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); }); + const MemoryViewType current_view_type = m_table.GetViewType(); - m_actionBYTEHW = new QAction(tr("Show as 2 bytes")); - m_actionBYTEHW->setCheckable(true); - m_contextMenu->addAction(m_actionBYTEHW); - connect(m_actionBYTEHW, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); }); + // View Types + QAction* byte_action = menu->addAction(tr("Show as 1 byte")); + byte_action->setCheckable(true); + byte_action->setChecked(current_view_type == MemoryViewType::BYTE); + connect(byte_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); }); - m_actionWORD = new QAction(tr("Show as 4 bytes")); - m_actionWORD->setCheckable(true); - m_contextMenu->addAction(m_actionWORD); - connect(m_actionWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); }); + QAction* bytehw_action = menu->addAction(tr("Show as 2 bytes")); + bytehw_action->setCheckable(true); + bytehw_action->setChecked(current_view_type == MemoryViewType::BYTEHW); + connect(bytehw_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); }); - m_actionDWORD = new QAction(tr("Show as 8 bytes")); - m_actionDWORD->setCheckable(true); - m_contextMenu->addAction(m_actionDWORD); - connect(m_actionDWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); }); + QAction* word_action = menu->addAction(tr("Show as 4 bytes")); + word_action->setCheckable(true); + word_action->setChecked(current_view_type == MemoryViewType::WORD); + connect(word_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); }); - m_contextMenu->addSeparator(); + QAction* dword_action = menu->addAction(tr("Show as 8 bytes")); + dword_action->setCheckable(true); + dword_action->setChecked(current_view_type == MemoryViewType::DWORD); + connect(dword_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); }); - action = new QAction((tr("Add to Saved Memory Addresses"))); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { emit addToSavedAddresses(m_table.selectedAddress); }); + menu->addSeparator(); - action = new QAction(tr("Copy Byte")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { contextCopyByte(); }); + createEventActions(menu, [this]() { + DebuggerEvents::AddToSavedAddresses event; + event.address = m_table.selectedAddress; + return std::optional(event); + }); - action = new QAction(tr("Copy Segment")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { contextCopySegment(); }); + connect(menu->addAction(tr("Copy Byte")), &QAction::triggered, this, &MemoryViewWidget::contextCopyByte); + connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &MemoryViewWidget::contextCopySegment); + connect(menu->addAction(tr("Copy Character")), &QAction::triggered, this, &MemoryViewWidget::contextCopyCharacter); + connect(menu->addAction(tr("Paste")), &QAction::triggered, this, &MemoryViewWidget::contextPaste); - action = new QAction(tr("Copy Character")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { contextCopyCharacter(); }); - - action = new QAction(tr("Paste")); - m_contextMenu->addAction(action); - connect(action, &QAction::triggered, this, [this]() { contextPaste(); }); - } - m_actionLittleEndian->setChecked(m_table.GetLittleEndian()); - - const MemoryViewType currentViewType = m_table.GetViewType(); - - m_actionBYTE->setChecked(currentViewType == MemoryViewType::BYTE); - m_actionBYTEHW->setChecked(currentViewType == MemoryViewType::BYTEHW); - m_actionWORD->setChecked(currentViewType == MemoryViewType::WORD); - m_actionDWORD->setChecked(currentViewType == MemoryViewType::DWORD); - m_contextMenu->popup(this->mapToGlobal(pos)); + menu->popup(this->mapToGlobal(pos)); this->repaint(); return; @@ -647,7 +656,7 @@ void MemoryViewWidget::keyPressEvent(QKeyEvent* event) } } this->repaint(); - VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); } void MemoryViewWidget::gotoAddress(u32 address) diff --git a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h index ed5831e5d7..65dcdcc56a 100644 --- a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h @@ -109,7 +109,7 @@ class MemoryViewWidget final : public DebuggerWidget Q_OBJECT public: - MemoryViewWidget(DebugInterface& cpu, QWidget* parent = nullptr); + MemoryViewWidget(const DebuggerWidgetParameters& parameters); ~MemoryViewWidget(); protected: @@ -120,7 +120,7 @@ protected: void keyPressEvent(QKeyEvent* event); public slots: - void customMenuRequested(QPoint pos); + void openContextMenu(QPoint pos); void contextGoToAddress(); void contextCopyByte(); @@ -129,20 +129,8 @@ public slots: void contextPaste(); void gotoAddress(u32 address); -signals: - void gotoInDisasm(u32 address, bool should_set_focus = true); - void addToSavedAddresses(u32 address); - void VMUpdate(); - private: Ui::MemoryViewWidget ui; - QMenu* m_contextMenu = 0x0; - QAction* m_actionLittleEndian; - QAction* m_actionBYTE; - QAction* m_actionBYTEHW; - QAction* m_actionWORD; - QAction* m_actionDWORD; - MemoryViewTable m_table; }; diff --git a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui index 54103700aa..c9d13501bb 100644 --- a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui @@ -10,9 +10,6 @@ 300 - - Qt::CustomContextMenu - Memory diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp index 356c53f5c3..a9e5e2cba5 100644 --- a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp @@ -6,107 +6,116 @@ #include "QtUtils.h" #include "Debugger/DebuggerSettingsManager.h" -#include -#include +#include +#include -SavedAddressesWidget::SavedAddressesWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) - , m_model(cpu) +SavedAddressesWidget::SavedAddressesWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) + , m_model(new SavedAddressesModel(cpu(), this)) { - //m_ui.savedAddressesList->setModel(&m_model); - //m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu); - //connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested, this, &CpuWidget::onSavedAddressesListContextMenu); - //for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes) - //{ - // m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode); - //} - //QTableView* savedAddressesTableView = m_ui.savedAddressesList; - //connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) { - // savedAddressesTableView->resizeColumnToContents(topLeft.column()); - //}); + m_ui.setupUi(this); + + m_ui.savedAddressesList->setModel(m_model); + + m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu); + connect( + m_ui.savedAddressesList, + &QTableView::customContextMenuRequested, + this, + &SavedAddressesWidget::openContextMenu); + + connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) { + if (title.isEmpty()) + return; + + if (m_model->rowCount() == 0) + DebuggerSettingsManager::loadGameSettings(m_model); + }); + + DebuggerSettingsManager::loadGameSettings(m_model); + + + + for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes) + { + m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode); + } + QTableView* savedAddressesTableView = m_ui.savedAddressesList; + connect(m_model, &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) { + savedAddressesTableView->resizeColumnToContents(topLeft.column()); + }); + + receiveEvent([this](const DebuggerEvents::AddToSavedAddresses& event) { + addAddress(event.address); + + if (event.switch_to_tab) + switchToThisTab(); + + return true; + }); } -void SavedAddressesWidget::onContextMenu(QPoint pos) +void SavedAddressesWidget::openContextMenu(QPoint pos) { - QMenu* contextMenu = new QMenu("Saved Addresses List Context Menu", m_ui.savedAddressesList); + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); - QAction* newAction = new QAction(tr("New"), m_ui.savedAddressesList); - connect(newAction, &QAction::triggered, this, &SavedAddressesWidget::contextNew); - contextMenu->addAction(newAction); + QAction* new_action = menu->addAction(tr("New")); + connect(new_action, &QAction::triggered, this, &SavedAddressesWidget::contextNew); - const QModelIndex indexAtPos = m_ui.savedAddressesList->indexAt(pos); - const bool isIndexValid = indexAtPos.isValid(); + const QModelIndex index_at_pos = m_ui.savedAddressesList->indexAt(pos); + const bool is_index_valid = index_at_pos.isValid(); + bool is_cpu_alive = cpu().isAlive(); - if (isIndexValid) - { - if (cpu().isAlive()) - { - QAction* goToAddressMemViewAction = new QAction(tr("Go to in Memory View"), m_ui.savedAddressesList); - connect(goToAddressMemViewAction, &QAction::triggered, this, [this, indexAtPos]() { - const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); - //m_ui.memoryviewWidget->gotoAddress(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); - //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); - not_yet_implemented(); - }); - contextMenu->addAction(goToAddressMemViewAction); + std::vector go_to_actions = createEventActions( + menu, [this, index_at_pos]() { + const QModelIndex rowAddressIndex = m_model->index(index_at_pos.row(), 0, QModelIndex()); - QAction* goToAddressDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.savedAddressesList); - connect(goToAddressDisassemblyAction, &QAction::triggered, this, [this, indexAtPos]() { - const QModelIndex rowAddressIndex = - m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); - //m_ui.disassemblyWidget->gotoAddressAndSetFocus( - // m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); - not_yet_implemented(); - }); - contextMenu->addAction(goToAddressDisassemblyAction); - } - - QAction* copyAction = new QAction(indexAtPos.column() == 0 ? tr("Copy Address") : tr("Copy Text"), m_ui.savedAddressesList); - connect(copyAction, &QAction::triggered, [this, indexAtPos]() { - QGuiApplication::clipboard()->setText( - m_ui.savedAddressesList->model()->data(indexAtPos, Qt::DisplayRole).toString()); + DebuggerEvents::GoToAddress event; + event.address = m_model->data(rowAddressIndex, Qt::UserRole).toUInt(); + return std::optional(event); }); - contextMenu->addAction(copyAction); - } - if (m_ui.savedAddressesList->model()->rowCount() > 0) + for (QAction* go_to_action : go_to_actions) + go_to_action->setEnabled(is_index_valid); + + QAction* copy_action = menu->addAction(index_at_pos.column() == 0 ? tr("Copy Address") : tr("Copy Text")); + copy_action->setEnabled(is_index_valid); + connect(copy_action, &QAction::triggered, [this, index_at_pos]() { + QGuiApplication::clipboard()->setText( + m_model->data(index_at_pos, Qt::DisplayRole).toString()); + }); + + if (m_model->rowCount() > 0) { - QAction* actionExportCSV = new QAction(tr("Copy all as CSV"), m_ui.savedAddressesList); - connect(actionExportCSV, &QAction::triggered, [this]() { + QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV")); + connect(copy_all_as_csv_action, &QAction::triggered, [this]() { QGuiApplication::clipboard()->setText( QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true)); }); - contextMenu->addAction(actionExportCSV); } - QAction* actionImportCSV = new QAction(tr("Paste from CSV"), m_ui.savedAddressesList); - connect(actionImportCSV, &QAction::triggered, this, &SavedAddressesWidget::contextPasteCSV); - contextMenu->addAction(actionImportCSV); + QAction* paste_from_csv_action = menu->addAction(tr("Paste from CSV")); + connect(paste_from_csv_action, &QAction::triggered, this, &SavedAddressesWidget::contextPasteCSV); - if (cpu().isAlive()) - { - QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.savedAddressesList); - connect(actionLoad, &QAction::triggered, [this]() { - m_model.clear(); - DebuggerSettingsManager::loadGameSettings(&m_model); - }); - contextMenu->addAction(actionLoad); + QAction* load_action = menu->addAction(tr("Load from Settings")); + load_action->setEnabled(is_cpu_alive); + connect(load_action, &QAction::triggered, [this]() { + m_model->clear(); + DebuggerSettingsManager::loadGameSettings(m_model); + }); - QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.savedAddressesList); - connect(actionSave, &QAction::triggered, this, &SavedAddressesWidget::saveToDebuggerSettings); - contextMenu->addAction(actionSave); - } + QAction* save_action = menu->addAction(tr("Save to Settings")); + save_action->setEnabled(is_cpu_alive); + connect(save_action, &QAction::triggered, this, &SavedAddressesWidget::saveToDebuggerSettings); - if (isIndexValid) - { - QAction* deleteAction = new QAction(tr("Delete"), m_ui.savedAddressesList); - connect(deleteAction, &QAction::triggered, this, [this, indexAtPos]() { - m_ui.savedAddressesList->model()->removeRows(indexAtPos.row(), 1); - }); - contextMenu->addAction(deleteAction); - } + QAction* delete_action = menu->addAction(tr("Delete")); + connect(delete_action, &QAction::triggered, this, [this, index_at_pos]() { + m_model->removeRows(index_at_pos.row(), 1); + }); + delete_action->setEnabled(is_index_valid); - contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos)); + menu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos)); } void SavedAddressesWidget::contextPasteCSV() @@ -121,38 +130,36 @@ void SavedAddressesWidget::contextPasteCSV() // In order to handle text with commas in them we must wrap values in quotes to mark // where a value starts and end so that text commas aren't identified as delimiters. // So matches each quote pair, parse it out, and removes the quotes to get the value. - QRegularExpression eachQuotePair(R"("([^"]|\\.)*")"); - QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line); + QRegularExpression each_quote_pair(R"("([^"]|\\.)*")"); + QRegularExpressionMatchIterator it = each_quote_pair.globalMatch(line); while (it.hasNext()) { QRegularExpressionMatch match = it.next(); - QString matchedValue = match.captured(0); - fields << matchedValue.mid(1, matchedValue.length() - 2); + QString matched_value = match.captured(0); + fields << matched_value.mid(1, matched_value.length() - 2); } - m_model.loadSavedAddressFromFieldList(fields); + m_model->loadSavedAddressFromFieldList(fields); } } void SavedAddressesWidget::contextNew() { qobject_cast(m_ui.savedAddressesList->model())->addRow(); - const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); - m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 0)); + const u32 row_count = m_model->rowCount(); + m_ui.savedAddressesList->edit(m_model->index(row_count - 1, 0)); } void SavedAddressesWidget::addAddress(u32 address) { qobject_cast(m_ui.savedAddressesList->model())->addRow(); - const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); - const QModelIndex addressIndex = m_ui.savedAddressesList->model()->index(rowCount - 1, 0); - //m_ui.tabWidget->setCurrentWidget(m_ui.tab_savedaddresses); - not_yet_implemented(); - m_ui.savedAddressesList->model()->setData(addressIndex, address, Qt::UserRole); - m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 1)); + const u32 row_count = m_model->rowCount(); + const QModelIndex address_index = m_model->index(row_count - 1, 0); + m_model->setData(address_index, address, Qt::UserRole); + m_ui.savedAddressesList->edit(m_model->index(row_count - 1, 1)); } void SavedAddressesWidget::saveToDebuggerSettings() { - DebuggerSettingsManager::saveGameSettings(&m_model); + DebuggerSettingsManager::saveGameSettings(m_model); } diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h index ed73046c65..96ed901859 100644 --- a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h @@ -14,9 +14,9 @@ class SavedAddressesWidget : public DebuggerWidget Q_OBJECT public: - SavedAddressesWidget(DebugInterface& cpu, QWidget* parent = nullptr); + SavedAddressesWidget(const DebuggerWidgetParameters& parameters); - void onContextMenu(QPoint pos); + void openContextMenu(QPoint pos); void contextPasteCSV(); void contextNew(); void addAddress(u32 address); @@ -25,5 +25,5 @@ public: private: Ui::SavedAddressesWidget m_ui; - SavedAddressesModel m_model; + SavedAddressesModel* m_model; }; diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui index a8d756f623..51d644d789 100644 --- a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui @@ -30,11 +30,7 @@ 0 - - - Qt::CustomContextMenu - - + diff --git a/pcsx2-qt/Debugger/RegisterWidget.cpp b/pcsx2-qt/Debugger/RegisterWidget.cpp index 49e4fbf65c..c4f9157be1 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.cpp +++ b/pcsx2-qt/Debugger/RegisterWidget.cpp @@ -13,15 +13,14 @@ #include #include -#include #include #define CAT_SHOW_FLOAT (categoryIndex == EECAT_FPR && m_showFPRFloat) || (categoryIndex == EECAT_VU0F && m_showVU0FFloat) using namespace QtUtils; -RegisterWidget::RegisterWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu) +RegisterWidget::RegisterWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) { this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); @@ -31,14 +30,19 @@ RegisterWidget::RegisterWidget(DebugInterface& cpu, QWidget* parent) connect(this, &RegisterWidget::customContextMenuRequested, this, &RegisterWidget::customMenuRequested); connect(ui.registerTabs, &QTabBar::currentChanged, this, &RegisterWidget::tabCurrentChanged); - for (int i = 0; i < cpu.getRegisterCategoryCount(); i++) + for (int i = 0; i < cpu().getRegisterCategoryCount(); i++) { - ui.registerTabs->addTab(cpu.getRegisterCategoryName(i)); + ui.registerTabs->addTab(cpu().getRegisterCategoryName(i)); } connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); }); applyMonospaceFont(); + + receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool { + update(); + return true; + }); } RegisterWidget::~RegisterWidget() @@ -218,72 +222,66 @@ void RegisterWidget::customMenuRequested(QPoint pos) if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative) return; - // Unlike the disassembly widget, we need to create a new context menu every time - // we show it. Because some register groups are special - if (!m_contextMenu) - m_contextMenu = new QMenu(this); - else - m_contextMenu->clear(); + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); const int categoryIndex = ui.registerTabs->currentIndex(); - QAction* action = 0; - if (categoryIndex == EECAT_FPR) { - m_contextMenu->addAction(action = new QAction(m_showFPRFloat ? tr("View as hex") : tr("View as float"))); + QAction* action = menu->addAction(tr("Show as Float")); + action->setCheckable(true); + action->setChecked(m_showFPRFloat); connect(action, &QAction::triggered, this, [this]() { m_showFPRFloat = !m_showFPRFloat; }); - m_contextMenu->addSeparator(); + + menu->addSeparator(); } if (categoryIndex == EECAT_VU0F) { - m_contextMenu->addAction(action = new QAction(m_showVU0FFloat ? tr("View as hex") : tr("View as float"))); + QAction* action = menu->addAction(tr("Show as Float")); + action->setCheckable(true); + action->setChecked(m_showVU0FFloat); connect(action, &QAction::triggered, this, [this]() { m_showVU0FFloat = !m_showVU0FFloat; }); - m_contextMenu->addSeparator(); + + menu->addSeparator(); } if (cpu().getRegisterSize(categoryIndex) == 128) { - m_contextMenu->addAction(action = new QAction(tr("Copy Top Half"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyTop); - m_contextMenu->addAction(action = new QAction(tr("Copy Bottom Half"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyBottom); - m_contextMenu->addAction(action = new QAction(tr("Copy Segment"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextCopySegment); + connect(menu->addAction(tr("Copy Top Half")), &QAction::triggered, this, &RegisterWidget::contextCopyTop); + connect(menu->addAction(tr("Copy Bottom Half")), &QAction::triggered, this, &RegisterWidget::contextCopyBottom); + connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &RegisterWidget::contextCopySegment); } else { - m_contextMenu->addAction(action = new QAction(tr("Copy Value"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyValue); + connect(menu->addAction(tr("Copy Value")), &QAction::triggered, this, &RegisterWidget::contextCopyValue); } - m_contextMenu->addSeparator(); + menu->addSeparator(); if (cpu().getRegisterSize(categoryIndex) == 128) { - m_contextMenu->addAction(action = new QAction(tr("Change Top Half"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeTop); - m_contextMenu->addAction(action = new QAction(tr("Change Bottom Half"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeBottom); - m_contextMenu->addAction(action = new QAction(tr("Change Segment"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeSegment); + connect(menu->addAction(tr("Change Top Half")), &QAction::triggered, + this, &RegisterWidget::contextChangeTop); + connect(menu->addAction(tr("Change Bottom Half")), &QAction::triggered, + this, &RegisterWidget::contextChangeBottom); + connect(menu->addAction(tr("Change Segment")), &QAction::triggered, + this, &RegisterWidget::contextChangeSegment); } else { - m_contextMenu->addAction(action = new QAction(tr("Change Value"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeValue); + connect(menu->addAction(tr("Change Value")), &QAction::triggered, + this, &RegisterWidget::contextChangeValue); } - m_contextMenu->addSeparator(); + menu->addSeparator(); - m_contextMenu->addAction(action = new QAction(tr("Go to in Disassembly"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm); + createEventActions(menu, [this]() { + return contextCreateGotoEvent(); + }); - m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this)); - connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoMemory); - - m_contextMenu->popup(this->mapToGlobal(pos)); + menu->popup(this->mapToGlobal(pos)); } @@ -371,7 +369,7 @@ void RegisterWidget::contextChangeValue() if (contextFetchNewValue(newVal, cpu().getRegister(categoryIndex, m_selectedRow).lo)) { cpu().setRegister(categoryIndex, m_selectedRow, u128::From64(newVal)); - VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); } } @@ -383,7 +381,7 @@ void RegisterWidget::contextChangeTop() { oldVal.hi = newVal; cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); - VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); } } @@ -395,7 +393,7 @@ void RegisterWidget::contextChangeBottom() { oldVal.lo = newVal; cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); - VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); } } @@ -407,11 +405,11 @@ void RegisterWidget::contextChangeSegment() { oldVal._u32[3 - m_selected128Field] = (u32)newVal; cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); - VMUpdate(); + DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate()); } } -void RegisterWidget::contextGotoDisasm() +std::optional RegisterWidget::contextCreateGotoEvent() { const int categoryIndex = ui.registerTabs->currentIndex(); u128 regVal = cpu().getRegister(categoryIndex, m_selectedRow); @@ -422,22 +420,16 @@ void RegisterWidget::contextGotoDisasm() else addr = regVal._u32[0]; - if (cpu().isValidAddress(addr)) - gotoInDisasm(addr); - else - QMessageBox::warning(this, tr("Invalid target address"), ("This register holds an invalid address.")); -} - -void RegisterWidget::contextGotoMemory() -{ - const int categoryIndex = ui.registerTabs->currentIndex(); - u128 regVal = cpu().getRegister(categoryIndex, m_selectedRow); - u32 addr = 0; - - if (cpu().getRegisterSize(categoryIndex) == 128) - addr = regVal._u32[3 - m_selected128Field]; - else - addr = regVal._u32[0]; - - gotoInMemory(addr); + if (!cpu().isValidAddress(addr)) + { + QMessageBox::warning( + this, + tr("Invalid target address"), + tr("This register holds an invalid address.")); + return std::nullopt; + } + + DebuggerEvents::GoToAddress event; + event.address = addr; + return event; } diff --git a/pcsx2-qt/Debugger/RegisterWidget.h b/pcsx2-qt/Debugger/RegisterWidget.h index f114cca55e..c545293a85 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.h +++ b/pcsx2-qt/Debugger/RegisterWidget.h @@ -19,7 +19,7 @@ class RegisterWidget final : public DebuggerWidget Q_OBJECT public: - RegisterWidget(DebugInterface& cpu, QWidget* parent = nullptr); + RegisterWidget(const DebuggerWidgetParameters& parameters); ~RegisterWidget(); protected: @@ -39,21 +39,13 @@ public slots: void contextChangeBottom(); void contextChangeSegment(); - void contextGotoDisasm(); - void contextGotoMemory(); + std::optional contextCreateGotoEvent(); void tabCurrentChanged(int cur); -signals: - void gotoInDisasm(u32 address, bool should_set_focus = true); - void gotoInMemory(u32 address); - void VMUpdate(); - private: Ui::RegisterWidget ui; - QMenu* m_contextMenu = 0x0; - // Returns true on success bool contextFetchNewValue(u64& out, u64 currentValue, bool segment = false); @@ -61,14 +53,14 @@ private: // because we share a widget QPoint m_renderStart; - s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc - s32 m_rowEnd; // Index, what register is the last one drawn - s32 m_rowHeight; // The height of each register row + s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc + s32 m_rowEnd; // Index, what register is the last one drawn + s32 m_rowHeight; // The height of each register row // Used for mouse clicks - s32 m_fieldStartX[4]; // Where the register segments start - s32 m_fieldWidth; // How wide the register segments are + s32 m_fieldStartX[4]; // Where the register segments start + s32 m_fieldWidth; // How wide the register segments are - s32 m_selectedRow = 0; // Index + s32 m_selectedRow = 0; // Index s32 m_selected128Field = 0; // Values are from 0 to 3 // TODO: Save this configuration ?? diff --git a/pcsx2-qt/Debugger/StackModel.cpp b/pcsx2-qt/Debugger/StackModel.cpp index eaec381a2f..2c2dd4dd05 100644 --- a/pcsx2-qt/Debugger/StackModel.cpp +++ b/pcsx2-qt/Debugger/StackModel.cpp @@ -24,16 +24,20 @@ int StackModel::columnCount(const QModelIndex&) const QVariant StackModel::data(const QModelIndex& index, int role) const { + size_t row = static_cast(index.row()); + if (row >= m_stackFrames.size()) + return QVariant(); + + const auto& stackFrame = m_stackFrames[row]; + if (role == Qt::DisplayRole) { - const auto& stackFrame = m_stackFrames.at(index.row()); - switch (index.column()) { case StackModel::ENTRY: return QtUtils::FilledQStringFromValue(stackFrame.entry, 16); case StackModel::ENTRY_LABEL: - return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name); + return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name); case StackModel::PC: return QtUtils::FilledQStringFromValue(stackFrame.pc, 16); case StackModel::PC_OPCODE: @@ -63,6 +67,7 @@ QVariant StackModel::data(const QModelIndex& index, int role) const return stackFrame.stackSize; } } + return QVariant(); } diff --git a/pcsx2-qt/Debugger/StackWidget.cpp b/pcsx2-qt/Debugger/StackWidget.cpp index 805376ead7..d68433bef9 100644 --- a/pcsx2-qt/Debugger/StackWidget.cpp +++ b/pcsx2-qt/Debugger/StackWidget.cpp @@ -5,53 +5,57 @@ #include "QtUtils.h" -#include -#include +#include +#include -StackWidget::StackWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) - , m_model(cpu) +StackWidget::StackWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) + , m_model(new StackModel(cpu())) { m_ui.setupUi(this); - connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &StackWidget::onContextMenu); + m_ui.stackList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &StackWidget::openContextMenu); connect(m_ui.stackList, &QTableView::doubleClicked, this, &StackWidget::onDoubleClick); - m_ui.stackList->setModel(&m_model); + m_ui.stackList->setModel(m_model); for (std::size_t i = 0; auto mode : StackModel::HeaderResizeModes) { m_ui.stackList->horizontalHeader()->setSectionResizeMode(i, mode); i++; } + + receiveEvent([this](const DebuggerEvents::VMUpdate& event) -> bool { + m_model->refreshData(); + return true; + }); } -void StackWidget::onContextMenu(QPoint pos) +void StackWidget::openContextMenu(QPoint pos) { if (!m_ui.stackList->selectionModel()->hasSelection()) return; - QMenu* contextMenu = new QMenu(tr("Stack List Context Menu"), m_ui.stackList); + QMenu* menu = new QMenu(m_ui.stackList); + menu->setAttribute(Qt::WA_DeleteOnClose); - QAction* actionCopy = new QAction(tr("Copy"), m_ui.stackList); - connect(actionCopy, &QAction::triggered, [this]() { - const auto* selModel = m_ui.stackList->selectionModel(); - - if (!selModel->hasSelection()) + QAction* copy_action = menu->addAction(tr("Copy")); + connect(copy_action, &QAction::triggered, [this]() { + const auto* selection_model = m_ui.stackList->selectionModel(); + if (!selection_model->hasSelection()) return; - QGuiApplication::clipboard()->setText(m_ui.stackList->model()->data(selModel->currentIndex()).toString()); + QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString()); }); - contextMenu->addAction(actionCopy); - contextMenu->addSeparator(); + menu->addSeparator(); - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.stackList); - connect(actionExport, &QAction::triggered, [this]() { + QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV")); + connect(copy_all_as_csv_action, &QAction::triggered, [this]() { QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model())); }); - contextMenu->addAction(actionExport); - contextMenu->popup(m_ui.stackList->viewport()->mapToGlobal(pos)); + menu->popup(m_ui.stackList->viewport()->mapToGlobal(pos)); } void StackWidget::onDoubleClick(const QModelIndex& index) @@ -60,17 +64,21 @@ void StackWidget::onDoubleClick(const QModelIndex& index) { case StackModel::StackModel::ENTRY: case StackModel::StackModel::ENTRY_LABEL: - //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::ENTRY), Qt::UserRole).toUInt()); - not_yet_implemented(); + { + QModelIndex entry_index = m_model->index(index.row(), StackModel::StackColumns::ENTRY); + goToInDisassembler(m_model->data(entry_index, Qt::UserRole).toUInt(), true); break; + } case StackModel::StackModel::SP: - //m_ui.memoryviewWidget->gotoAddress(m_ui.stackList->model()->data(index, Qt::UserRole).toUInt()); - //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); - not_yet_implemented(); + { + goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true); break; + } default: // Default to PC - //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::PC), Qt::UserRole).toUInt()); - not_yet_implemented(); + { + QModelIndex pc_index = m_model->index(index.row(), StackModel::StackColumns::PC); + goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true); break; + } } } diff --git a/pcsx2-qt/Debugger/StackWidget.h b/pcsx2-qt/Debugger/StackWidget.h index 32b5907da2..112bcfd37c 100644 --- a/pcsx2-qt/Debugger/StackWidget.h +++ b/pcsx2-qt/Debugger/StackWidget.h @@ -14,13 +14,13 @@ class StackWidget final : public DebuggerWidget Q_OBJECT public: - StackWidget(DebugInterface& cpu, QWidget* parent = nullptr); + StackWidget(const DebuggerWidgetParameters& parameters); - void onContextMenu(QPoint pos); + void openContextMenu(QPoint pos); void onDoubleClick(const QModelIndex& index); private: Ui::StackWidget m_ui; - StackModel m_model; + StackModel* m_model; }; diff --git a/pcsx2-qt/Debugger/StackWidget.ui b/pcsx2-qt/Debugger/StackWidget.ui index 44830715af..1f5451ba99 100644 --- a/pcsx2-qt/Debugger/StackWidget.ui +++ b/pcsx2-qt/Debugger/StackWidget.ui @@ -30,11 +30,7 @@ 0 - - - Qt::CustomContextMenu - - + diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp index e86d8af394..a5e03581ee 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -17,20 +17,17 @@ static bool testName(const QString& name, const QString& filter); SymbolTreeWidget::SymbolTreeWidget( u32 flags, s32 symbol_address_alignment, - DebugInterface& cpu, - QWidget* parent) - : DebuggerWidget(&cpu, parent) - , m_cpu(cpu) + const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) , m_flags(flags) , m_symbol_address_alignment(symbol_address_alignment) + , m_group_by_module(cpu().getCpuType() == BREAKPOINT_IOP) { m_ui.setupUi(this); - setupMenu(); - connect(m_ui.refreshButton, &QPushButton::clicked, this, [&]() { - m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { - m_cpu.GetSymbolGuardian().UpdateFunctionHashes(database, m_cpu); + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().UpdateFunctionHashes(database, cpu()); }); reset(); @@ -46,11 +43,16 @@ SymbolTreeWidget::SymbolTreeWidget( }); m_ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openMenu); + connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openContextMenu); connect(m_ui.treeView, &QTreeView::expanded, this, [&]() { updateVisibleNodes(true); }); + + receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool { + updateModel(); + return true; + }); } SymbolTreeWidget::~SymbolTreeWidget() = default; @@ -75,14 +77,14 @@ void SymbolTreeWidget::reset() if (!m_model) setupTree(); - m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked()); + m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column); SymbolFilters filters; std::unique_ptr root; - m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { - filters.group_by_module = m_group_by_module && m_group_by_module->isChecked(); - filters.group_by_section = m_group_by_section && m_group_by_section->isChecked(); - filters.group_by_source_file = m_group_by_source_file && m_group_by_source_file->isChecked(); + cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + filters.group_by_module = m_group_by_module; + filters.group_by_section = m_group_by_section; + filters.group_by_source_file = m_group_by_source_file; filters.string = m_ui.filterBox->text(); root = buildTree(filters, database); @@ -90,7 +92,7 @@ void SymbolTreeWidget::reset() if (root) { - root->sortChildrenRecursively(m_sort_by_if_type_is_known && m_sort_by_if_type_is_known->isChecked()); + root->sortChildrenRecursively(m_sort_by_if_type_is_known); m_model->reset(std::move(root)); // Read the initial values for visible nodes. @@ -118,8 +120,8 @@ void SymbolTreeWidget::updateVisibleNodes(bool update_hashes) // Hash functions for symbols with visible nodes. if (update_hashes) { - m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { - SymbolTreeNode::updateSymbolHashes(nodes, m_cpu, database); + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + SymbolTreeNode::updateSymbolHashes(nodes, cpu(), database); }); } @@ -151,16 +153,16 @@ void SymbolTreeWidget::expandGroups(QModelIndex index) void SymbolTreeWidget::setupTree() { - m_model = new SymbolTreeModel(m_cpu, this); + m_model = new SymbolTreeModel(cpu(), this); m_ui.treeView->setModel(m_model); - auto location_delegate = new SymbolTreeLocationDelegate(m_cpu, m_symbol_address_alignment, this); + auto location_delegate = new SymbolTreeLocationDelegate(cpu(), m_symbol_address_alignment, this); m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::LOCATION, location_delegate); - auto type_delegate = new SymbolTreeTypeDelegate(m_cpu, this); + auto type_delegate = new SymbolTreeTypeDelegate(cpu(), this); m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::TYPE, type_delegate); - auto value_delegate = new SymbolTreeValueDelegate(m_cpu, this); + auto value_delegate = new SymbolTreeValueDelegate(cpu(), this); m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, value_delegate); m_ui.treeView->setAlternatingRowColors(true); @@ -382,95 +384,7 @@ std::unique_ptr SymbolTreeWidget::groupByModule( return child; } -void SymbolTreeWidget::setupMenu() -{ - m_context_menu = new QMenu(this); - - QAction* copy_name = new QAction(tr("Copy Name"), this); - connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName); - m_context_menu->addAction(copy_name); - - if (m_flags & ALLOW_MANGLED_NAME_ACTIONS) - { - QAction* copy_mangled_name = new QAction(tr("Copy Mangled Name"), this); - connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName); - m_context_menu->addAction(copy_mangled_name); - } - - QAction* copy_location = new QAction(tr("Copy Location"), this); - connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation); - m_context_menu->addAction(copy_location); - - m_context_menu->addSeparator(); - - m_rename_symbol = new QAction(tr("Rename Symbol"), this); - connect(m_rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol); - m_context_menu->addAction(m_rename_symbol); - - m_context_menu->addSeparator(); - - m_go_to_in_disassembly = new QAction(tr("Go to in Disassembly"), this); - connect(m_go_to_in_disassembly, &QAction::triggered, this, &SymbolTreeWidget::onGoToInDisassembly); - m_context_menu->addAction(m_go_to_in_disassembly); - - m_m_go_to_in_memory_view = new QAction(tr("Go to in Memory View"), this); - connect(m_m_go_to_in_memory_view, &QAction::triggered, this, &SymbolTreeWidget::onGoToInMemoryView); - m_context_menu->addAction(m_m_go_to_in_memory_view); - - m_show_size_column = new QAction(tr("Show Size Column"), this); - m_show_size_column->setCheckable(true); - connect(m_show_size_column, &QAction::triggered, this, &SymbolTreeWidget::reset); - m_context_menu->addAction(m_show_size_column); - - if (m_flags & ALLOW_GROUPING) - { - m_context_menu->addSeparator(); - - m_group_by_module = new QAction(tr("Group by Module"), this); - m_group_by_module->setCheckable(true); - if (m_cpu.getCpuType() == BREAKPOINT_IOP) - m_group_by_module->setChecked(true); - connect(m_group_by_module, &QAction::toggled, this, &SymbolTreeWidget::reset); - m_context_menu->addAction(m_group_by_module); - - m_group_by_section = new QAction(tr("Group by Section"), this); - m_group_by_section->setCheckable(true); - connect(m_group_by_section, &QAction::toggled, this, &SymbolTreeWidget::reset); - m_context_menu->addAction(m_group_by_section); - - m_group_by_source_file = new QAction(tr("Group by Source File"), this); - m_group_by_source_file->setCheckable(true); - connect(m_group_by_source_file, &QAction::toggled, this, &SymbolTreeWidget::reset); - m_context_menu->addAction(m_group_by_source_file); - } - - if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN) - { - m_context_menu->addSeparator(); - - m_sort_by_if_type_is_known = new QAction(tr("Sort by if type is known"), this); - m_sort_by_if_type_is_known->setCheckable(true); - m_context_menu->addAction(m_sort_by_if_type_is_known); - - connect(m_sort_by_if_type_is_known, &QAction::toggled, this, &SymbolTreeWidget::reset); - } - - if (m_flags & ALLOW_TYPE_ACTIONS) - { - m_context_menu->addSeparator(); - - m_reset_children = new QAction(tr("Reset children"), this); - m_context_menu->addAction(m_reset_children); - - m_change_type_temporarily = new QAction(tr("Change type temporarily"), this); - m_context_menu->addAction(m_change_type_temporarily); - - connect(m_reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren); - connect(m_change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily); - } -} - -void SymbolTreeWidget::openMenu(QPoint pos) +void SymbolTreeWidget::openContextMenu(QPoint pos) { SymbolTreeNode* node = currentNode(); if (!node) @@ -480,17 +394,107 @@ void SymbolTreeWidget::openMenu(QPoint pos) bool node_is_symbol = node->symbol.valid(); bool node_is_memory = node->location.type == SymbolTreeLocation::MEMORY; - m_rename_symbol->setEnabled(node_is_symbol); - m_go_to_in_disassembly->setEnabled(node_is_memory); - m_m_go_to_in_memory_view->setEnabled(node_is_memory); + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); - if (m_reset_children) - m_reset_children->setEnabled(node_is_object); + QAction* copy_name = menu->addAction(tr("Copy Name")); + connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName); - if (m_change_type_temporarily) - m_change_type_temporarily->setEnabled(node_is_object); + if (m_flags & ALLOW_MANGLED_NAME_ACTIONS) + { + QAction* copy_mangled_name = menu->addAction(tr("Copy Mangled Name")); + connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName); + } - m_context_menu->exec(m_ui.treeView->viewport()->mapToGlobal(pos)); + QAction* copy_location = menu->addAction(tr("Copy Location")); + connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation); + + menu->addSeparator(); + + QAction* rename_symbol = menu->addAction(tr("Rename Symbol")); + rename_symbol->setEnabled(node_is_symbol); + connect(rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol); + + menu->addSeparator(); + + std::vector go_to_actions = createEventActions( + menu, [this]() -> std::optional { + SymbolTreeNode* node = currentNode(); + if (!node) + return std::nullopt; + + DebuggerEvents::GoToAddress event; + event.address = node->location.address; + return event; + }); + + for (QAction* action : go_to_actions) + action->setEnabled(node_is_memory); + + QAction* show_size_column = menu->addAction(tr("Show Size Column")); + show_size_column->setCheckable(true); + show_size_column->setChecked(m_show_size_column); + connect(show_size_column, &QAction::triggered, this, [this](bool checked) { + m_show_size_column = checked; + m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column); + }); + + if (m_flags & ALLOW_GROUPING) + { + menu->addSeparator(); + + QAction* group_by_module = menu->addAction(tr("Group by Module")); + group_by_module->setCheckable(true); + group_by_module->setChecked(m_group_by_module); + connect(group_by_module, &QAction::toggled, this, [this](bool checked) { + m_group_by_module = checked; + reset(); + }); + + QAction* group_by_section = menu->addAction(tr("Group by Section")); + group_by_section->setCheckable(true); + group_by_section->setChecked(m_group_by_section); + connect(group_by_section, &QAction::toggled, this, [this](bool checked) { + m_group_by_section = checked; + reset(); + }); + + QAction* group_by_source_file = menu->addAction(tr("Group by Source File")); + group_by_source_file->setCheckable(true); + group_by_source_file->setChecked(m_group_by_source_file); + connect(group_by_source_file, &QAction::toggled, this, [this](bool checked) { + m_group_by_source_file = checked; + reset(); + }); + } + + if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN) + { + menu->addSeparator(); + + QAction* sort_by_if_type_is_known = menu->addAction(tr("Sort by if type is known")); + sort_by_if_type_is_known->setCheckable(true); + sort_by_if_type_is_known->setChecked(m_sort_by_if_type_is_known); + connect(sort_by_if_type_is_known, &QAction::toggled, this, [this](bool checked) { + m_sort_by_if_type_is_known = checked; + reset(); + }); + } + + if (m_flags & ALLOW_TYPE_ACTIONS) + { + menu->addSeparator(); + + QAction* reset_children = menu->addAction(tr("Reset Children")); + reset_children->setEnabled(node_is_object); + connect(reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren); + + QAction* change_type_temporarily = menu->addAction(tr("Change Type Temporarily")); + change_type_temporarily->setEnabled(node_is_object); + connect(change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily); + } + + menu->popup(m_ui.treeView->viewport()->mapToGlobal(pos)); } bool SymbolTreeWidget::needsReset() const @@ -510,7 +514,7 @@ void SymbolTreeWidget::onDeleteButtonPressed() if (QMessageBox::question(this, tr("Confirm Deletion"), tr("Delete '%1'?").arg(node->name)) != QMessageBox::Yes) return; - m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { node->symbol.destroy_symbol(database, true); }); @@ -544,7 +548,7 @@ void SymbolTreeWidget::onCopyLocation() if (!node) return; - QApplication::clipboard()->setText(node->location.toString(m_cpu)); + QApplication::clipboard()->setText(node->location.toString(cpu())); } void SymbolTreeWidget::onRenameSymbol() @@ -557,7 +561,7 @@ void SymbolTreeWidget::onRenameSymbol() QString label = tr("Name:"); QString text; - m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { const ccc::Symbol* symbol = node->symbol.lookup_symbol(database); if (!symbol || !symbol->address().valid()) return; @@ -570,29 +574,11 @@ void SymbolTreeWidget::onRenameSymbol() if (!ok) return; - m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { node->symbol.rename_symbol(name, database); }); } -void SymbolTreeWidget::onGoToInDisassembly() -{ - SymbolTreeNode* node = currentNode(); - if (!node) - return; - - emit goToInDisassembly(node->location.address); -} - -void SymbolTreeWidget::onGoToInMemoryView() -{ - SymbolTreeNode* node = currentNode(); - if (!node) - return; - - emit goToInMemoryView(node->location.address); -} - void SymbolTreeWidget::onResetChildren() { if (!m_model) @@ -635,22 +621,14 @@ void SymbolTreeWidget::onChangeTypeTemporarily() void SymbolTreeWidget::onTreeViewClicked(const QModelIndex& index) { - if (!index.isValid()) + if (!index.isValid() || (m_flags & CLICK_TO_GO_TO_IN_DISASSEMBLER) == 0) return; SymbolTreeNode* node = m_model->nodeFromIndex(index); - if (!node) + if (!node || node->location.type != SymbolTreeLocation::MEMORY) return; - switch (index.column()) - { - case SymbolTreeModel::NAME: - emit nameColumnClicked(node->location.address); - break; - case SymbolTreeModel::LOCATION: - emit locationColumnClicked(node->location.address); - break; - } + goToInDisassembler(node->location.address, false); } SymbolTreeNode* SymbolTreeWidget::currentNode() @@ -664,12 +642,11 @@ SymbolTreeNode* SymbolTreeWidget::currentNode() // ***************************************************************************** -FunctionTreeWidget::FunctionTreeWidget(DebugInterface& cpu, QWidget* parent) +FunctionTreeWidget::FunctionTreeWidget(const DebuggerWidgetParameters& parameters) : SymbolTreeWidget( - ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS, + ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS | CLICK_TO_GO_TO_IN_DISASSEMBLER, 4, - cpu, - parent) + parameters) { } @@ -745,19 +722,18 @@ void FunctionTreeWidget::configureColumns() void FunctionTreeWidget::onNewButtonPressed() { - NewFunctionDialog* dialog = new NewFunctionDialog(m_cpu, this); + NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this); if (dialog->exec() == QDialog::Accepted) reset(); } // ***************************************************************************** -GlobalVariableTreeWidget::GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) +GlobalVariableTreeWidget::GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters) : SymbolTreeWidget( ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS, 1, - cpu, - parent) + parameters) { } @@ -888,19 +864,18 @@ void GlobalVariableTreeWidget::configureColumns() void GlobalVariableTreeWidget::onNewButtonPressed() { - NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(m_cpu, this); + NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(cpu(), this); if (dialog->exec() == QDialog::Accepted) reset(); } // ***************************************************************************** -LocalVariableTreeWidget::LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) +LocalVariableTreeWidget::LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters) : SymbolTreeWidget( ALLOW_TYPE_ACTIONS, 1, - cpu, - parent) + parameters) { } @@ -911,10 +886,10 @@ bool LocalVariableTreeWidget::needsReset() const if (!m_function.valid()) return true; - u32 program_counter = m_cpu.getPC(); + u32 program_counter = cpu().getPC(); bool left_function = true; - m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { const ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function || !function->address().valid()) return; @@ -934,7 +909,7 @@ bool LocalVariableTreeWidget::needsReset() const std::vector LocalVariableTreeWidget::getSymbols( const QString& filter, const ccc::SymbolDatabase& database) { - u32 program_counter = m_cpu.getPC(); + u32 program_counter = cpu().getPC(); const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter); if (!function || !function->local_variables().has_value()) { @@ -943,7 +918,7 @@ std::vector LocalVariableTreeWidget::getSymbols( } m_function = function->handle(); - m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function); + m_caller_stack_pointer = cpu().getCallerStackPointer(*function); std::vector symbols; @@ -1017,19 +992,18 @@ void LocalVariableTreeWidget::configureColumns() void LocalVariableTreeWidget::onNewButtonPressed() { - NewLocalVariableDialog* dialog = new NewLocalVariableDialog(m_cpu, this); + NewLocalVariableDialog* dialog = new NewLocalVariableDialog(cpu(), this); if (dialog->exec() == QDialog::Accepted) reset(); } // ***************************************************************************** -ParameterVariableTreeWidget::ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent) +ParameterVariableTreeWidget::ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters) : SymbolTreeWidget( ALLOW_TYPE_ACTIONS, 1, - cpu, - parent) + parameters) { } @@ -1040,10 +1014,10 @@ bool ParameterVariableTreeWidget::needsReset() const if (!m_function.valid()) return true; - u32 program_counter = m_cpu.getPC(); + u32 program_counter = cpu().getPC(); bool left_function = true; - m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { const ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function || !function->address().valid()) return; @@ -1065,7 +1039,7 @@ std::vector ParameterVariableTreeWidget::getSymbol { std::vector symbols; - u32 program_counter = m_cpu.getPC(); + u32 program_counter = cpu().getPC(); const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter); if (!function || !function->parameter_variables().has_value()) { @@ -1074,7 +1048,7 @@ std::vector ParameterVariableTreeWidget::getSymbol } m_function = function->handle(); - m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function); + m_caller_stack_pointer = cpu().getCallerStackPointer(*function); for (const ccc::ParameterVariableHandle parameter_variable_handle : *function->parameter_variables()) { @@ -1144,7 +1118,7 @@ void ParameterVariableTreeWidget::configureColumns() void ParameterVariableTreeWidget::onNewButtonPressed() { - NewParameterVariableDialog* dialog = new NewParameterVariableDialog(m_cpu, this); + NewParameterVariableDialog* dialog = new NewParameterVariableDialog(cpu(), this); if (dialog->exec() == QDialog::Accepted) reset(); } diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h index 0186b46dbf..b88df50ef0 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h @@ -24,12 +24,6 @@ public: void updateVisibleNodes(bool update_hashes); void expandGroups(QModelIndex index); -signals: - void goToInDisassembly(u32 address); - void goToInMemoryView(u32 address); - void nameColumnClicked(u32 address); - void locationColumnClicked(u32 address); - protected: struct SymbolWork { @@ -44,8 +38,7 @@ protected: SymbolTreeWidget( u32 flags, s32 symbol_address_alignment, - DebugInterface& cpu, - QWidget* parent = nullptr); + const DebuggerWidgetParameters& parameters); void resizeEvent(QResizeEvent* event) override; @@ -73,8 +66,7 @@ protected: const SymbolWork*& prev_work, const SymbolFilters& filters); - void setupMenu(); - void openMenu(QPoint pos); + void openContextMenu(QPoint pos); virtual bool needsReset() const; @@ -93,8 +85,6 @@ protected: void onCopyMangledName(); void onCopyLocation(); void onRenameSymbol(); - void onGoToInDisassembly(); - void onGoToInMemoryView(); void onResetChildren(); void onChangeTypeTemporarily(); @@ -104,39 +94,33 @@ protected: Ui::SymbolTreeWidget m_ui; - DebugInterface& m_cpu; SymbolTreeModel* m_model = nullptr; - QMenu* m_context_menu = nullptr; - QAction* m_rename_symbol = nullptr; - QAction* m_go_to_in_disassembly = nullptr; - QAction* m_m_go_to_in_memory_view = nullptr; - QAction* m_show_size_column = nullptr; - QAction* m_group_by_module = nullptr; - QAction* m_group_by_section = nullptr; - QAction* m_group_by_source_file = nullptr; - QAction* m_sort_by_if_type_is_known = nullptr; - QAction* m_reset_children = nullptr; - QAction* m_change_type_temporarily = nullptr; - enum Flags { NO_SYMBOL_TREE_FLAGS = 0, ALLOW_GROUPING = 1 << 0, ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN = 1 << 1, ALLOW_TYPE_ACTIONS = 1 << 2, - ALLOW_MANGLED_NAME_ACTIONS = 1 << 3 + ALLOW_MANGLED_NAME_ACTIONS = 1 << 3, + CLICK_TO_GO_TO_IN_DISASSEMBLER = 1 << 4 }; u32 m_flags; u32 m_symbol_address_alignment; + + bool m_show_size_column = false; + bool m_group_by_module = false; + bool m_group_by_section = false; + bool m_group_by_source_file = false; + bool m_sort_by_if_type_is_known = false; }; class FunctionTreeWidget : public SymbolTreeWidget { Q_OBJECT public: - explicit FunctionTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + explicit FunctionTreeWidget(const DebuggerWidgetParameters& parameters); virtual ~FunctionTreeWidget(); protected: @@ -155,7 +139,7 @@ class GlobalVariableTreeWidget : public SymbolTreeWidget { Q_OBJECT public: - explicit GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + explicit GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters); virtual ~GlobalVariableTreeWidget(); protected: @@ -174,7 +158,7 @@ class LocalVariableTreeWidget : public SymbolTreeWidget { Q_OBJECT public: - explicit LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + explicit LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters); virtual ~LocalVariableTreeWidget(); protected: @@ -198,7 +182,7 @@ class ParameterVariableTreeWidget : public SymbolTreeWidget { Q_OBJECT public: - explicit ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + explicit ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters); virtual ~ParameterVariableTreeWidget(); protected: diff --git a/pcsx2-qt/Debugger/ThreadModel.cpp b/pcsx2-qt/Debugger/ThreadModel.cpp index 2a9a2791dc..19291668aa 100644 --- a/pcsx2-qt/Debugger/ThreadModel.cpp +++ b/pcsx2-qt/Debugger/ThreadModel.cpp @@ -23,8 +23,13 @@ int ThreadModel::columnCount(const QModelIndex&) const QVariant ThreadModel::data(const QModelIndex& index, int role) const { - const auto threads = m_cpu.GetThreadList(); - auto* const thread = threads.at(index.row()).get(); + const std::vector> threads = m_cpu.GetThreadList(); + + size_t row = static_cast(index.row()); + if (row >= threads.size()) + return QVariant(); + + const BiosThread* thread = threads[row].get(); if (role == Qt::DisplayRole) { diff --git a/pcsx2-qt/Debugger/ThreadWidget.cpp b/pcsx2-qt/Debugger/ThreadWidget.cpp index 73401586ad..4c37ad65e6 100644 --- a/pcsx2-qt/Debugger/ThreadWidget.cpp +++ b/pcsx2-qt/Debugger/ThreadWidget.cpp @@ -8,18 +8,20 @@ #include #include -ThreadWidget::ThreadWidget(DebugInterface& cpu, QWidget* parent) - : DebuggerWidget(&cpu, parent) - , m_model(cpu) +ThreadWidget::ThreadWidget(const DebuggerWidgetParameters& parameters) + : DebuggerWidget(parameters) + , m_model(new ThreadModel(cpu())) + , m_proxy_model(new QSortFilterProxyModel()) { m_ui.setupUi(this); - connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &ThreadWidget::onContextMenu); + m_ui.threadList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &ThreadWidget::openContextMenu); connect(m_ui.threadList, &QTableView::doubleClicked, this, &ThreadWidget::onDoubleClick); - m_proxy_model.setSourceModel(&m_model); - m_proxy_model.setSortRole(Qt::UserRole); - m_ui.threadList->setModel(&m_proxy_model); + m_proxy_model->setSourceModel(m_model); + m_proxy_model->setSortRole(Qt::UserRole); + m_ui.threadList->setModel(m_proxy_model); m_ui.threadList->setSortingEnabled(true); m_ui.threadList->sortByColumn(ThreadModel::ThreadColumns::ID, Qt::SortOrder::AscendingOrder); for (std::size_t i = 0; auto mode : ThreadModel::HeaderResizeModes) @@ -27,35 +29,38 @@ ThreadWidget::ThreadWidget(DebugInterface& cpu, QWidget* parent) m_ui.threadList->horizontalHeader()->setSectionResizeMode(i, mode); i++; } + + receiveEvent([this](const DebuggerEvents::VMUpdate& event) -> bool { + m_model->refreshData(); + return true; + }); } -void ThreadWidget::onContextMenu(QPoint pos) +void ThreadWidget::openContextMenu(QPoint pos) { if (!m_ui.threadList->selectionModel()->hasSelection()) return; - QMenu* contextMenu = new QMenu(tr("Thread List Context Menu"), m_ui.threadList); + QMenu* menu = new QMenu(m_ui.threadList); + menu->setAttribute(Qt::WA_DeleteOnClose); - QAction* actionCopy = new QAction(tr("Copy"), m_ui.threadList); - connect(actionCopy, &QAction::triggered, [this]() { - const auto* selModel = m_ui.threadList->selectionModel(); - - if (!selModel->hasSelection()) + QAction* copy = menu->addAction(tr("Copy")); + connect(copy, &QAction::triggered, [this]() { + const QItemSelectionModel* selection_model = m_ui.threadList->selectionModel(); + if (!selection_model->hasSelection()) return; - QGuiApplication::clipboard()->setText(m_ui.threadList->model()->data(selModel->currentIndex()).toString()); + QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString()); }); - contextMenu->addAction(actionCopy); - contextMenu->addSeparator(); + menu->addSeparator(); - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.threadList); - connect(actionExport, &QAction::triggered, [this]() { + QAction* copy_all_as_csv = menu->addAction(tr("Copy all as CSV")); + connect(copy_all_as_csv, &QAction::triggered, [this]() { QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model())); }); - contextMenu->addAction(actionExport); - contextMenu->popup(m_ui.threadList->viewport()->mapToGlobal(pos)); + menu->popup(m_ui.threadList->viewport()->mapToGlobal(pos)); } void ThreadWidget::onDoubleClick(const QModelIndex& index) @@ -63,13 +68,15 @@ void ThreadWidget::onDoubleClick(const QModelIndex& index) switch (index.column()) { case ThreadModel::ThreadColumns::ENTRY: - not_yet_implemented(); - //m_ui.memoryviewWidget->gotoAddress(m_ui.threadList->model()->data(index, Qt::UserRole).toUInt()); - //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); + { + goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true); break; + } default: // Default to PC - not_yet_implemented(); - //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.threadList->model()->data(m_ui.threadList->model()->index(index.row(), ThreadModel::ThreadColumns::PC), Qt::UserRole).toUInt()); + { + QModelIndex pc_index = m_model->index(index.row(), ThreadModel::ThreadColumns::PC); + goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true); break; + } } } diff --git a/pcsx2-qt/Debugger/ThreadWidget.h b/pcsx2-qt/Debugger/ThreadWidget.h index f0d71064d7..ef501bbe99 100644 --- a/pcsx2-qt/Debugger/ThreadWidget.h +++ b/pcsx2-qt/Debugger/ThreadWidget.h @@ -8,21 +8,21 @@ #include "DebuggerWidget.h" #include "ThreadModel.h" -#include +#include class ThreadWidget final : public DebuggerWidget { Q_OBJECT public: - ThreadWidget(DebugInterface& cpu, QWidget* parent = nullptr); + ThreadWidget(const DebuggerWidgetParameters& parameters); - void onContextMenu(QPoint pos); + void openContextMenu(QPoint pos); void onDoubleClick(const QModelIndex& index); private: Ui::ThreadWidget m_ui; - ThreadModel m_model; - QSortFilterProxyModel m_proxy_model; + ThreadModel* m_model; + QSortFilterProxyModel* m_proxy_model; }; diff --git a/pcsx2-qt/Debugger/ThreadWidget.ui b/pcsx2-qt/Debugger/ThreadWidget.ui index 4d498c2146..99b60e881f 100644 --- a/pcsx2-qt/Debugger/ThreadWidget.ui +++ b/pcsx2-qt/Debugger/ThreadWidget.ui @@ -30,11 +30,7 @@ 0 - - - Qt::CustomContextMenu - - + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 96e5e3fa1d..97be475830 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -224,6 +224,7 @@ + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index ec38a431d1..706391a1c8 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -598,6 +598,9 @@ Debugger + + Debugger + Debugger