From 6fe97b42c3a2f839b5502c1a87b2fd61175c7139 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Sun, 23 Feb 2025 12:44:38 +0000 Subject: [PATCH] Debugger: Allow having multiple dock widgets of the same type --- .../Debugger/Breakpoints/BreakpointWidget.cpp | 2 +- pcsx2-qt/Debugger/DebuggerWidget.cpp | 121 +++++- pcsx2-qt/Debugger/DebuggerWidget.h | 43 +- pcsx2-qt/Debugger/DisassemblyWidget.cpp | 2 +- pcsx2-qt/Debugger/Docking/DockLayout.cpp | 372 ++++++++++++------ pcsx2-qt/Debugger/Docking/DockLayout.h | 27 +- pcsx2-qt/Debugger/Docking/DockManager.cpp | 153 ++++++- pcsx2-qt/Debugger/Docking/DockManager.h | 8 +- pcsx2-qt/Debugger/Docking/DockTables.cpp | 2 +- pcsx2-qt/Debugger/Docking/DockTables.h | 2 +- pcsx2-qt/Debugger/Docking/DockUtils.cpp | 2 +- pcsx2-qt/Debugger/Docking/DockUtils.h | 2 +- pcsx2-qt/Debugger/Docking/DockViews.cpp | 176 ++++++--- pcsx2-qt/Debugger/Docking/DockViews.h | 16 +- .../Debugger/Memory/MemorySearchWidget.cpp | 2 +- pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp | 2 +- .../Debugger/Memory/SavedAddressesWidget.cpp | 2 +- pcsx2-qt/Debugger/RegisterWidget.cpp | 2 +- pcsx2-qt/Debugger/StackWidget.cpp | 2 +- .../Debugger/SymbolTree/SymbolTreeWidgets.cpp | 2 +- pcsx2-qt/Debugger/ThreadWidget.cpp | 2 +- pcsx2/DebugTools/DebugInterface.h | 2 +- 22 files changed, 703 insertions(+), 241 deletions(-) diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp index 679fa26c74..e0f864caf0 100644 --- a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp @@ -11,7 +11,7 @@ #include BreakpointWidget::BreakpointWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES) , m_model(new BreakpointModel(cpu())) { m_ui.setupUi(this); diff --git a/pcsx2-qt/Debugger/DebuggerWidget.cpp b/pcsx2-qt/Debugger/DebuggerWidget.cpp index b4c8eccade..145ed3f1bd 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.cpp +++ b/pcsx2-qt/Debugger/DebuggerWidget.cpp @@ -11,6 +11,16 @@ #include "DebugTools/DebugInterface.h" #include "common/Assertions.h" +#include "common/Console.h" + +DebuggerWidget::DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags) + : QWidget(parameters.parent) + , m_unique_name(parameters.unique_name) + , m_cpu(parameters.cpu) + , m_cpu_override(parameters.cpu_override) + , m_flags(flags) +{ +} DebugInterface& DebuggerWidget::cpu() const { @@ -21,13 +31,49 @@ DebugInterface& DebuggerWidget::cpu() const return *m_cpu; } -QString DebuggerWidget::displayName() +QString DebuggerWidget::uniqueName() const { - auto description = DockTables::DEBUGGER_WIDGETS.find(metaObject()->className()); - if (description == DockTables::DEBUGGER_WIDGETS.end()) - return QString(); + return m_unique_name; +} - return QCoreApplication::translate("DebuggerWidget", description->second.display_name); +QString DebuggerWidget::displayName() const +{ + QString name = displayNameWithoutSuffix(); + + // If there are multiple debugger widgets of the same name, append a number + // to the display name. + if (m_display_name_suffix_number.has_value()) + name = tr("%1 #%2").arg(name).arg(*m_display_name_suffix_number); + + if (m_cpu_override) + name = tr("%1 (%2)").arg(name).arg(DebugInterface::cpuName(*m_cpu_override)); + + return name; +} + +QString DebuggerWidget::displayNameWithoutSuffix() const +{ + return m_translated_display_name; +} + +QString DebuggerWidget::customDisplayName() const +{ + return m_custom_display_name; +} + +void DebuggerWidget::setCustomDisplayName(QString display_name) +{ + m_custom_display_name = display_name; +} + +bool DebuggerWidget::isPrimary() const +{ + return m_is_primary; +} + +void DebuggerWidget::setPrimary(bool is_primary) +{ + m_is_primary = is_primary; } bool DebuggerWidget::setCpu(DebugInterface& new_cpu) @@ -72,10 +118,24 @@ bool DebuggerWidget::acceptsEventType(const char* event_type) void DebuggerWidget::toJson(JsonValueWrapper& json) { + std::string custom_display_name_str = m_custom_display_name.toStdString(); + rapidjson::Value custom_display_name; + custom_display_name.SetString(custom_display_name_str.c_str(), custom_display_name_str.size(), json.allocator()); + json.value().AddMember("customDisplayName", custom_display_name, json.allocator()); + + json.value().AddMember("isPrimary", m_is_primary, json.allocator()); } bool DebuggerWidget::fromJson(JsonValueWrapper& json) { + auto custom_display_name = json.value().FindMember("customDisplayName"); + if (custom_display_name != json.value().MemberEnd() && custom_display_name->value.IsString()) + m_custom_display_name = QString(custom_display_name->value.GetString()); + + auto is_primary = json.value().FindMember("isPrimary"); + if (is_primary != json.value().MemberEnd() && is_primary->value.IsBool()) + m_is_primary = is_primary->value.GetBool(); + return true; } @@ -97,6 +157,37 @@ void DebuggerWidget::switchToThisTab() g_debugger_window->dockManager().switchToDebuggerWidget(this); } +bool DebuggerWidget::supportsMultipleInstances() +{ + return !(m_flags & DISALLOW_MULTIPLE_INSTANCES); +} + +void DebuggerWidget::retranslateDisplayName() +{ + if (!m_custom_display_name.isEmpty()) + { + m_translated_display_name = m_custom_display_name; + } + else + { + auto description = DockTables::DEBUGGER_WIDGETS.find(metaObject()->className()); + if (description != DockTables::DEBUGGER_WIDGETS.end()) + m_translated_display_name = QCoreApplication::translate("DebuggerWidget", description->second.display_name); + else + m_translated_display_name = QString(); + } +} + +std::optional DebuggerWidget::displayNameSuffixNumber() const +{ + return m_display_name_suffix_number; +} + +void DebuggerWidget::setDisplayNameSuffixNumber(std::optional suffix_number) +{ + m_display_name_suffix_number = suffix_number; +} + void DebuggerWidget::goToInDisassembler(u32 address, bool switch_to_tab) { DebuggerEvents::GoToAddress event; @@ -115,20 +206,17 @@ void DebuggerWidget::goToInMemoryView(u32 address, bool 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)) + if (widget->isPrimary() && widget->handleEvent(event)) + return; + + for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets()) + if (!widget->isPrimary() && widget->handleEvent(event)) return; } @@ -157,6 +245,13 @@ std::vector DebuggerWidget::createEventActionsImplementation( if ((!skip_self || widget != this) && widget->acceptsEventType(event_type)) receivers.emplace_back(widget); + std::sort(receivers.begin(), receivers.end(), [&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) { + if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix()) + return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber(); + + return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix(); + }); + QMenu* submenu = nullptr; if (receivers.size() > max_top_level_actions) { diff --git a/pcsx2-qt/Debugger/DebuggerWidget.h b/pcsx2-qt/Debugger/DebuggerWidget.h index d6b0ab132e..2238c56aeb 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.h +++ b/pcsx2-qt/Debugger/DebuggerWidget.h @@ -15,6 +15,7 @@ class JsonValueWrapper; // Container for variables to be passed to the constructor of DebuggerWidget. struct DebuggerWidgetParameters { + QString unique_name; DebugInterface* cpu = nullptr; std::optional cpu_override; QWidget* parent = nullptr; @@ -26,8 +27,17 @@ class DebuggerWidget : public QWidget Q_OBJECT public: + QString uniqueName() const; + // Get the translated name that should be displayed for this widget. - QString displayName(); + QString displayName() const; + QString displayNameWithoutSuffix() const; + + QString customDisplayName() const; + void setCustomDisplayName(QString display_name); + + bool isPrimary() const; + void setPrimary(bool is_primary); // Get the effective debug interface associated with this particular widget // if it's set, otherwise return the one associated with the layout that @@ -133,11 +143,25 @@ public: void switchToThisTab(); + bool supportsMultipleInstances(); + + void retranslateDisplayName(); + + std::optional displayNameSuffixNumber() const; + void setDisplayNameSuffixNumber(std::optional suffix_number); + static void goToInDisassembler(u32 address, bool switch_to_tab); static void goToInMemoryView(u32 address, bool switch_to_tab); protected: - DebuggerWidget(const DebuggerWidgetParameters& parameters); + enum Flags + { + NO_DEBUGGER_FLAGS = 0, + // Prevent the user from opening multiple dock widgets of this type. + DISALLOW_MULTIPLE_INSTANCES = 1 << 0 + }; + + DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags); private: static void sendEventImplementation(const DebuggerEvents::Event& event); @@ -151,7 +175,22 @@ private: const char* action_prefix, std::function event_func); + QString m_unique_name; + + // A user-defined name, or an empty string if no name was specified so that + // the default names can be retranslated on the fly. + QString m_custom_display_name; + + QString m_translated_display_name; + std::optional m_display_name_suffix_number; + + // Primary debugger widgets will be chosen to handle events first. For + // example, clicking on an address to go to it in the primary memory view. + bool m_is_primary = false; + DebugInterface* m_cpu; std::optional m_cpu_override; + u32 m_flags; + std::multimap> m_event_handlers; }; diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index 5549c6db3c..fa99b17faf 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -20,7 +20,7 @@ using namespace QtUtils; DisassemblyWidget::DisassemblyWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) { m_ui.setupUi(this); diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.cpp b/pcsx2-qt/Debugger/Docking/DockLayout.cpp index 3a1a24e142..f5b37a3bb5 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.cpp +++ b/pcsx2-qt/Debugger/Docking/DockLayout.cpp @@ -47,14 +47,20 @@ DockLayout::DockLayout( { for (size_t i = 0; i < default_layout.widgets.size(); i++) { - auto iterator = DockTables::DEBUGGER_WIDGETS.find(QString::fromStdString(default_layout.widgets[i].type)); + auto iterator = DockTables::DEBUGGER_WIDGETS.find(default_layout.widgets[i].type); pxAssertRel(iterator != DockTables::DEBUGGER_WIDGETS.end(), "Invalid default layout."); const DockTables::DebuggerWidgetDescription& dock_description = iterator->second; DebuggerWidgetParameters parameters; + parameters.unique_name = generateNewUniqueName(default_layout.widgets[i].type.c_str()); parameters.cpu = &DebugInterface::get(cpu); + + if (parameters.unique_name.isEmpty()) + continue; + DebuggerWidget* widget = dock_description.create_widget(parameters); - m_widgets.emplace(QString::fromStdString(default_layout.widgets[i].type), widget); + widget->setPrimary(true); + m_widgets.emplace(parameters.unique_name, widget); } save(index); @@ -81,6 +87,7 @@ DockLayout::DockLayout( : m_name(name) , m_cpu(cpu) , m_is_default(is_default) + , m_next_unique_name(layout_to_clone.m_next_unique_name) , m_base_layout(layout_to_clone.m_base_layout) , m_toolbars(layout_to_clone.m_toolbars) , m_geometry(layout_to_clone.m_geometry) @@ -92,9 +99,13 @@ DockLayout::DockLayout( continue; DebuggerWidgetParameters parameters; + parameters.unique_name = unique_name; parameters.cpu = &DebugInterface::get(cpu); parameters.cpu_override = widget_to_clone->cpuOverride(); + DebuggerWidget* new_widget = widget_description->second.create_widget(parameters); + new_widget->setCustomDisplayName(widget_to_clone->customDisplayName()); + new_widget->setPrimary(widget_to_clone->isPrimary()); m_widgets.emplace(unique_name, new_widget); } @@ -155,8 +166,8 @@ void DockLayout::setCpu(BreakPointCpu cpu) void DockLayout::freeze() { - pxAssert(!m_is_frozen); - m_is_frozen = true; + pxAssert(m_is_active); + m_is_active = false; if (g_debugger_window) m_toolbars = g_debugger_window->saveState(); @@ -178,8 +189,8 @@ void DockLayout::freeze() void DockLayout::thaw() { - pxAssert(m_is_frozen); - m_is_frozen = false; + pxAssert(!m_is_active); + m_is_active = true; if (!g_debugger_window) return; @@ -204,26 +215,26 @@ void DockLayout::thaw() { // This is a newly created layout with no geometry information. setupDefaultLayout(); - return; } - - // Restore the geometry of the dock widgets we just recreated. - KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow); - if (!saver.restoreLayout(m_geometry)) + else { - // We've failed to restore the geometry, so just tear down whatever dock - // widgets may exist and then setup the default layout. - for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets()) + // Create all the dock widgets. + KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow); + if (!saver.restoreLayout(m_geometry)) { - // Make sure the dock widget releases ownership of its content. - auto view = static_cast(dock->view()); - view->setWidget(new QWidget()); + // We've failed to restore the geometry, so just tear down whatever + // dock widgets may exist and then setup the default layout. + for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets()) + { + // Make sure the dock widget releases ownership of its content. + auto view = static_cast(dock->view()); + view->setWidget(new QWidget()); - delete dock; + delete dock; + } + + setupDefaultLayout(); } - - setupDefaultLayout(); - return; } // Check that all the dock widgets have been restored correctly. @@ -242,16 +253,19 @@ void DockLayout::thaw() for (const QString& unique_name : orphaned_debugger_widgets) { auto widget_iterator = m_widgets.find(unique_name); + pxAssert(widget_iterator != m_widgets.end()); + + setPrimaryDebuggerWidget(widget_iterator->second.get(), false); delete widget_iterator->second.get(); m_widgets.erase(widget_iterator); } - retranslateDockWidgets(); + updateDockWidgetTitles(); } KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& name) { - pxAssert(!m_is_frozen); + pxAssert(m_is_active); pxAssert(KDDockWidgets::LayoutSaver::restoreInProgress()); auto widget_iterator = m_widgets.find(name); @@ -260,6 +274,7 @@ KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& nam DebuggerWidget* widget = widget_iterator->second; pxAssert(widget); + pxAssert(widget->uniqueName() == name); auto view = static_cast( KDDockWidgets::Config::self().viewFactory()->createDockWidget(name)); @@ -268,139 +283,132 @@ KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& nam return view->asController(); } -void DockLayout::retranslateDockWidgets() +void DockLayout::updateDockWidgetTitles() { - for (KDDockWidgets::Core::DockWidget* widget : KDDockWidgets::DockRegistry::self()->dockwidgets()) - retranslateDockWidget(widget); -} - -void DockLayout::retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget) -{ - pxAssert(!m_is_frozen); - - auto widget_iterator = m_widgets.find(dock_widget->uniqueName()); - if (widget_iterator == m_widgets.end()) + if (!m_is_active) return; - DebuggerWidget* widget = widget_iterator->second.get(); - if (!widget) - return; - ; - std::optional cpu_override = widget->cpuOverride(); + // Translate default debugger widget names. + for (auto& [unique_name, widget] : m_widgets) + widget->retranslateDisplayName(); - if (cpu_override.has_value()) + // Determine if any widgets have duplicate display names. + std::map> display_name_to_widgets; + for (auto& [unique_name, widget] : m_widgets) + display_name_to_widgets[widget->displayNameWithoutSuffix()].emplace_back(widget.get()); + + for (auto& [display_name, widgets] : display_name_to_widgets) { - const char* cpu_name = DebugInterface::cpuName(*cpu_override); - dock_widget->setTitle(QString("%1 (%2)").arg(widget->displayName()).arg(cpu_name)); + std::sort(widgets.begin(), widgets.end(), + [&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) { + return lhs->uniqueName() < rhs->uniqueName(); + }); + + for (size_t i = 0; i < widgets.size(); i++) + { + std::optional suffix_number; + if (widgets.size() != 1) + suffix_number = static_cast(i + 1); + + widgets[i]->setDisplayNameSuffixNumber(suffix_number); + } } - else + + // Propagate the new names from the debugger widgets to the dock widgets. + for (auto& [unique_name, widget] : m_widgets) { - dock_widget->setTitle(std::move(widget->displayName())); + auto [controller, view] = DockUtils::dockWidgetFromName(widget->uniqueName()); + if (!controller) + continue; + + controller->setTitle(widget->displayName()); } } -void DockLayout::dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget) -{ - // The LayoutSaver class will close a bunch of dock widgets. We only want to - // delete the dock widgets when they're being closed by the user. - if (KDDockWidgets::LayoutSaver::restoreInProgress()) - return; - - auto debugger_widget_iterator = m_widgets.find(dock_widget->uniqueName()); - if (debugger_widget_iterator == m_widgets.end()) - return; - - m_widgets.erase(debugger_widget_iterator); - dock_widget->deleteLater(); -} - const std::map>& DockLayout::debuggerWidgets() { return m_widgets; } -bool DockLayout::hasDebuggerWidget(QString unique_name) +bool DockLayout::hasDebuggerWidget(const QString& unique_name) { return m_widgets.find(unique_name) != m_widgets.end(); } -void DockLayout::toggleDebuggerWidget(QString unique_name) +size_t DockLayout::countDebuggerWidgetsOfType(const char* type) { - pxAssert(!m_is_frozen); + size_t count = 0; + for (const auto& [unique_name, widget] : m_widgets) + { + if (strcmp(widget->metaObject()->className(), type) == 0) + count++; + } + + return count; +} + +void DockLayout::createDebuggerWidget(const std::string& type) +{ + pxAssert(m_is_active); if (!g_debugger_window) return; - auto debugger_widget_iterator = m_widgets.find(unique_name); - auto [controller, view] = DockUtils::dockWidgetFromName(unique_name); + auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type); + pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end()); - if (debugger_widget_iterator == m_widgets.end()) - { - // Create the dock widget. - if (controller) - return; + const DockTables::DebuggerWidgetDescription& description = description_iterator->second; - auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(unique_name); - if (description_iterator == DockTables::DEBUGGER_WIDGETS.end()) - return; + DebuggerWidgetParameters parameters; + parameters.unique_name = generateNewUniqueName(type.c_str()); + parameters.cpu = &DebugInterface::get(m_cpu); - const DockTables::DebuggerWidgetDescription& description = description_iterator->second; + if (parameters.unique_name.isEmpty()) + return; - DebuggerWidgetParameters parameters; - parameters.cpu = &DebugInterface::get(m_cpu); - DebuggerWidget* widget = description.create_widget(parameters); - m_widgets.emplace(unique_name, widget); + DebuggerWidget* widget = description.create_widget(parameters); + m_widgets.emplace(parameters.unique_name, widget); - auto view = static_cast( - KDDockWidgets::Config::self().viewFactory()->createDockWidget(unique_name)); - view->setWidget(widget); + setPrimaryDebuggerWidget(widget, countDebuggerWidgetsOfType(type.c_str()) == 0); - KDDockWidgets::Core::DockWidget* controller = view->asController(); - if (!controller) - { - delete view; - return; - } + auto view = static_cast( + KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName())); + view->setWidget(widget); - DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window); - retranslateDockWidget(controller); - } - else - { - // Delete the dock widget. - if (!controller) - return; + KDDockWidgets::Core::DockWidget* controller = view->asController(); + pxAssert(controller); - m_widgets.erase(debugger_widget_iterator); - delete controller; - } + DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window); + updateDockWidgetTitles(); } -void DockLayout::recreateDebuggerWidget(QString unique_name) +void DockLayout::recreateDebuggerWidget(const QString& unique_name) { - pxAssert(!m_is_frozen); + pxAssert(m_is_active); auto [controller, view] = DockUtils::dockWidgetFromName(unique_name); - if (!controller || !view) - return; + pxAssert(controller); + pxAssert(view); auto debugger_widget_iterator = m_widgets.find(unique_name); - if (debugger_widget_iterator == m_widgets.end()) - return; + pxAssert(debugger_widget_iterator != m_widgets.end()); DebuggerWidget* old_debugger_widget = debugger_widget_iterator->second; pxAssert(old_debugger_widget == view->widget()); auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(old_debugger_widget->metaObject()->className()); - if (description_iterator == DockTables::DEBUGGER_WIDGETS.end()) - return; + pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end()); const DockTables::DebuggerWidgetDescription& description = description_iterator->second; DebuggerWidgetParameters parameters; + parameters.unique_name = old_debugger_widget->uniqueName(); parameters.cpu = &DebugInterface::get(m_cpu); parameters.cpu_override = old_debugger_widget->cpuOverride(); + DebuggerWidget* new_debugger_widget = description.create_widget(parameters); + new_debugger_widget->setCustomDisplayName(old_debugger_widget->customDisplayName()); + new_debugger_widget->setPrimary(old_debugger_widget->isPrimary()); debugger_widget_iterator->second = new_debugger_widget; view->setWidget(new_debugger_widget); @@ -408,6 +416,77 @@ void DockLayout::recreateDebuggerWidget(QString unique_name) delete old_debugger_widget; } +void DockLayout::destroyDebuggerWidget(const QString& unique_name) +{ + pxAssert(m_is_active); + + if (!g_debugger_window) + return; + + auto debugger_widget_iterator = m_widgets.find(unique_name); + if (debugger_widget_iterator == m_widgets.end()) + return; + + setPrimaryDebuggerWidget(debugger_widget_iterator->second.get(), false); + delete debugger_widget_iterator->second.get(); + m_widgets.erase(debugger_widget_iterator); + + auto [controller, view] = DockUtils::dockWidgetFromName(unique_name); + if (!controller) + return; + + controller->deleteLater(); + + updateDockWidgetTitles(); +} + +void DockLayout::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary) +{ + bool present = false; + for (auto& [unique_name, test_widget] : m_widgets) + { + if (test_widget.get() == widget) + { + present = true; + break; + } + } + + if (!present) + return; + + if (is_primary) + { + // Set the passed widget as the primary widget. + for (auto& [unique_name, test_widget] : m_widgets) + { + if (strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0) + { + test_widget->setPrimary(test_widget.get() == widget); + } + } + } + else if (widget->isPrimary()) + { + // Set an arbitrary widget as the primary widget. + bool next = true; + for (auto& [unique_name, test_widget] : m_widgets) + { + if (test_widget != widget && + strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0) + { + test_widget->setPrimary(next); + next = false; + } + } + + // If we haven't set another widget as the primary one we can't make + // this one not the primary one. + if (!next) + widget->setPrimary(false); + } +} + void DockLayout::deleteFile() { if (m_layout_file_path.empty()) @@ -416,12 +495,13 @@ void DockLayout::deleteFile() if (!FileSystem::DeleteFilePath(m_layout_file_path.c_str())) Console.Error("Debugger: Failed to delete layout file '%s'.", m_layout_file_path.c_str()); } + bool DockLayout::save(DockLayout::Index layout_index) { if (!g_debugger_window) return false; - if (!m_is_frozen) + if (m_is_active) { m_toolbars = g_debugger_window->saveState(); @@ -451,6 +531,7 @@ bool DockLayout::save(DockLayout::Index layout_index) json.AddMember("target", rapidjson::Value().SetString(cpu_name, strlen(cpu_name)), json.GetAllocator()); json.AddMember("index", static_cast(layout_index), json.GetAllocator()); json.AddMember("isDefault", m_is_default, json.GetAllocator()); + json.AddMember("nextUniqueName", m_next_unique_name, json.GetAllocator()); if (!m_base_layout.empty()) { @@ -545,7 +626,7 @@ void DockLayout::load( LoadResult& result, DockLayout::Index& index_last_session) { - pxAssert(m_is_frozen); + pxAssert(!m_is_active); result = SUCCESS; @@ -631,6 +712,10 @@ void DockLayout::load( if (is_default != json.MemberEnd() && is_default->value.IsBool()) m_is_default = is_default->value.GetBool(); + auto next_unique_name = json.FindMember("nextUniqueName"); + if (next_unique_name != json.MemberBegin() && next_unique_name->value.IsInt()) + m_next_unique_name = next_unique_name->value.GetInt(); + auto base_layout = json.FindMember("baseLayout"); if (base_layout != json.MemberEnd() && base_layout->value.IsString()) m_base_layout = base_layout->value.GetString(); @@ -648,6 +733,10 @@ void DockLayout::load( if (unique_name == object.MemberEnd() || !unique_name->value.IsString()) continue; + auto widgets_iterator = m_widgets.find(unique_name->value.GetString()); + if (widgets_iterator != m_widgets.end()) + continue; + auto type = object.FindMember("type"); if (type == object.MemberEnd() || !type->value.IsString()) continue; @@ -667,8 +756,10 @@ void DockLayout::load( } DebuggerWidgetParameters parameters; + parameters.unique_name = unique_name->value.GetString(); parameters.cpu = &DebugInterface::get(m_cpu); parameters.cpu_override = cpu_override; + DebuggerWidget* widget = description->second.create_widget(parameters); JsonValueWrapper wrapper(object, json.GetAllocator()); @@ -693,13 +784,44 @@ void DockLayout::load( } m_layout_file_path = path; + + validatePrimaryDebuggerWidgets(); +} + +void DockLayout::validatePrimaryDebuggerWidgets() +{ + std::map> type_to_widgets; + for (const auto& [unique_name, widget] : m_widgets) + type_to_widgets[widget->metaObject()->className()].emplace_back(widget.get()); + + for (auto& [type, widgets] : type_to_widgets) + { + u32 primary_widgets = 0; + + // Make sure at most one widget is marked as primary. + for (DebuggerWidget* widget : widgets) + { + if (widget->isPrimary()) + { + if (primary_widgets != 0) + widget->setPrimary(false); + + primary_widgets++; + } + } + + // If none of the widgets were marked as primary, just set the first one + // as the primary one. + if (primary_widgets == 0) + widgets[0]->setPrimary(true); + } } void DockLayout::setupDefaultLayout() { - pxAssert(!m_is_frozen); + pxAssert(m_is_active); - if (m_base_layout.empty() || !g_debugger_window) + if (!g_debugger_window) return; const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout); @@ -713,15 +835,16 @@ void DockLayout::setupDefaultLayout() const DockTables::DefaultDockGroupDescription& group = base_layout->groups[static_cast(dock_description.group)]; - auto widget_iterator = m_widgets.find(QString::fromStdString(dock_description.type)); - if (widget_iterator == m_widgets.end()) + DebuggerWidget* widget = nullptr; + for (auto& [unique_name, test_widget] : m_widgets) + if (test_widget->metaObject()->className() == dock_description.type) + widget = test_widget; + + if (!widget) continue; - const QString& unique_name = widget_iterator->first; - DebuggerWidget* widget = widget_iterator->second; - auto view = static_cast( - KDDockWidgets::Config::self().viewFactory()->createDockWidget(unique_name)); + KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName())); view->setWidget(widget); if (!groups[static_cast(dock_description.group)]) @@ -742,6 +865,21 @@ void DockLayout::setupDefaultLayout() for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups()) group->setCurrentTabIndex(0); - - retranslateDockWidgets(); +} + +QString DockLayout::generateNewUniqueName(const char* type) +{ + QString name; + do + { + if (m_next_unique_name == INT_MAX) + return QString(); + + // Produce unique names that will lexicographically sort in the order + // they were allocated. This ensures the #1, #2, etc suffixes for dock + // widgets with conflicting names will be assigned in the correct order. + name = QStringLiteral("%1-%2").arg(m_next_unique_name, 16, 10, QLatin1Char('0')).arg(type); + m_next_unique_name++; + } while (hasDebuggerWidget(name)); + return name; } diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.h b/pcsx2-qt/Debugger/Docking/DockLayout.h index e851d0ffb7..a4b42f4ebf 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.h +++ b/pcsx2-qt/Debugger/Docking/DockLayout.h @@ -92,14 +92,15 @@ public: void thaw(); KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name); - void retranslateDockWidgets(); - void retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget); - void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget); + void updateDockWidgetTitles(); const std::map>& debuggerWidgets(); - bool hasDebuggerWidget(QString unique_name); - void toggleDebuggerWidget(QString unique_name); - void recreateDebuggerWidget(QString unique_name); + bool hasDebuggerWidget(const QString& unique_name); + size_t countDebuggerWidgetsOfType(const char* type); + void createDebuggerWidget(const std::string& type); + void recreateDebuggerWidget(const QString& unique_name); + void destroyDebuggerWidget(const QString& unique_name); + void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary); void deleteFile(); @@ -111,8 +112,13 @@ private: DockLayout::LoadResult& result, DockLayout::Index& index_last_session); + // Make sure there is only a single primary debugger widget of each type. + void validatePrimaryDebuggerWidgets(); + void setupDefaultLayout(); + QString generateNewUniqueName(const char* type); + // The name displayed in the user interface. Also used to determine the // file name for the layout file. std::string m_name; @@ -124,6 +130,9 @@ private: // Is this one of the default layouts? bool m_is_default = false; + // A counter used to generate new unique names for dock widgets. + int m_next_unique_name = 0; + // The name of the default layout which this layout was based on. This will // be used if the m_geometry variable above is empty. std::string m_base_layout; @@ -145,7 +154,7 @@ private: // exists exists on disk, or empty if no such file exists. std::string m_layout_file_path; - // If this layout is the currently selected layout this will be false, - // otherwise it will be true. - bool m_is_frozen = true; + // If this layout is the currently selected layout this will be true, + // otherwise it will be false. + bool m_is_active = false; }; diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp index 91e97a95f5..c1771f0d41 100644 --- a/pcsx2-qt/Debugger/Docking/DockManager.cpp +++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp @@ -315,17 +315,119 @@ void DockManager::createWindowsMenu(QMenu* menu) DockLayout& layout = m_layouts.at(m_current_layout); - for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS) + // Create a menu that allows for multiple dock widgets of the same type to + // be opened. + QMenu* add_another_menu = menu->addMenu(tr("Add Another...")); + + std::vector add_another_widgets; + std::set add_another_types; + for (const auto& [unique_name, widget] : layout.debuggerWidgets()) + { + std::string type = widget->metaObject()->className(); + + if (widget->supportsMultipleInstances() && !add_another_types.contains(type)) + { + add_another_widgets.emplace_back(widget); + add_another_types.emplace(type); + } + } + + std::sort(add_another_widgets.begin(), add_another_widgets.end(), + [](const DebuggerWidget* lhs, const DebuggerWidget* rhs) { + if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix()) + return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber(); + + return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix(); + }); + + for (DebuggerWidget* widget : add_another_widgets) + { + const char* type = widget->metaObject()->className(); + + const auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type); + pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end()); + + QAction* action = add_another_menu->addAction(description_iterator->second.display_name); + connect(action, &QAction::triggered, this, [this, type]() { + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + m_layouts.at(m_current_layout).createDebuggerWidget(type); + }); + } + + if (add_another_widgets.empty()) + add_another_menu->setDisabled(true); + + menu->addSeparator(); + + struct DebuggerWidgetToggle + { + QString display_name; + std::optional suffix_number; + QAction* action; + }; + + std::vector toggles; + std::set toggle_types; + + // Create a menu item for each open debugger widget. + for (const auto& [unique_name, widget] : layout.debuggerWidgets()) { QAction* action = new QAction(menu); - action->setText(QCoreApplication::translate("DebuggerWidget", desc.display_name)); + action->setText(widget->displayName()); action->setCheckable(true); - action->setChecked(layout.hasDebuggerWidget(type)); - connect(action, &QAction::triggered, this, [&layout, type]() { - layout.toggleDebuggerWidget(type); + action->setChecked(true); + connect(action, &QAction::triggered, this, [this, unique_name]() { + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name); }); - menu->addAction(action); + + DebuggerWidgetToggle& toggle = toggles.emplace_back(); + toggle.display_name = widget->displayNameWithoutSuffix(); + toggle.suffix_number = widget->displayNameSuffixNumber(); + toggle.action = action; + + toggle_types.emplace(widget->metaObject()->className()); } + + // Create menu items to open debugger widgets without any open instances. + for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS) + { + if (!toggle_types.contains(type)) + { + QString display_name = QCoreApplication::translate("DebuggerWidget", desc.display_name); + + QAction* action = new QAction(menu); + action->setText(display_name); + action->setCheckable(true); + action->setChecked(false); + connect(action, &QAction::triggered, this, [this, type]() { + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + m_layouts.at(m_current_layout).createDebuggerWidget(type); + }); + + DebuggerWidgetToggle& toggle = toggles.emplace_back(); + toggle.display_name = display_name; + toggle.suffix_number = std::nullopt; + toggle.action = action; + } + } + + std::sort(toggles.begin(), toggles.end(), + [](const DebuggerWidgetToggle& lhs, const DebuggerWidgetToggle& rhs) { + if (lhs.display_name == rhs.display_name) + return lhs.suffix_number < rhs.suffix_number; + + return lhs.display_name < rhs.display_name; + }); + + for (const DebuggerWidgetToggle& toggle : toggles) + menu->addAction(toggle.action); } QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar) @@ -564,20 +666,12 @@ bool DockManager::hasNameConflict(const std::string& name, DockLayout::Index lay return false; } -void DockManager::retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget) +void DockManager::updateDockWidgetTitles() { if (m_current_layout == DockLayout::INVALID_INDEX) return; - m_layouts.at(m_current_layout).retranslateDockWidget(dock_widget); -} - -void DockManager::dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget) -{ - if (m_current_layout == DockLayout::INVALID_INDEX) - return; - - m_layouts.at(m_current_layout).dockWidgetClosed(dock_widget); + m_layouts.at(m_current_layout).updateDockWidgetTitles(); } const std::map>& DockManager::debuggerWidgets() @@ -589,7 +683,15 @@ const std::map>& DockManager::debuggerWidgets( return m_layouts.at(m_current_layout).debuggerWidgets(); } -void DockManager::recreateDebuggerWidget(QString unique_name) +size_t DockManager::countDebuggerWidgetsOfType(const char* type) +{ + if (m_current_layout == DockLayout::INVALID_INDEX) + return 0; + + return m_layouts.at(m_current_layout).countDebuggerWidgetsOfType(type); +} + +void DockManager::recreateDebuggerWidget(const QString& unique_name) { if (m_current_layout == DockLayout::INVALID_INDEX) return; @@ -597,6 +699,22 @@ void DockManager::recreateDebuggerWidget(QString unique_name) m_layouts.at(m_current_layout).recreateDebuggerWidget(unique_name); } +void DockManager::destroyDebuggerWidget(const QString& unique_name) +{ + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name); +} + +void DockManager::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary) +{ + if (m_current_layout == DockLayout::INVALID_INDEX) + return; + + m_layouts.at(m_current_layout).setPrimaryDebuggerWidget(widget, is_primary); +} + void DockManager::switchToDebuggerWidget(DebuggerWidget* widget) { if (m_current_layout == DockLayout::INVALID_INDEX) @@ -613,6 +731,7 @@ void DockManager::switchToDebuggerWidget(DebuggerWidget* widget) } } + bool DockManager::isLayoutLocked() { return m_layout_locked; diff --git a/pcsx2-qt/Debugger/Docking/DockManager.h b/pcsx2-qt/Debugger/Docking/DockManager.h index 93c860833a..21451bb41a 100644 --- a/pcsx2-qt/Debugger/Docking/DockManager.h +++ b/pcsx2-qt/Debugger/Docking/DockManager.h @@ -71,11 +71,13 @@ public: bool hasNameConflict(const std::string& name, DockLayout::Index layout_index); - void retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget); - void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget); + void updateDockWidgetTitles(); const std::map>& debuggerWidgets(); - void recreateDebuggerWidget(QString unique_name); + size_t countDebuggerWidgetsOfType(const char* type); + void recreateDebuggerWidget(const QString& unique_name); + void destroyDebuggerWidget(const QString& unique_name); + void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary); void switchToDebuggerWidget(DebuggerWidget* widget); bool isLayoutLocked(); diff --git a/pcsx2-qt/Debugger/Docking/DockTables.cpp b/pcsx2-qt/Debugger/Docking/DockTables.cpp index 534b04478f..19dfbff0e1 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.cpp +++ b/pcsx2-qt/Debugger/Docking/DockTables.cpp @@ -34,7 +34,7 @@ using namespace DockUtils; } \ } -const std::map DockTables::DEBUGGER_WIDGETS = { +const std::map DockTables::DEBUGGER_WIDGETS = { DEBUGGER_WIDGET(BreakpointWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Breakpoints"), BOTTOM_MIDDLE), DEBUGGER_WIDGET(DisassemblyWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Disassembly"), TOP_RIGHT), DEBUGGER_WIDGET(FunctionTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Functions"), TOP_LEFT), diff --git a/pcsx2-qt/Debugger/Docking/DockTables.h b/pcsx2-qt/Debugger/Docking/DockTables.h index 55fe955351..d332aa988f 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.h +++ b/pcsx2-qt/Debugger/Docking/DockTables.h @@ -28,7 +28,7 @@ namespace DockTables DockUtils::PreferredLocation preferred_location; }; - extern const std::map DEBUGGER_WIDGETS; + extern const std::map DEBUGGER_WIDGETS; enum class DefaultDockGroup { diff --git a/pcsx2-qt/Debugger/Docking/DockUtils.cpp b/pcsx2-qt/Debugger/Docking/DockUtils.cpp index be4dbc3152..07eccc6391 100644 --- a/pcsx2-qt/Debugger/Docking/DockUtils.cpp +++ b/pcsx2-qt/Debugger/Docking/DockUtils.cpp @@ -8,7 +8,7 @@ #include #include -DockUtils::DockWidgetPair DockUtils::dockWidgetFromName(QString unique_name) +DockUtils::DockWidgetPair DockUtils::dockWidgetFromName(const QString& unique_name) { KDDockWidgets::Vector names{unique_name}; KDDockWidgets::Vector dock_widgets = diff --git a/pcsx2-qt/Debugger/Docking/DockUtils.h b/pcsx2-qt/Debugger/Docking/DockUtils.h index c3ea0f2411..2347b7bbc9 100644 --- a/pcsx2-qt/Debugger/Docking/DockUtils.h +++ b/pcsx2-qt/Debugger/Docking/DockUtils.h @@ -15,7 +15,7 @@ namespace DockUtils KDDockWidgets::QtWidgets::DockWidget* view = nullptr; }; - DockWidgetPair dockWidgetFromName(QString unique_name); + DockWidgetPair dockWidgetFromName(const QString& unique_name); enum PreferredLocation { diff --git a/pcsx2-qt/Debugger/Docking/DockViews.cpp b/pcsx2-qt/Debugger/Docking/DockViews.cpp index 5570963ef8..5818e95f15 100644 --- a/pcsx2-qt/Debugger/Docking/DockViews.cpp +++ b/pcsx2-qt/Debugger/Docking/DockViews.cpp @@ -12,7 +12,9 @@ #include #include -#include +#include +#include +#include KDDockWidgets::Core::View* DockViewFactory::createDockWidget( const QString& unique_name, @@ -58,14 +60,13 @@ DockWidget::DockWidget( void DockWidget::openStateChanged(bool open) { - auto view = static_cast(sender()); - - KDDockWidgets::Core::DockWidget* controller = view->asController(); - if (!controller) + // The LayoutSaver class will close a bunch of dock widgets. We only want to + // delete the dock widgets when they're being closed by the user. + if (KDDockWidgets::LayoutSaver::restoreInProgress()) return; if (!open && g_debugger_window) - g_debugger_window->dockManager().dockWidgetClosed(controller); + g_debugger_window->dockManager().destroyDebuggerWidget(uniqueName()); } // ***************************************************************************** @@ -115,104 +116,151 @@ DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent) : KDDockWidgets::QtWidgets::TabBar(controller, parent) { setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::contextMenu); + connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu); } -void DockTabBar::contextMenu(QPoint pos) +void DockTabBar::openContextMenu(QPoint pos) { - auto tab_bar = qobject_cast(sender()); - int tab_index = tab_bar->tabAt(pos); - - // Filter out the placeholder widget displayed when there are no layouts. - if (!hasDebuggerWidget(tab_index)) + if (!g_debugger_window) return; - QMenu* menu = new QMenu(tr("Dock Widget Menu"), tab_bar); + int tab_index = tabAt(pos); + + // Filter out the placeholder widget displayed when there are no layouts. + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; + + size_t dock_widgets_of_type = g_debugger_window->dockManager().countDebuggerWidgetsOfType( + widget->metaObject()->className()); + + QMenu* menu = new QMenu(tr("Dock Widget Context Menu"), this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + QAction* rename_action = menu->addAction(tr("Rename")); + connect(rename_action, &QAction::triggered, this, [this, tab_index]() { + if (!g_debugger_window) + return; + + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; + + bool ok; + QString new_name = QInputDialog::getText( + this, tr("Rename Window"), tr("New name:"), QLineEdit::Normal, widget->displayNameWithoutSuffix(), &ok); + if (!ok) + return; + + widget->setCustomDisplayName(new_name); + g_debugger_window->dockManager().updateDockWidgetTitles(); + }); + + QAction* reset_name_action = menu->addAction(tr("Reset Name")); + reset_name_action->setEnabled(!widget->customDisplayName().isEmpty()); + connect(reset_name_action, &QAction::triggered, this, [this, tab_index] { + if (!g_debugger_window) + return; + + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; + + widget->setCustomDisplayName(QString()); + g_debugger_window->dockManager().updateDockWidgetTitles(); + }); + + QAction* primary_action = menu->addAction(tr("Primary")); + primary_action->setCheckable(true); + primary_action->setChecked(widget->isPrimary()); + primary_action->setEnabled(dock_widgets_of_type > 1); + connect(primary_action, &QAction::triggered, this, [this, tab_index](bool checked) { + if (!g_debugger_window) + return; + + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; + + g_debugger_window->dockManager().setPrimaryDebuggerWidget(widget, checked); + }); QMenu* set_target_menu = menu->addMenu(tr("Set Target")); + QActionGroup* set_target_group = new QActionGroup(menu); + set_target_group->setExclusive(true); for (BreakPointCpu cpu : DEBUG_CPUS) { const char* long_cpu_name = DebugInterface::longCpuName(cpu); 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(); - if (!tab_bar_controller) - return; - KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index); - if (!dock_controller) - return; - - KDDockWidgets::QtWidgets::DockWidget* dock_view = - static_cast(dock_controller->view()); - - DebuggerWidget* widget = qobject_cast(dock_view->widget()); - if (!widget) - return; - - if (!g_debugger_window) - return; - - if (!widget->setCpuOverride(cpu)) - g_debugger_window->dockManager().recreateDebuggerWidget(dock_view->uniqueName()); - - g_debugger_window->dockManager().retranslateDockWidget(dock_controller); + QAction* cpu_action = set_target_menu->addAction(text); + cpu_action->setCheckable(true); + cpu_action->setChecked(widget->cpuOverride().has_value() && *widget->cpuOverride() == cpu); + connect(cpu_action, &QAction::triggered, this, [this, tab_index, cpu]() { + setCpuOverrideForTab(tab_index, cpu); }); - set_target_menu->addAction(action); + set_target_group->addAction(cpu_action); } set_target_menu->addSeparator(); - QAction* inherit_action = new QAction(tr("Inherit From Layout"), menu); - connect(inherit_action, &QAction::triggered, this, [tab_bar, tab_index]() { - KDDockWidgets::Core::TabBar* tab_bar_controller = tab_bar->asController(); - if (!tab_bar_controller) - return; - - KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index); - if (!dock_controller) - return; - - KDDockWidgets::QtWidgets::DockWidget* dock_view = - static_cast(dock_controller->view()); - - DebuggerWidget* widget = qobject_cast(dock_view->widget()); - if (!widget) - return; + QAction* inherit_action = set_target_menu->addAction(tr("Inherit From Layout")); + inherit_action->setCheckable(true); + inherit_action->setChecked(!widget->cpuOverride().has_value()); + connect(inherit_action, &QAction::triggered, this, [this, tab_index]() { + setCpuOverrideForTab(tab_index, std::nullopt); + }); + set_target_group->addAction(inherit_action); + QAction* close_action = menu->addAction(tr("Close")); + connect(close_action, &QAction::triggered, this, [this, tab_index]() { if (!g_debugger_window) return; - if (!widget->setCpuOverride(std::nullopt)) - g_debugger_window->dockManager().recreateDebuggerWidget(dock_view->uniqueName()); + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; - g_debugger_window->dockManager().retranslateDockWidget(dock_controller); + g_debugger_window->dockManager().destroyDebuggerWidget(widget->uniqueName()); }); - set_target_menu->addAction(inherit_action); - menu->popup(tab_bar->mapToGlobal(pos)); + menu->popup(mapToGlobal(pos)); } -bool DockTabBar::hasDebuggerWidget(int tab_index) +void DockTabBar::setCpuOverrideForTab(int tab_index, std::optional cpu_override) +{ + if (!g_debugger_window) + return; + + auto [widget, controller, view] = widgetsFromTabIndex(tab_index); + if (!widget) + return; + + if (!widget->setCpuOverride(cpu_override)) + g_debugger_window->dockManager().recreateDebuggerWidget(view->uniqueName()); + + g_debugger_window->dockManager().updateDockWidgetTitles(); +} + +DockTabBar::WidgetsFromTabIndexResult DockTabBar::widgetsFromTabIndex(int tab_index) { KDDockWidgets::Core::TabBar* tab_bar_controller = asController(); if (!tab_bar_controller) - return false; + return {}; KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index); if (!dock_controller) - return false; + return {}; auto dock_view = static_cast(dock_controller->view()); DebuggerWidget* widget = qobject_cast(dock_view->widget()); if (!widget) - return false; + return {}; - return true; + return {widget, dock_controller, dock_view}; } void DockTabBar::mouseDoubleClickEvent(QMouseEvent* ev) diff --git a/pcsx2-qt/Debugger/Docking/DockViews.h b/pcsx2-qt/Debugger/Docking/DockViews.h index 12b26edfbd..eea0ad85f9 100644 --- a/pcsx2-qt/Debugger/Docking/DockViews.h +++ b/pcsx2-qt/Debugger/Docking/DockViews.h @@ -9,6 +9,9 @@ #include #include +#include "DebugTools/DebugInterface.h" + +class DebuggerWidget; class DockManager; class DockViewFactory : public KDDockWidgets::QtWidgets::ViewFactory @@ -82,8 +85,17 @@ public: DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent = nullptr); protected: - void contextMenu(QPoint pos); - bool hasDebuggerWidget(int tab_index); + void openContextMenu(QPoint pos); + + struct WidgetsFromTabIndexResult + { + DebuggerWidget* debugger_widget = nullptr; + KDDockWidgets::Core::DockWidget* controller = nullptr; + KDDockWidgets::QtWidgets::DockWidget* view = nullptr; + }; + + void setCpuOverrideForTab(int tab_index, std::optional cpu_override); + WidgetsFromTabIndexResult widgetsFromTabIndex(int tab_index); void mouseDoubleClickEvent(QMouseEvent* ev) override; }; diff --git a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp index 6a36b745b8..9a5968e043 100644 --- a/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp @@ -24,7 +24,7 @@ using SearchResult = MemorySearchWidget::SearchResult; using namespace QtUtils; MemorySearchWidget::MemorySearchWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) { m_ui.setupUi(this); this->repaint(); diff --git a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp index 5469444747..c501625880 100644 --- a/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp @@ -452,7 +452,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu) MemoryViewWidget */ MemoryViewWidget::MemoryViewWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) , m_table(this) { ui.setupUi(this); diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp index a9e5e2cba5..0d135d1431 100644 --- a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp @@ -10,7 +10,7 @@ #include SavedAddressesWidget::SavedAddressesWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES) , m_model(new SavedAddressesModel(cpu(), this)) { m_ui.setupUi(this); diff --git a/pcsx2-qt/Debugger/RegisterWidget.cpp b/pcsx2-qt/Debugger/RegisterWidget.cpp index c4f9157be1..303097a1d7 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.cpp +++ b/pcsx2-qt/Debugger/RegisterWidget.cpp @@ -20,7 +20,7 @@ using namespace QtUtils; RegisterWidget::RegisterWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) { this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); diff --git a/pcsx2-qt/Debugger/StackWidget.cpp b/pcsx2-qt/Debugger/StackWidget.cpp index d68433bef9..6528f1a080 100644 --- a/pcsx2-qt/Debugger/StackWidget.cpp +++ b/pcsx2-qt/Debugger/StackWidget.cpp @@ -9,7 +9,7 @@ #include StackWidget::StackWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) , m_model(new StackModel(cpu())) { m_ui.setupUi(this); diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp index a5e03581ee..e3fcbeaf28 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -18,7 +18,7 @@ SymbolTreeWidget::SymbolTreeWidget( u32 flags, s32 symbol_address_alignment, const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) , m_flags(flags) , m_symbol_address_alignment(symbol_address_alignment) , m_group_by_module(cpu().getCpuType() == BREAKPOINT_IOP) diff --git a/pcsx2-qt/Debugger/ThreadWidget.cpp b/pcsx2-qt/Debugger/ThreadWidget.cpp index 4c37ad65e6..69075d7706 100644 --- a/pcsx2-qt/Debugger/ThreadWidget.cpp +++ b/pcsx2-qt/Debugger/ThreadWidget.cpp @@ -9,7 +9,7 @@ #include ThreadWidget::ThreadWidget(const DebuggerWidgetParameters& parameters) - : DebuggerWidget(parameters) + : DebuggerWidget(parameters, NO_DEBUGGER_FLAGS) , m_model(new ThreadModel(cpu())) , m_proxy_model(new QSortFilterProxyModel()) { diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index 170e5dc4da..e0097c498e 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -33,7 +33,7 @@ enum BreakPointCpu BREAKPOINT_IOP_AND_EE = 0x03 }; -inline std::vector DEBUG_CPUS = { +inline const std::array DEBUG_CPUS = { BREAKPOINT_EE, BREAKPOINT_IOP, };