mirror of https://github.com/PCSX2/pcsx2.git
Debugger: Add support for multiple UI layouts
This commit is contained in:
parent
c76cca874b
commit
59210dffa9
|
@ -168,8 +168,7 @@ target_sources(pcsx2-qt PRIVATE
|
|||
Debugger/DisassemblyWidget.cpp
|
||||
Debugger/DisassemblyWidget.h
|
||||
Debugger/DisassemblyWidget.ui
|
||||
Debugger/DockManager.cpp
|
||||
Debugger/DockManager.h
|
||||
Debugger/JsonValueWrapper.h
|
||||
Debugger/RegisterWidget.cpp
|
||||
Debugger/RegisterWidget.h
|
||||
Debugger/RegisterWidget.ui
|
||||
|
@ -189,6 +188,22 @@ target_sources(pcsx2-qt PRIVATE
|
|||
Debugger/Breakpoints/BreakpointWidget.cpp
|
||||
Debugger/Breakpoints/BreakpointWidget.h
|
||||
Debugger/Breakpoints/BreakpointWidget.ui
|
||||
Debugger/Docking/DockLayout.cpp
|
||||
Debugger/Docking/DockLayout.h
|
||||
Debugger/Docking/DockManager.cpp
|
||||
Debugger/Docking/DockManager.h
|
||||
Debugger/Docking/DockTables.cpp
|
||||
Debugger/Docking/DockTables.h
|
||||
Debugger/Docking/DockUtils.cpp
|
||||
Debugger/Docking/DockUtils.h
|
||||
Debugger/Docking/DockViews.cpp
|
||||
Debugger/Docking/DockViews.h
|
||||
Debugger/Docking/LayoutEditorDialog.cpp
|
||||
Debugger/Docking/LayoutEditorDialog.h
|
||||
Debugger/Docking/LayoutEditorDialog.ui
|
||||
Debugger/Docking/NoLayoutsWidget.cpp
|
||||
Debugger/Docking/NoLayoutsWidget.h
|
||||
Debugger/Docking/NoLayoutsWidget.ui
|
||||
Debugger/Memory/MemorySearchWidget.cpp
|
||||
Debugger/Memory/MemorySearchWidget.h
|
||||
Debugger/Memory/MemorySearchWidget.ui
|
||||
|
|
|
@ -3,20 +3,67 @@
|
|||
|
||||
#include "DebuggerWidget.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "JsonValueWrapper.h"
|
||||
|
||||
DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_cpu(cpu)
|
||||
{
|
||||
}
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
|
||||
DebugInterface& DebuggerWidget::cpu() const
|
||||
{
|
||||
pxAssertRel(m_cpu, "DebuggerWidget::cpu() called on object that doesn't have a CPU type set.");
|
||||
if (m_cpu_override.has_value())
|
||||
return DebugInterface::get(*m_cpu_override);
|
||||
|
||||
pxAssertRel(m_cpu, "DebuggerWidget::cpu called on object with null cpu.");
|
||||
return *m_cpu;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::setCpu(DebugInterface& new_cpu)
|
||||
{
|
||||
BreakPointCpu before = cpu().getCpuType();
|
||||
m_cpu = &new_cpu;
|
||||
BreakPointCpu after = cpu().getCpuType();
|
||||
return before == after;
|
||||
}
|
||||
|
||||
std::optional<BreakPointCpu> DebuggerWidget::cpuOverride() const
|
||||
{
|
||||
return m_cpu_override;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::setCpuOverride(std::optional<BreakPointCpu> new_cpu)
|
||||
{
|
||||
BreakPointCpu before = cpu().getCpuType();
|
||||
m_cpu_override = new_cpu;
|
||||
BreakPointCpu after = cpu().getCpuType();
|
||||
return before == after;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void DebuggerWidget::applyMonospaceFont()
|
||||
{
|
||||
// Easiest way to handle cross platform monospace fonts
|
||||
|
@ -29,3 +76,9 @@ void DebuggerWidget::applyMonospaceFont()
|
|||
setStyleSheet(QStringLiteral("font: 10pt 'Monospace'"));
|
||||
#endif
|
||||
}
|
||||
|
||||
DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_cpu(cpu)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -12,17 +12,39 @@ inline void not_yet_implemented()
|
|||
abort();
|
||||
}
|
||||
|
||||
class JsonValueWrapper;
|
||||
|
||||
// The base class for the contents of the dock widgets in the debugger.
|
||||
class DebuggerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr);
|
||||
|
||||
public:
|
||||
// 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.
|
||||
DebugInterface& cpu() const;
|
||||
|
||||
// Set the debug interface associated with the layout. If false is returned,
|
||||
// we have to recreate the object.
|
||||
bool setCpu(DebugInterface& new_cpu);
|
||||
|
||||
// Get the CPU associated with this particular widget.
|
||||
std::optional<BreakPointCpu> cpuOverride() const;
|
||||
|
||||
// Set the CPU associated with the individual dock widget. If false is
|
||||
// returned, we have to recreate the object.
|
||||
bool setCpuOverride(std::optional<BreakPointCpu> new_cpu);
|
||||
|
||||
virtual void toJson(JsonValueWrapper& json);
|
||||
virtual bool fromJson(JsonValueWrapper& json);
|
||||
|
||||
void applyMonospaceFont();
|
||||
|
||||
protected:
|
||||
DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
DebugInterface* m_cpu;
|
||||
std::optional<BreakPointCpu> m_cpu_override;
|
||||
};
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "DebuggerWindow.h"
|
||||
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
#include "DebugTools/SymbolImporter.h"
|
||||
|
@ -11,12 +13,20 @@
|
|||
#include "MainWindow.h"
|
||||
#include "AnalysisOptionsDialog.h"
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
DebuggerWindow* g_debugger_window = nullptr;
|
||||
|
||||
DebuggerWindow::DebuggerWindow(QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::MainWindow(QStringLiteral("DebuggerWindow"), {}, parent)
|
||||
, m_dock_manager(this)
|
||||
, m_dock_manager(new DockManager(this))
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
g_debugger_window = this;
|
||||
|
||||
m_dock_manager->loadLayouts();
|
||||
|
||||
connect(m_ui.actionRun, &QAction::triggered, this, &DebuggerWindow::onRunPause);
|
||||
connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepInto);
|
||||
connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOver);
|
||||
|
@ -24,27 +34,71 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
|
|||
connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse);
|
||||
connect(m_ui.actionOnTop, &QAction::triggered, [this] { this->setWindowFlags(this->windowFlags() ^ Qt::WindowStaysOnTopHint); this->show(); });
|
||||
|
||||
connect(m_ui.menuWindows, &QMenu::aboutToShow, this, [this]() {
|
||||
m_dock_manager->createWindowsMenu(m_ui.menuWindows);
|
||||
});
|
||||
|
||||
connect(m_ui.actionResetAllLayouts, &QAction::triggered, [this]() {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
g_debugger_window, tr("Confirmation"), tr("Are you sure you want to reset all layouts?"));
|
||||
|
||||
if (result == QMessageBox::Yes)
|
||||
m_dock_manager->resetAllLayouts();
|
||||
});
|
||||
|
||||
connect(m_ui.actionResetDefaultLayouts, &QAction::triggered, [this]() {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
g_debugger_window, tr("Confirmation"), tr("Are you sure you want to reset the default layouts?"));
|
||||
|
||||
if (result == QMessageBox::Yes)
|
||||
m_dock_manager->resetDefaultLayouts();
|
||||
});
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMStateChanged);
|
||||
connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMStateChanged);
|
||||
|
||||
onVMStateChanged(); // If we missed a state change while we weren't loaded
|
||||
|
||||
// We can't do this in the designer, but we want to right align the actionOnTop action in the toolbar
|
||||
//QWidget* spacer = new QWidget(this);
|
||||
//spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
//m_ui.toolBar->insertWidget(m_ui.actionAnalyse, spacer);
|
||||
m_dock_manager->switchToLayout(0);
|
||||
|
||||
//m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900");
|
||||
//m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000");
|
||||
QMenuBar* menu_bar = menuBar();
|
||||
|
||||
m_dock_manager.switchToLayout(0);
|
||||
setMenuWidget(m_dock_manager->createLayoutSwitcher(menu_bar));
|
||||
|
||||
//QTabBar* tabs = new QTabBar();
|
||||
//tabs->addTab("Test");
|
||||
//m_ui.menuBar->layout()->addWidget(tabs);
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerOpened();
|
||||
});
|
||||
}
|
||||
|
||||
DebuggerWindow::~DebuggerWindow() = default;
|
||||
DebuggerWindow* DebuggerWindow::getInstance()
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
createInstance();
|
||||
|
||||
return g_debugger_window;
|
||||
}
|
||||
|
||||
DebuggerWindow* DebuggerWindow::createInstance()
|
||||
{
|
||||
// Setup KDDockWidgets.
|
||||
DockManager::configureDockingSystem();
|
||||
|
||||
if (g_debugger_window)
|
||||
destroyInstance();
|
||||
|
||||
return new DebuggerWindow(nullptr);
|
||||
}
|
||||
|
||||
void DebuggerWindow::destroyInstance()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
g_debugger_window->close();
|
||||
}
|
||||
|
||||
DockManager& DebuggerWindow::dockManager()
|
||||
{
|
||||
return *m_dock_manager;
|
||||
}
|
||||
|
||||
// There is no straightforward way to set the tab text to bold in Qt
|
||||
// Sorry colour blind people, but this is the best we can do for now
|
||||
|
@ -131,18 +185,16 @@ void DebuggerWindow::onAnalyse()
|
|||
dialog->show();
|
||||
}
|
||||
|
||||
void DebuggerWindow::showEvent(QShowEvent* event)
|
||||
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerOpened();
|
||||
});
|
||||
QMainWindow::showEvent(event);
|
||||
}
|
||||
dockManager().saveCurrentLayout();
|
||||
|
||||
void DebuggerWindow::hideEvent(QHideEvent* event)
|
||||
{
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerClosed();
|
||||
});
|
||||
QMainWindow::hideEvent(event);
|
||||
|
||||
KDDockWidgets::QtWidgets::MainWindow::closeEvent(event);
|
||||
|
||||
g_debugger_window = nullptr;
|
||||
deleteLater();
|
||||
}
|
||||
|
|
|
@ -5,17 +5,24 @@
|
|||
|
||||
#include "ui_DebuggerWindow.h"
|
||||
|
||||
#include "DockManager.h"
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
|
||||
class DockManager;
|
||||
|
||||
class DebuggerWindow : public KDDockWidgets::QtWidgets::MainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DebuggerWindow(QWidget* parent);
|
||||
~DebuggerWindow();
|
||||
|
||||
static DebuggerWindow* getInstance();
|
||||
static DebuggerWindow* createInstance();
|
||||
static void destroyInstance();
|
||||
|
||||
DockManager& dockManager();
|
||||
|
||||
public slots:
|
||||
void onVMStateChanged();
|
||||
|
@ -26,8 +33,7 @@ public slots:
|
|||
void onAnalyse();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event);
|
||||
void hideEvent(QHideEvent* event);
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
private:
|
||||
Ui::DebuggerWindow m_ui;
|
||||
|
@ -36,7 +42,9 @@ private:
|
|||
QAction* m_actionStepOver;
|
||||
QAction* m_actionStepOut;
|
||||
|
||||
DockManager m_dock_manager;
|
||||
DockManager* m_dock_manager;
|
||||
|
||||
void setTabActiveStyle(BreakPointCpu toggledCPU);
|
||||
};
|
||||
|
||||
extern DebuggerWindow* g_debugger_window;
|
||||
|
|
|
@ -77,17 +77,19 @@
|
|||
<string>Windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuLayouts">
|
||||
<property name="title">
|
||||
<string>Layouts</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView">
|
||||
<property name="title">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<addaction name="actionOnTop"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuLayouts">
|
||||
<property name="title">
|
||||
<string>Layouts</string>
|
||||
</property>
|
||||
<addaction name="actionResetAllLayouts"/>
|
||||
<addaction name="actionResetDefaultLayouts"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuDebug"/>
|
||||
|
@ -169,6 +171,21 @@
|
|||
<string>Analyze</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetAllLayouts">
|
||||
<property name="text">
|
||||
<string>Reset All Layouts</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetDefaultLayouts">
|
||||
<property name="text">
|
||||
<string>Reset Default Layouts</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetSplitterPositions">
|
||||
<property name="text">
|
||||
<string>Reset Splitter Positions</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockManager.h"
|
||||
|
||||
#include "DebuggerWindow.h"
|
||||
#include "DisassemblyWidget.h"
|
||||
#include "RegisterWidget.h"
|
||||
#include "StackWidget.h"
|
||||
#include "ThreadWidget.h"
|
||||
#include "Breakpoints/BreakpointWidget.h"
|
||||
#include "Memory/MemorySearchWidget.h"
|
||||
#include "Memory/MemoryViewWidget.h"
|
||||
#include "Memory/SavedAddressesWidget.h"
|
||||
#include "SymbolTree/SymbolTreeWidgets.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QtTranslation>
|
||||
|
||||
#define FOR_EACH_DEBUGGER_DOCK_WIDGET \
|
||||
/* Top right. */ \
|
||||
X(DisassemblyWidget, QT_TRANSLATE_NOOP("DockWidget", "Disassembly"), OnRight, Root) \
|
||||
/* Bottom. */ \
|
||||
X(MemoryViewWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory"), OnBottom, DisassemblyWidget) \
|
||||
X(BreakpointWidget, QT_TRANSLATE_NOOP("DockWidget", "Breakpoints"), None, MemoryViewWidget) \
|
||||
X(ThreadWidget, QT_TRANSLATE_NOOP("DockWidget", "Threads"), None, MemoryViewWidget) \
|
||||
X(StackWidget, QT_TRANSLATE_NOOP("DockWidget", "Stack"), None, MemoryViewWidget) \
|
||||
X(SavedAddressesWidget, QT_TRANSLATE_NOOP("DockWidget", "Saved Addresses"), None, MemoryViewWidget) \
|
||||
X(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Globals"), None, MemoryViewWidget) \
|
||||
X(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Locals"), None, MemoryViewWidget) \
|
||||
X(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Parameters"), None, MemoryViewWidget) \
|
||||
/* Top left. */ \
|
||||
X(RegisterWidget, QT_TRANSLATE_NOOP("DockWidget", "Registers"), OnLeft, DisassemblyWidget) \
|
||||
X(FunctionTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Functions"), None, RegisterWidget) \
|
||||
X(MemorySearchWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory Search"), None, RegisterWidget)
|
||||
|
||||
DockManager::DockManager(DebuggerWindow* window)
|
||||
: m_window(window)
|
||||
{
|
||||
createDefaultLayout("R5900", r5900Debug);
|
||||
//createDefaultLayout("R3000", r3000Debug);
|
||||
loadLayouts();
|
||||
}
|
||||
|
||||
void DockManager::configure_docking_system()
|
||||
{
|
||||
KDDockWidgets::Config::self().setFlags(
|
||||
KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible |
|
||||
KDDockWidgets::Config::Flag_AlwaysShowTabs |
|
||||
KDDockWidgets::Config::Flag_AllowReorderTabs |
|
||||
KDDockWidgets::Config::Flag_TabsHaveCloseButton |
|
||||
KDDockWidgets::Config::Flag_TitleBarIsFocusable);
|
||||
}
|
||||
|
||||
const std::vector<DockManager::Layout>& DockManager::layouts()
|
||||
{
|
||||
return m_layouts;
|
||||
}
|
||||
|
||||
void DockManager::switchToLayout(size_t layout)
|
||||
{
|
||||
//m_layouts.at(m_current_layout).dock_manager->setParent(nullptr);
|
||||
//m_window->setCentralWidget(m_layouts.at(layout).dock_manager);
|
||||
//m_current_layout = layout;
|
||||
}
|
||||
|
||||
size_t DockManager::cloneLayout(size_t existing_layout, std::string new_name)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DockManager::deleteLayout(size_t layout)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void DockManager::loadLayouts()
|
||||
{
|
||||
}
|
||||
|
||||
void DockManager::saveLayouts()
|
||||
{
|
||||
}
|
||||
|
||||
size_t DockManager::createDefaultLayout(const char* name, DebugInterface& cpu)
|
||||
{
|
||||
size_t index = m_layouts.size();
|
||||
|
||||
Layout& layout = m_layouts.emplace_back();
|
||||
layout.name = name;
|
||||
layout.cpu = cpu.getCpuType();
|
||||
layout.user_defined = false;
|
||||
|
||||
KDDockWidgets::QtWidgets::DockWidget* dock_Root = nullptr;
|
||||
#define X(Type, title, Location, Parent) \
|
||||
KDDockWidgets::QtWidgets::DockWidget* dock_##Type = new KDDockWidgets::QtWidgets::DockWidget(title); \
|
||||
dock_##Type->setWidget(new Type(cpu)); \
|
||||
if (KDDockWidgets::Location_##Location != KDDockWidgets::Location_None) \
|
||||
m_window->addDockWidget(dock_##Type, KDDockWidgets::Location_##Location, dock_##Parent); \
|
||||
else \
|
||||
dock_##Parent->addDockWidgetAsTab(dock_##Type);
|
||||
FOR_EACH_DEBUGGER_DOCK_WIDGET
|
||||
#undef X
|
||||
|
||||
return index;
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
|
||||
class DebuggerWindow;
|
||||
|
||||
class DockManager
|
||||
{
|
||||
public:
|
||||
struct Layout
|
||||
{
|
||||
std::string name;
|
||||
BreakPointCpu cpu;
|
||||
bool user_defined = false;
|
||||
};
|
||||
|
||||
DockManager(DebuggerWindow* window);
|
||||
|
||||
static void configure_docking_system();
|
||||
|
||||
const std::vector<Layout>& layouts();
|
||||
void switchToLayout(size_t layout);
|
||||
size_t cloneLayout(size_t existing_layout, std::string new_name);
|
||||
bool deleteLayout(size_t layout);
|
||||
|
||||
void loadLayouts();
|
||||
void saveLayouts();
|
||||
|
||||
protected:
|
||||
size_t createDefaultLayout(const char* name, DebugInterface& cpu);
|
||||
|
||||
KDDockWidgets::QtWidgets::MainWindow* m_window;
|
||||
|
||||
std::vector<Layout> m_layouts;
|
||||
size_t m_current_layout = 0;
|
||||
};
|
|
@ -0,0 +1,682 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockLayout.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/LayoutSaver.h>
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/core/Layout.h>
|
||||
#include <kddockwidgets/core/ViewFactory.h>
|
||||
#include <kddockwidgets/qtwidgets/Group.h>
|
||||
#include <kddockwidgets/qtwidgets/MainWindow.h>
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
|
||||
const char* DEBUGGER_LAYOUT_FILE_FORMAT = "PCSX2 Debugger User Interface Layout";
|
||||
|
||||
// Increment this whenever there is a breaking change to the JSON format.
|
||||
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR = 1;
|
||||
|
||||
// Increment this whenever there is a non-breaking change to the JSON format.
|
||||
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR = 0;
|
||||
|
||||
DockLayout::DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockTables::DefaultDockLayout& default_layout,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, 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(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);
|
||||
m_widgets.emplace(default_layout.widgets[i].type, widget);
|
||||
}
|
||||
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, m_is_default(is_default)
|
||||
{
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockLayout& layout_to_clone,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, m_is_default(is_default)
|
||||
, m_base_layout(layout_to_clone.m_base_layout)
|
||||
, m_geometry(layout_to_clone.m_geometry)
|
||||
{
|
||||
for (const auto& [unique_name, widget_to_clone] : layout_to_clone.m_widgets)
|
||||
{
|
||||
auto widget_description = DockTables::DEBUGGER_WIDGETS.find(widget_to_clone->metaObject()->className());
|
||||
if (widget_description == DockTables::DEBUGGER_WIDGETS.end())
|
||||
continue;
|
||||
|
||||
DebuggerWidget* new_widget = widget_description->second.create_widget(DebugInterface::get(cpu));
|
||||
m_widgets.emplace(unique_name, new_widget);
|
||||
}
|
||||
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
const std::string& path,
|
||||
DockLayout::LoadResult& result,
|
||||
DockLayout::Index& index_last_session,
|
||||
DockLayout::Index index)
|
||||
{
|
||||
load(path, result, index_last_session);
|
||||
}
|
||||
|
||||
DockLayout::~DockLayout()
|
||||
{
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
delete widget;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& DockLayout::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void DockLayout::setName(std::string name)
|
||||
{
|
||||
m_name = std::move(name);
|
||||
}
|
||||
|
||||
BreakPointCpu DockLayout::cpu() const
|
||||
{
|
||||
return m_cpu;
|
||||
}
|
||||
|
||||
bool DockLayout::isDefault() const
|
||||
{
|
||||
return m_is_default;
|
||||
}
|
||||
|
||||
void DockLayout::setCpu(BreakPointCpu cpu)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
if (!widget->setCpu(DebugInterface::get(cpu)))
|
||||
recreateDebuggerWidget(unique_name);
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::freeze()
|
||||
{
|
||||
pxAssert(!m_is_frozen);
|
||||
m_is_frozen = true;
|
||||
|
||||
// Store the geometry of all the dock widgets as JSON.
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
m_geometry = saver.serializeLayout();
|
||||
|
||||
// Delete the dock widgets.
|
||||
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
|
||||
{
|
||||
// Make sure the dock widget releases ownership of its content.
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
|
||||
view->setWidget(new QWidget());
|
||||
|
||||
delete dock;
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::thaw(DebuggerWindow* window)
|
||||
{
|
||||
pxAssert(m_is_frozen);
|
||||
m_is_frozen = false;
|
||||
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
|
||||
if (m_geometry.isEmpty())
|
||||
{
|
||||
// This is a newly created layout with no geometry information.
|
||||
setupDefaultLayout(window);
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the geometry of the dock widgets we just recreated.
|
||||
if (!saver.restoreLayout(m_geometry))
|
||||
{
|
||||
// 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<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
|
||||
view->setWidget(new QWidget());
|
||||
|
||||
delete dock;
|
||||
}
|
||||
|
||||
setupDefaultLayout(window);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that all the dock widgets have been restored correctly.
|
||||
std::vector<QString> orphaned_debugger_widgets;
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
if (!controller || !view)
|
||||
{
|
||||
Console.Error("Debugger: Failed to restore dock widget '%s'.", unique_name.toStdString().c_str());
|
||||
orphaned_debugger_widgets.emplace_back(unique_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any debugger widgets that haven't been restored correctly.
|
||||
for (const QString& unique_name : orphaned_debugger_widgets)
|
||||
{
|
||||
auto widget_iterator = m_widgets.find(unique_name);
|
||||
delete widget_iterator->second.get();
|
||||
m_widgets.erase(widget_iterator);
|
||||
}
|
||||
|
||||
retranslateDockWidgets();
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& name)
|
||||
{
|
||||
pxAssert(!m_is_frozen);
|
||||
pxAssert(KDDockWidgets::LayoutSaver::restoreInProgress());
|
||||
|
||||
auto widget_iterator = m_widgets.find(name);
|
||||
if (widget_iterator == m_widgets.end())
|
||||
return nullptr;
|
||||
|
||||
DebuggerWidget* widget = widget_iterator->second;
|
||||
pxAssert(widget);
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(name));
|
||||
view->setWidget(widget);
|
||||
|
||||
return view->asController<KDDockWidgets::Core::DockWidget>();
|
||||
}
|
||||
|
||||
void DockLayout::retranslateDockWidgets()
|
||||
{
|
||||
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())
|
||||
return;
|
||||
|
||||
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<BreakPointCpu> 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));
|
||||
}
|
||||
else
|
||||
{
|
||||
dock_widget->setTitle(std::move(translated_title));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool DockLayout::hasDebuggerWidget(QString unique_name)
|
||||
{
|
||||
return m_widgets.find(unique_name) != m_widgets.end();
|
||||
}
|
||||
|
||||
void DockLayout::toggleDebuggerWidget(QString unique_name, DebuggerWindow* window)
|
||||
{
|
||||
pxAssert(!m_is_frozen);
|
||||
|
||||
auto debugger_widget_iterator = m_widgets.find(unique_name);
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
|
||||
if (debugger_widget_iterator == m_widgets.end())
|
||||
{
|
||||
// Create the dock widget.
|
||||
if (controller)
|
||||
return;
|
||||
|
||||
auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(unique_name);
|
||||
if (description_iterator == DockTables::DEBUGGER_WIDGETS.end())
|
||||
return;
|
||||
|
||||
const DockTables::DebuggerWidgetDescription& description = description_iterator->second;
|
||||
|
||||
DebuggerWidget* widget = description.create_widget(DebugInterface::get(m_cpu));
|
||||
m_widgets.emplace(unique_name, widget);
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(unique_name));
|
||||
view->setWidget(widget);
|
||||
|
||||
KDDockWidgets::Core::DockWidget* controller = view->asController<KDDockWidgets::Core::DockWidget>();
|
||||
if (!controller)
|
||||
{
|
||||
delete view;
|
||||
return;
|
||||
}
|
||||
|
||||
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, window);
|
||||
retranslateDockWidget(controller);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete the dock widget.
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
m_widgets.erase(debugger_widget_iterator);
|
||||
delete controller;
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::recreateDebuggerWidget(QString unique_name)
|
||||
{
|
||||
pxAssert(!m_is_frozen);
|
||||
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
if (!controller || !view)
|
||||
return;
|
||||
|
||||
auto debugger_widget_iterator = m_widgets.find(unique_name);
|
||||
if (debugger_widget_iterator == m_widgets.end())
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
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());
|
||||
debugger_widget_iterator->second = new_debugger_widget;
|
||||
|
||||
view->setWidget(new_debugger_widget);
|
||||
|
||||
delete old_debugger_widget;
|
||||
}
|
||||
|
||||
void DockLayout::deleteFile()
|
||||
{
|
||||
if (m_layout_file_path.empty())
|
||||
return;
|
||||
|
||||
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 (!m_is_frozen)
|
||||
{
|
||||
// Store the geometry of all the dock widgets as JSON.
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
m_geometry = saver.serializeLayout();
|
||||
}
|
||||
|
||||
// Serialize the layout as JSON.
|
||||
rapidjson::Document json(rapidjson::kObjectType);
|
||||
rapidjson::Document geometry;
|
||||
|
||||
const char* cpu_name = DebugInterface::cpuName(m_cpu);
|
||||
const std::string& default_layouts_hash = DockTables::hashDefaultLayouts();
|
||||
|
||||
rapidjson::Value format;
|
||||
format.SetString(DEBUGGER_LAYOUT_FILE_FORMAT, strlen(DEBUGGER_LAYOUT_FILE_FORMAT));
|
||||
json.AddMember("format", format, json.GetAllocator());
|
||||
|
||||
json.AddMember("version_major", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator());
|
||||
json.AddMember("version_minor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator());
|
||||
rapidjson::Value version_hash;
|
||||
version_hash.SetString(default_layouts_hash.c_str(), default_layouts_hash.size());
|
||||
json.AddMember("version_hash", version_hash, json.GetAllocator());
|
||||
|
||||
json.AddMember("name", rapidjson::Value().SetString(m_name.c_str(), m_name.size()), json.GetAllocator());
|
||||
json.AddMember("target", rapidjson::Value().SetString(cpu_name, strlen(cpu_name)), json.GetAllocator());
|
||||
json.AddMember("index", static_cast<int>(layout_index), json.GetAllocator());
|
||||
json.AddMember("isDefault", m_is_default, json.GetAllocator());
|
||||
|
||||
if (!m_base_layout.empty())
|
||||
{
|
||||
rapidjson::Value base_layout;
|
||||
base_layout.SetString(m_base_layout.c_str(), m_base_layout.size());
|
||||
json.AddMember("baseLayout", base_layout, json.GetAllocator());
|
||||
}
|
||||
|
||||
rapidjson::Value widgets(rapidjson::kArrayType);
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
rapidjson::Value object(rapidjson::kObjectType);
|
||||
|
||||
std::string name_str = unique_name.toStdString();
|
||||
rapidjson::Value name;
|
||||
name.SetString(name_str.c_str(), name_str.size(), json.GetAllocator());
|
||||
object.AddMember("uniqueName", name, json.GetAllocator());
|
||||
|
||||
const char* type_str = widget->metaObject()->className();
|
||||
rapidjson::Value type;
|
||||
type.SetString(type_str, strlen(type_str), json.GetAllocator());
|
||||
object.AddMember("type", type, json.GetAllocator());
|
||||
|
||||
JsonValueWrapper wrapper(object, json.GetAllocator());
|
||||
widget->toJson(wrapper);
|
||||
|
||||
widgets.PushBack(object, json.GetAllocator());
|
||||
}
|
||||
json.AddMember("widgets", widgets, json.GetAllocator());
|
||||
|
||||
if (!m_geometry.isEmpty() && !geometry.Parse(m_geometry).HasParseError())
|
||||
json.AddMember("geometry", geometry, json.GetAllocator());
|
||||
|
||||
rapidjson::StringBuffer string_buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(string_buffer);
|
||||
json.Accept(writer);
|
||||
|
||||
std::string safe_name = Path::SanitizeFileName(m_name);
|
||||
|
||||
// Create a temporary file first so that we don't corrupt an existing file
|
||||
// in the case that we succeed in opening the file but fail to write our
|
||||
// data to it.
|
||||
std::string temp_file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".tmp");
|
||||
|
||||
if (!FileSystem::WriteStringToFile(temp_file_path.c_str(), string_buffer.GetString()))
|
||||
{
|
||||
Console.Error("Debugger: Failed to save temporary layout file '%s'.", temp_file_path.c_str());
|
||||
FileSystem::DeleteFilePath(temp_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now move the layout to its final location.
|
||||
std::string file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".json");
|
||||
|
||||
if (!FileSystem::RenamePath(temp_file_path.c_str(), file_path.c_str()))
|
||||
{
|
||||
Console.Error("Debugger: Failed to move layout file to '%s'.", file_path.c_str());
|
||||
FileSystem::DeleteFilePath(temp_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the layout has been renamed we need to delete the old file.
|
||||
if (file_path != m_layout_file_path)
|
||||
deleteFile();
|
||||
|
||||
m_layout_file_path = std::move(file_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DockLayout::load(
|
||||
const std::string& path,
|
||||
LoadResult& result,
|
||||
DockLayout::Index& index_last_session)
|
||||
{
|
||||
pxAssert(m_is_frozen);
|
||||
|
||||
result = SUCCESS;
|
||||
|
||||
std::optional<std::string> text = FileSystem::ReadFileToString(path.c_str());
|
||||
if (!text.has_value())
|
||||
{
|
||||
Console.Error("Debugger: Failed to open layout file '%s'.", path.c_str());
|
||||
result = FILE_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document json;
|
||||
if (json.Parse(text->c_str()).HasParseError() || !json.IsObject())
|
||||
{
|
||||
Console.Error("Debugger: Failed to parse layout file '%s' as JSON.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto format = json.FindMember("format");
|
||||
if (format == json.MemberEnd() ||
|
||||
!format->value.IsString() ||
|
||||
strcmp(format->value.GetString(), DEBUGGER_LAYOUT_FILE_FORMAT) != 0)
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'format' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_major = json.FindMember("version_major");
|
||||
if (version_major == json.MemberEnd() || !version_major->value.IsInt())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_major' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (version_major->value.GetInt() != DEBUGGER_LAYOUT_FILE_VERSION_MAJOR)
|
||||
{
|
||||
result = MAJOR_VERSION_MISMATCH;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_minor = json.FindMember("version_minor");
|
||||
if (version_minor == json.MemberEnd() || !version_minor->value.IsInt())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_minor' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_hash = json.FindMember("version_hash");
|
||||
if (version_hash == json.MemberEnd() || !version_hash->value.IsString())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_hash' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(version_hash->value.GetString(), DockTables::hashDefaultLayouts().c_str()) != 0)
|
||||
result = DEFAULT_LAYOUT_HASH_MISMATCH;
|
||||
|
||||
auto name = json.FindMember("name");
|
||||
if (name != json.MemberEnd() && name->value.IsString())
|
||||
m_name = name->value.GetString();
|
||||
else
|
||||
m_name = QCoreApplication::translate("DockLayout", "Unnamed").toStdString();
|
||||
|
||||
auto target = json.FindMember("target");
|
||||
m_cpu = BREAKPOINT_EE;
|
||||
if (target != json.MemberEnd() && target->value.IsString())
|
||||
{
|
||||
for (BreakPointCpu cpu : DEBUG_CPUS)
|
||||
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
|
||||
m_cpu = cpu;
|
||||
}
|
||||
|
||||
auto index = json.FindMember("index");
|
||||
if (index != json.MemberEnd() && index->value.IsInt())
|
||||
index_last_session = index->value.GetInt();
|
||||
|
||||
auto is_default = json.FindMember("isDefault");
|
||||
if (is_default != json.MemberEnd() && is_default->value.IsBool())
|
||||
m_is_default = is_default->value.GetBool();
|
||||
|
||||
auto base_layout = json.FindMember("baseLayout");
|
||||
if (base_layout != json.MemberEnd() && base_layout->value.IsString())
|
||||
m_base_layout = base_layout->value.GetString();
|
||||
|
||||
auto widgets = json.FindMember("widgets");
|
||||
if (widgets != json.MemberEnd() && widgets->value.IsArray())
|
||||
{
|
||||
for (rapidjson::Value& object : widgets->value.GetArray())
|
||||
{
|
||||
auto unique_name = object.FindMember("uniqueName");
|
||||
if (unique_name == object.MemberEnd() || !unique_name->value.IsString())
|
||||
continue;
|
||||
|
||||
auto type = object.FindMember("type");
|
||||
if (type == object.MemberEnd() || !type->value.IsString())
|
||||
continue;
|
||||
|
||||
auto description = DockTables::DEBUGGER_WIDGETS.find(type->value.GetString());
|
||||
if (description == DockTables::DEBUGGER_WIDGETS.end())
|
||||
continue;
|
||||
|
||||
DebuggerWidget* widget = description->second.create_widget(DebugInterface::get(m_cpu));
|
||||
|
||||
JsonValueWrapper wrapper(object, json.GetAllocator());
|
||||
if (!widget->fromJson(wrapper))
|
||||
{
|
||||
delete widget;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_widgets.emplace(unique_name->value.GetString(), widget);
|
||||
}
|
||||
}
|
||||
|
||||
auto geometry = json.FindMember("geometry");
|
||||
if (geometry != json.MemberEnd() && geometry->value.IsObject())
|
||||
{
|
||||
rapidjson::StringBuffer string_buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(string_buffer);
|
||||
geometry->value.Accept(writer);
|
||||
|
||||
m_geometry = QByteArray(string_buffer.GetString(), string_buffer.GetSize());
|
||||
}
|
||||
|
||||
m_layout_file_path = path;
|
||||
}
|
||||
|
||||
void DockLayout::setupDefaultLayout(DebuggerWindow* window)
|
||||
{
|
||||
pxAssert(!m_is_frozen);
|
||||
|
||||
if (m_base_layout.empty())
|
||||
return;
|
||||
|
||||
const DockTables::DefaultDockLayout* layout = nullptr;
|
||||
for (const DockTables::DefaultDockLayout& default_layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
if (default_layout.name == m_base_layout)
|
||||
layout = &default_layout;
|
||||
|
||||
if (!layout)
|
||||
return;
|
||||
|
||||
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(layout->groups.size(), nullptr);
|
||||
|
||||
for (const DockTables::DefaultDockWidgetDescription& dock_description : layout->widgets)
|
||||
{
|
||||
const DockTables::DefaultDockGroupDescription& group = layout->groups[static_cast<u32>(dock_description.group)];
|
||||
|
||||
auto widget_iterator = m_widgets.find(dock_description.type);
|
||||
if (widget_iterator == m_widgets.end())
|
||||
continue;
|
||||
|
||||
const QString& unique_name = widget_iterator->first;
|
||||
DebuggerWidget* widget = widget_iterator->second;
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(unique_name));
|
||||
view->setWidget(widget);
|
||||
|
||||
if (!groups[static_cast<u32>(dock_description.group)])
|
||||
{
|
||||
KDDockWidgets::QtWidgets::DockWidget* parent = nullptr;
|
||||
if (group.parent != DockTables::DefaultDockGroup::ROOT)
|
||||
parent = groups[static_cast<u32>(group.parent)];
|
||||
|
||||
window->addDockWidget(view, group.location, parent);
|
||||
|
||||
groups[static_cast<u32>(dock_description.group)] = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
groups[static_cast<u32>(dock_description.group)]->addDockWidgetAsTab(view);
|
||||
}
|
||||
}
|
||||
|
||||
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
||||
group->setCurrentTabIndex(0);
|
||||
|
||||
retranslateDockWidgets();
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
class DebuggerWidget;
|
||||
class DebuggerWindow;
|
||||
|
||||
extern const char* DEBUGGER_LAYOUT_FILE_FORMAT;
|
||||
|
||||
// Increment this whenever there is a breaking change to the JSON format.
|
||||
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR;
|
||||
|
||||
// Increment this whenever there is a non-breaking change to the JSON format.
|
||||
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR;
|
||||
|
||||
class DockLayout
|
||||
{
|
||||
public:
|
||||
using Index = size_t;
|
||||
static const constexpr Index INVALID_INDEX = SIZE_MAX;
|
||||
|
||||
enum LoadResult
|
||||
{
|
||||
SUCCESS,
|
||||
FILE_NOT_FOUND,
|
||||
INVALID_FORMAT,
|
||||
MAJOR_VERSION_MISMATCH,
|
||||
DEFAULT_LAYOUT_HASH_MISMATCH,
|
||||
CONFLICTING_NAME
|
||||
};
|
||||
|
||||
// Create a layout based on a default layout.
|
||||
DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockTables::DefaultDockLayout& default_layout,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Create a new blank layout.
|
||||
DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Clone an existing layout.
|
||||
DockLayout(
|
||||
std::string name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockLayout& layout_to_clone,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Load a layout from a file.
|
||||
DockLayout(
|
||||
const std::string& path,
|
||||
LoadResult& result,
|
||||
DockLayout::Index& index_last_session,
|
||||
DockLayout::Index index);
|
||||
|
||||
~DockLayout();
|
||||
|
||||
DockLayout(const DockLayout& rhs) = delete;
|
||||
DockLayout& operator=(const DockLayout& rhs) = delete;
|
||||
|
||||
DockLayout(DockLayout&& rhs) = default;
|
||||
DockLayout& operator=(DockLayout&&) = default;
|
||||
|
||||
const std::string& name() const;
|
||||
void setName(std::string name);
|
||||
|
||||
BreakPointCpu cpu() const;
|
||||
void setCpu(BreakPointCpu cpu);
|
||||
|
||||
bool isDefault() const;
|
||||
|
||||
// Tear down and save the state of all the dock widgets from this layout.
|
||||
void freeze();
|
||||
|
||||
// Restore the state of all the dock widgets from this layout.
|
||||
void thaw(DebuggerWindow* window);
|
||||
|
||||
KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name);
|
||||
void retranslateDockWidgets();
|
||||
void retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget);
|
||||
void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget);
|
||||
|
||||
bool hasDebuggerWidget(QString unique_name);
|
||||
void toggleDebuggerWidget(QString unique_name, DebuggerWindow* window);
|
||||
void recreateDebuggerWidget(QString unique_name);
|
||||
|
||||
void deleteFile();
|
||||
|
||||
bool save(DockLayout::Index layout_index);
|
||||
|
||||
private:
|
||||
void load(
|
||||
const std::string& path,
|
||||
DockLayout::LoadResult& result,
|
||||
DockLayout::Index& index_last_session);
|
||||
|
||||
void setupDefaultLayout(DebuggerWindow* window);
|
||||
|
||||
// The name displayed in the user interface. Also used to determine the
|
||||
// file name for the layout file.
|
||||
std::string m_name;
|
||||
|
||||
// The default target for dock widgets in this layout. This can be
|
||||
// overriden on a per-widget basis.
|
||||
BreakPointCpu m_cpu;
|
||||
|
||||
// Is this one of the default layouts?
|
||||
bool m_is_default = false;
|
||||
|
||||
// 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;
|
||||
|
||||
// All the dock widgets currently open in this layout. If this is the active
|
||||
// layout then these will be owned by the docking system, otherwise they
|
||||
// won't be and will need to be cleaned up separately.
|
||||
std::map<QString, QPointer<DebuggerWidget>> m_widgets;
|
||||
|
||||
// The geometry of all the dock widgets, converted to JSON by the
|
||||
// LayoutSaver class from KDDockWidgets.
|
||||
QByteArray m_geometry;
|
||||
|
||||
// The absolute file path of the corresponding layout file as it currently
|
||||
// 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;
|
||||
};
|
|
@ -0,0 +1,602 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockManager.h"
|
||||
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
#include "Debugger/Docking/DockViews.h"
|
||||
#include "Debugger/Docking/LayoutEditorDialog.h"
|
||||
#include "Debugger/Docking/NoLayoutsWidget.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Path.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/core/Stack.h>
|
||||
#include <kddockwidgets/qtwidgets/Stack.h>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QtTranslation>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
DockManager::DockManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
QTimer* autosave_timer = new QTimer(this);
|
||||
connect(autosave_timer, &QTimer::timeout, this, &DockManager::saveCurrentLayout);
|
||||
autosave_timer->start(60 * 1000);
|
||||
}
|
||||
|
||||
void DockManager::configureDockingSystem()
|
||||
{
|
||||
static bool done = false;
|
||||
if (done)
|
||||
return;
|
||||
|
||||
KDDockWidgets::Config::self().setFlags(
|
||||
KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible |
|
||||
KDDockWidgets::Config::Flag_AlwaysShowTabs |
|
||||
KDDockWidgets::Config::Flag_AllowReorderTabs |
|
||||
KDDockWidgets::Config::Flag_TitleBarIsFocusable);
|
||||
|
||||
KDDockWidgets::Config::self().setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory);
|
||||
KDDockWidgets::Config::self().setViewFactory(new DockViewFactory());
|
||||
KDDockWidgets::Config::self().setDragAboutToStartFunc(&DockManager::dragAboutToStart);
|
||||
KDDockWidgets::Config::self().setStartDragDistance(std::max(QApplication::startDragDistance(), 32));
|
||||
|
||||
done = true;
|
||||
}
|
||||
|
||||
bool DockManager::deleteLayout(DockLayout::Index layout_index)
|
||||
{
|
||||
pxAssertRel(layout_index != DockLayout::INVALID_INDEX,
|
||||
"DockManager::deleteLayout called with INVALID_INDEX.");
|
||||
|
||||
if (layout_index == m_current_layout)
|
||||
{
|
||||
DockLayout::Index other_layout = DockLayout::INVALID_INDEX;
|
||||
if (layout_index + 1 < m_layouts.size())
|
||||
other_layout = layout_index + 1;
|
||||
else if (layout_index > 0)
|
||||
other_layout = layout_index - 1;
|
||||
|
||||
switchToLayout(other_layout);
|
||||
}
|
||||
|
||||
m_layouts.at(layout_index).deleteFile();
|
||||
m_layouts.erase(m_layouts.begin() + layout_index);
|
||||
|
||||
// All the layouts after the one being deleted have been shifted over by
|
||||
// one, so adjust the current layout index accordingly.
|
||||
if (m_current_layout > layout_index && m_current_layout != DockLayout::INVALID_INDEX)
|
||||
m_current_layout--;
|
||||
|
||||
if (m_layouts.empty())
|
||||
{
|
||||
NoLayoutsWidget* widget = new NoLayoutsWidget;
|
||||
connect(widget->createDefaultLayoutsButton(), &QPushButton::clicked, this, &DockManager::resetAllLayouts);
|
||||
|
||||
KDDockWidgets::QtWidgets::DockWidget* dock = new KDDockWidgets::QtWidgets::DockWidget("placeholder");
|
||||
dock->setTitle(tr("No Layouts"));
|
||||
dock->setWidget(widget);
|
||||
g_debugger_window->addDockWidget(dock, KDDockWidgets::Location_OnTop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DockManager::switchToLayout(DockLayout::Index layout_index)
|
||||
{
|
||||
if (layout_index == m_current_layout)
|
||||
return;
|
||||
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
{
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
layout.freeze();
|
||||
layout.save(m_current_layout);
|
||||
}
|
||||
|
||||
m_current_layout = layout_index;
|
||||
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
{
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
layout.thaw(g_debugger_window);
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::loadLayouts()
|
||||
{
|
||||
m_layouts.clear();
|
||||
|
||||
// Load the layouts.
|
||||
FileSystem::FindResultsArray files;
|
||||
FileSystem::FindFiles(
|
||||
EmuFolders::DebuggerLayouts.c_str(),
|
||||
"*.json",
|
||||
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES,
|
||||
&files);
|
||||
|
||||
bool needs_reset = false;
|
||||
bool order_changed = false;
|
||||
std::vector<DockLayout::Index> indices_last_session;
|
||||
|
||||
for (const FILESYSTEM_FIND_DATA& ffd : files)
|
||||
{
|
||||
DockLayout::LoadResult result;
|
||||
DockLayout::Index index_last_session = DockLayout::INVALID_INDEX;
|
||||
DockLayout::Index index =
|
||||
createLayout(ffd.FileName, result, index_last_session);
|
||||
|
||||
DockLayout& layout = m_layouts.at(index);
|
||||
|
||||
// Try to make sure the layout has a unique name.
|
||||
const std::string& name = layout.name();
|
||||
std::string new_name = name;
|
||||
if (result == DockLayout::SUCCESS || result == DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
||||
{
|
||||
for (int i = 2; hasNameConflict(new_name, index) && i < 100; i++)
|
||||
{
|
||||
if (i == 99)
|
||||
{
|
||||
result = DockLayout::CONFLICTING_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
new_name = fmt::format("{} #{}", name, i);
|
||||
}
|
||||
}
|
||||
|
||||
needs_reset |= result != DockLayout::SUCCESS;
|
||||
|
||||
if (result != DockLayout::SUCCESS && result != DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
||||
{
|
||||
deleteLayout(index);
|
||||
// Only delete the file if we've identified that it's actually a
|
||||
// layout file.
|
||||
if (result == DockLayout::MAJOR_VERSION_MISMATCH || result == DockLayout::CONFLICTING_NAME)
|
||||
FileSystem::DeleteFilePath(ffd.FileName.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (new_name != name)
|
||||
{
|
||||
layout.setName(new_name);
|
||||
layout.save(index);
|
||||
}
|
||||
|
||||
if (index_last_session != index)
|
||||
order_changed = true;
|
||||
|
||||
indices_last_session.emplace_back(index_last_session);
|
||||
}
|
||||
|
||||
// Make sure the layouts remain in the same order they were in previously.
|
||||
std::vector<DockLayout*> layout_pointers;
|
||||
for (DockLayout& layout : m_layouts)
|
||||
layout_pointers.emplace_back(&layout);
|
||||
|
||||
std::sort(layout_pointers.begin(), layout_pointers.end(),
|
||||
[this, &indices_last_session](const DockLayout* lhs, const DockLayout* rhs) {
|
||||
size_t lhs_index = lhs - m_layouts.data();
|
||||
size_t rhs_index = rhs - m_layouts.data();
|
||||
DockLayout::Index lhs_index_last_session = indices_last_session.at(lhs_index);
|
||||
DockLayout::Index rhs_index_last_session = indices_last_session.at(rhs_index);
|
||||
return lhs_index_last_session < rhs_index_last_session;
|
||||
});
|
||||
|
||||
std::vector<DockLayout> sorted_layouts;
|
||||
for (size_t i = 0; i < layout_pointers.size(); i++)
|
||||
sorted_layouts.emplace_back(std::move(*layout_pointers[i]));
|
||||
|
||||
m_layouts = std::move(sorted_layouts);
|
||||
|
||||
if (m_layouts.empty() || needs_reset)
|
||||
resetDefaultLayouts();
|
||||
else
|
||||
updateLayoutSwitcher();
|
||||
|
||||
// Make sure the indices in the existing layout files match up with the
|
||||
// indices of any new layouts.
|
||||
if (order_changed)
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
bool DockManager::saveLayouts()
|
||||
{
|
||||
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
||||
if (!m_layouts[i].save(i))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DockManager::saveCurrentLayout()
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return true;
|
||||
|
||||
return m_layouts.at(m_current_layout).save(m_current_layout);
|
||||
}
|
||||
|
||||
void DockManager::resetAllLayouts()
|
||||
{
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
for (DockLayout& layout : m_layouts)
|
||||
layout.deleteFile();
|
||||
|
||||
m_layouts.clear();
|
||||
|
||||
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
createLayout(tr(layout.name.c_str()).toStdString(), layout.cpu, true, layout);
|
||||
|
||||
switchToLayout(0);
|
||||
updateLayoutSwitcher();
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
void DockManager::resetDefaultLayouts()
|
||||
{
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
std::vector<DockLayout> old_layouts = std::move(m_layouts);
|
||||
m_layouts = std::vector<DockLayout>();
|
||||
|
||||
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
createLayout(tr(layout.name.c_str()).toStdString(), layout.cpu, true, layout);
|
||||
|
||||
for (DockLayout& layout : old_layouts)
|
||||
if (!layout.isDefault())
|
||||
m_layouts.emplace_back(std::move(layout));
|
||||
else
|
||||
layout.deleteFile();
|
||||
|
||||
switchToLayout(0);
|
||||
updateLayoutSwitcher();
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
void DockManager::createWindowsMenu(QMenu* menu)
|
||||
{
|
||||
menu->clear();
|
||||
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
|
||||
for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS)
|
||||
{
|
||||
QAction* action = new QAction(menu);
|
||||
action->setText(QCoreApplication::translate("DebuggerWidget", desc.title));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(layout.hasDebuggerWidget(type));
|
||||
connect(action, &QAction::triggered, this, [&layout, type]() {
|
||||
layout.toggleDebuggerWidget(type, g_debugger_window);
|
||||
});
|
||||
menu->addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar)
|
||||
{
|
||||
QWidget* container = new QWidget;
|
||||
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, 2, 2, 0);
|
||||
container->setLayout(layout);
|
||||
|
||||
QWidget* menu_wrapper = new QWidget;
|
||||
menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
layout->addWidget(menu_wrapper);
|
||||
|
||||
QHBoxLayout* menu_layout = new QHBoxLayout;
|
||||
menu_layout->setContentsMargins(0, 4, 0, 4);
|
||||
menu_wrapper->setLayout(menu_layout);
|
||||
|
||||
menu_layout->addWidget(menu_bar);
|
||||
|
||||
m_switcher = new QTabBar;
|
||||
m_switcher->setContentsMargins(0, 0, 0, 0);
|
||||
m_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
m_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_switcher->setMovable(true);
|
||||
layout->addWidget(m_switcher);
|
||||
|
||||
updateLayoutSwitcher();
|
||||
|
||||
connect(m_switcher, &QTabBar::tabMoved, this, &DockManager::layoutSwitcherTabMoved);
|
||||
connect(m_switcher, &QTabBar::customContextMenuRequested, this, &DockManager::layoutSwitcherContextMenu);
|
||||
|
||||
QWidget* spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
QPushButton* lock_layout_toggle = new QPushButton;
|
||||
connect(lock_layout_toggle, &QPushButton::toggled, this, [this, lock_layout_toggle](bool checked) {
|
||||
setLayoutLocked(checked);
|
||||
if (m_layout_locked)
|
||||
lock_layout_toggle->setText(tr("Layout Locked"));
|
||||
else
|
||||
lock_layout_toggle->setText(tr("Layout Unlocked"));
|
||||
});
|
||||
lock_layout_toggle->setCheckable(true);
|
||||
lock_layout_toggle->setChecked(m_layout_locked);
|
||||
lock_layout_toggle->setFlat(true);
|
||||
layout->addWidget(lock_layout_toggle);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void DockManager::updateLayoutSwitcher()
|
||||
{
|
||||
if (!m_switcher)
|
||||
return;
|
||||
|
||||
disconnect(m_tab_connection);
|
||||
|
||||
for (int i = m_switcher->count(); i > 0; i--)
|
||||
m_switcher->removeTab(i - 1);
|
||||
|
||||
for (DockLayout& layout : m_layouts)
|
||||
{
|
||||
const char* cpu_name = DebugInterface::cpuName(layout.cpu());
|
||||
QString tab_name = QString("%1 (%2)").arg(layout.name().c_str()).arg(cpu_name);
|
||||
m_switcher->addTab(tab_name);
|
||||
}
|
||||
|
||||
m_plus_tab_index = m_switcher->addTab("+");
|
||||
m_current_tab_index = m_current_layout;
|
||||
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
m_switcher->setCurrentIndex(m_current_layout);
|
||||
|
||||
// If we don't have any layouts, the currently selected tab will never be
|
||||
// changed, so we respond to all clicks instead.
|
||||
if (!m_layouts.empty())
|
||||
m_tab_connection = connect(m_switcher, &QTabBar::currentChanged, this, &DockManager::layoutSwitcherTabChanged);
|
||||
else
|
||||
m_tab_connection = connect(m_switcher, &QTabBar::tabBarClicked, this, &DockManager::layoutSwitcherTabChanged);
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherTabChanged(int index)
|
||||
{
|
||||
if (index == m_plus_tab_index)
|
||||
{
|
||||
if (m_current_tab_index >= 0 && m_current_tab_index < m_plus_tab_index)
|
||||
m_switcher->setCurrentIndex(m_current_tab_index);
|
||||
|
||||
auto name_validator = [this](const std::string& name) {
|
||||
return !hasNameConflict(name, DockLayout::INVALID_INDEX);
|
||||
};
|
||||
|
||||
bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
|
||||
LayoutEditorDialog* dialog = new LayoutEditorDialog(
|
||||
name_validator, can_clone_current_layout, g_debugger_window);
|
||||
if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
|
||||
{
|
||||
DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
|
||||
|
||||
const auto [mode, index] = dialog->initial_state();
|
||||
switch (mode)
|
||||
{
|
||||
case LayoutEditorDialog::DEFAULT_LAYOUT:
|
||||
{
|
||||
const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout);
|
||||
break;
|
||||
}
|
||||
case LayoutEditorDialog::BLANK_LAYOUT:
|
||||
{
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false);
|
||||
break;
|
||||
}
|
||||
case LayoutEditorDialog::CLONE_LAYOUT:
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
DockLayout::Index old_layout = m_current_layout;
|
||||
|
||||
// Freeze the current layout so we can copy the geometry.
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switchToLayout(new_layout);
|
||||
updateLayoutSwitcher();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DockLayout::Index layout_index = static_cast<DockLayout::Index>(index);
|
||||
if (layout_index < 0 || layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
switchToLayout(layout_index);
|
||||
m_current_tab_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherTabMoved(int from, int to)
|
||||
{
|
||||
DockLayout::Index from_index = static_cast<DockLayout::Index>(from);
|
||||
DockLayout::Index to_index = static_cast<DockLayout::Index>(to);
|
||||
|
||||
if (from_index >= m_layouts.size() || to_index >= m_layouts.size())
|
||||
{
|
||||
// This happens when the user tries to move a layout to the right of the
|
||||
// plus button.
|
||||
updateLayoutSwitcher();
|
||||
return;
|
||||
}
|
||||
|
||||
DockLayout& from_layout = m_layouts[from_index];
|
||||
DockLayout& to_layout = m_layouts[to_index];
|
||||
|
||||
std::swap(from_layout, to_layout);
|
||||
|
||||
from_layout.save(from_index);
|
||||
to_layout.save(to_index);
|
||||
|
||||
if (from_index == m_current_layout)
|
||||
m_current_layout = to_index;
|
||||
else if (to_index == m_current_layout)
|
||||
m_current_layout = from_index;
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherContextMenu(QPoint pos)
|
||||
{
|
||||
int tab_index = m_switcher->tabAt(pos);
|
||||
if (tab_index < 0 || tab_index >= m_plus_tab_index)
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(tr("Layout Switcher Context Menu"), m_switcher);
|
||||
|
||||
QAction* edit_action = new QAction(tr("Edit Layout"), menu);
|
||||
connect(edit_action, &QAction::triggered, [this, tab_index]() {
|
||||
DockLayout::Index layout_index = static_cast<DockLayout::Index>(tab_index);
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
|
||||
auto name_validator = [this, layout_index](const std::string& name) {
|
||||
return !hasNameConflict(name, layout_index);
|
||||
};
|
||||
|
||||
LayoutEditorDialog* dialog = new LayoutEditorDialog(
|
||||
layout.name(), layout.cpu(), name_validator, g_debugger_window);
|
||||
|
||||
if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
|
||||
{
|
||||
layout.setName(dialog->name());
|
||||
layout.setCpu(dialog->cpu());
|
||||
|
||||
layout.save(layout_index);
|
||||
|
||||
updateLayoutSwitcher();
|
||||
}
|
||||
});
|
||||
menu->addAction(edit_action);
|
||||
|
||||
QAction* delete_action = new QAction(tr("Delete Layout"), menu);
|
||||
connect(delete_action, &QAction::triggered, [this, tab_index]() {
|
||||
DockLayout::Index layout_index = static_cast<DockLayout::Index>(tab_index);
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
|
||||
QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name().c_str());
|
||||
QMessageBox::StandardButton result = QMessageBox::question(g_debugger_window, tr("Confirmation"), text);
|
||||
|
||||
if (result == QMessageBox::Yes)
|
||||
{
|
||||
deleteLayout(layout_index);
|
||||
updateLayoutSwitcher();
|
||||
}
|
||||
});
|
||||
menu->addAction(delete_action);
|
||||
|
||||
menu->popup(m_switcher->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
bool DockManager::hasNameConflict(const std::string& name, DockLayout::Index layout_index)
|
||||
{
|
||||
std::string safe_name = Path::SanitizeFileName(name);
|
||||
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
||||
if (i != layout_index && StringUtil::compareNoCase(m_layouts[i].name(), safe_name))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DockManager::retranslateDockWidget(KDDockWidgets::Core::DockWidget* dock_widget)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void DockManager::recreateDebuggerWidget(QString unique_name)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).recreateDebuggerWidget(unique_name);
|
||||
}
|
||||
|
||||
bool DockManager::isLayoutLocked()
|
||||
{
|
||||
return m_layout_locked;
|
||||
}
|
||||
|
||||
void DockManager::setLayoutLocked(bool locked)
|
||||
{
|
||||
m_layout_locked = locked;
|
||||
|
||||
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
||||
{
|
||||
auto stack = static_cast<KDDockWidgets::QtWidgets::Stack*>(group->stack()->view());
|
||||
stack->setTabsClosable(!m_layout_locked);
|
||||
|
||||
// HACK: Make sure the sizes of the tabs get updated.
|
||||
if (stack->tabBar()->count() > 0)
|
||||
stack->tabBar()->setTabText(0, stack->tabBar()->tabText(0));
|
||||
}
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return nullptr;
|
||||
|
||||
DockManager& manager = g_debugger_window->dockManager();
|
||||
if (manager.m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return nullptr;
|
||||
|
||||
return manager.m_layouts.at(manager.m_current_layout).createDockWidget(name);
|
||||
}
|
||||
|
||||
bool DockManager::dragAboutToStart(KDDockWidgets::Core::Draggable* draggable)
|
||||
{
|
||||
bool locked = true;
|
||||
if (g_debugger_window)
|
||||
locked = g_debugger_window->dockManager().isLayoutLocked();
|
||||
|
||||
KDDockWidgets::Config::self().setDropIndicatorsInhibited(locked);
|
||||
|
||||
if (draggable->isInProgrammaticDrag())
|
||||
return true;
|
||||
|
||||
// Allow floating windows to be dragged around even if the layout is locked.
|
||||
if (draggable->isWindow())
|
||||
return true;
|
||||
|
||||
if (!g_debugger_window)
|
||||
return false;
|
||||
|
||||
return !locked;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debugger/Docking/DockLayout.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/core/Draggable_p.h>
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtWidgets/QTabBar>
|
||||
|
||||
class DockManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockManager(QObject* parent = nullptr);
|
||||
|
||||
DockManager(const DockManager& rhs) = delete;
|
||||
DockManager& operator=(const DockManager& rhs) = delete;
|
||||
|
||||
DockManager(DockManager&& rhs) = delete;
|
||||
DockManager& operator=(DockManager&&) = delete;
|
||||
|
||||
// This needs to be called before any KDDockWidgets objects are created
|
||||
// including the debugger window itself.
|
||||
static void configureDockingSystem();
|
||||
|
||||
template <typename... Args>
|
||||
DockLayout::Index createLayout(Args&&... args)
|
||||
{
|
||||
DockLayout::Index layout_index = m_layouts.size();
|
||||
|
||||
if (m_layouts.empty())
|
||||
{
|
||||
// Delete the placeholder created in DockManager::deleteLayout.
|
||||
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
|
||||
delete dock;
|
||||
}
|
||||
|
||||
m_layouts.emplace_back(std::forward<Args>(args)..., layout_index);
|
||||
|
||||
return layout_index;
|
||||
}
|
||||
|
||||
bool deleteLayout(DockLayout::Index layout_index);
|
||||
|
||||
void switchToLayout(DockLayout::Index layout_index);
|
||||
|
||||
void loadLayouts();
|
||||
bool saveLayouts();
|
||||
bool saveCurrentLayout();
|
||||
|
||||
void resetAllLayouts();
|
||||
void resetDefaultLayouts();
|
||||
|
||||
void createWindowsMenu(QMenu* menu);
|
||||
|
||||
QWidget* createLayoutSwitcher(QWidget* menu_bar);
|
||||
void updateLayoutSwitcher();
|
||||
void layoutSwitcherTabChanged(int index);
|
||||
void layoutSwitcherTabMoved(int from, int to);
|
||||
void layoutSwitcherContextMenu(QPoint pos);
|
||||
|
||||
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 recreateDebuggerWidget(QString unique_name);
|
||||
|
||||
bool isLayoutLocked();
|
||||
void setLayoutLocked(bool locked);
|
||||
|
||||
private:
|
||||
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);
|
||||
static bool dragAboutToStart(KDDockWidgets::Core::Draggable* draggable);
|
||||
|
||||
std::vector<DockLayout> m_layouts;
|
||||
DockLayout::Index m_current_layout = DockLayout::INVALID_INDEX;
|
||||
|
||||
QTabBar* m_switcher = nullptr;
|
||||
int m_plus_tab_index = -1;
|
||||
int m_current_tab_index = -1;
|
||||
|
||||
QMetaObject::Connection m_tab_connection;
|
||||
|
||||
bool m_layout_locked = true;
|
||||
};
|
|
@ -0,0 +1,175 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockTables.h"
|
||||
|
||||
#include "Debugger/DisassemblyWidget.h"
|
||||
#include "Debugger/RegisterWidget.h"
|
||||
#include "Debugger/StackWidget.h"
|
||||
#include "Debugger/ThreadWidget.h"
|
||||
#include "Debugger/Breakpoints/BreakpointWidget.h"
|
||||
#include "Debugger/Memory/MemorySearchWidget.h"
|
||||
#include "Debugger/Memory/MemoryViewWidget.h"
|
||||
#include "Debugger/Memory/SavedAddressesWidget.h"
|
||||
#include "Debugger/SymbolTree/SymbolTreeWidgets.h"
|
||||
|
||||
#include "common/MD5Digest.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
using namespace DockUtils;
|
||||
|
||||
#define DEBUGGER_WIDGET(type, title, preferred_location) \
|
||||
{ \
|
||||
#type, \
|
||||
{ \
|
||||
[](DebugInterface& cpu) -> DebuggerWidget* { return new type(cpu); }, \
|
||||
title, \
|
||||
preferred_location \
|
||||
} \
|
||||
}
|
||||
|
||||
const std::map<QString, DockTables::DebuggerWidgetDescription> 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),
|
||||
DEBUGGER_WIDGET(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Globals"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Locals"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(MemorySearchWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory Search"), TOP_LEFT),
|
||||
DEBUGGER_WIDGET(MemoryViewWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Parameters"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(RegisterWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Registers"), TOP_LEFT),
|
||||
DEBUGGER_WIDGET(SavedAddressesWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Saved Addresses"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(StackWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Stack"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(ThreadWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Threads"), BOTTOM_MIDDLE),
|
||||
};
|
||||
|
||||
#undef DEBUGGER_WIDGET
|
||||
|
||||
const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUTS = {
|
||||
{
|
||||
QT_TRANSLATE_NOOP("DebuggerLayout", "R5900"),
|
||||
BREAKPOINT_EE,
|
||||
{
|
||||
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
|
||||
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
|
||||
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
|
||||
},
|
||||
{
|
||||
/* DefaultDockGroup::TOP_RIGHT */
|
||||
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
|
||||
/* DefaultDockGroup::BOTTOM */
|
||||
{"MemoryViewWidget", DefaultDockGroup::BOTTOM},
|
||||
{"BreakpointWidget", DefaultDockGroup::BOTTOM},
|
||||
{"ThreadWidget", DefaultDockGroup::BOTTOM},
|
||||
{"StackWidget", DefaultDockGroup::BOTTOM},
|
||||
{"SavedAddressesWidget", DefaultDockGroup::BOTTOM},
|
||||
{"GlobalVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
{"LocalVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
{"ParameterVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
/* DefaultDockGroup::TOP_LEFT */
|
||||
{"RegisterWidget", DefaultDockGroup::TOP_LEFT},
|
||||
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
|
||||
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
|
||||
},
|
||||
},
|
||||
{
|
||||
QT_TRANSLATE_NOOP("DebuggerLayout", "R3000"),
|
||||
BREAKPOINT_IOP,
|
||||
{
|
||||
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
|
||||
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
|
||||
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
|
||||
},
|
||||
{
|
||||
/* DefaultDockGroup::TOP_RIGHT */
|
||||
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
|
||||
/* DefaultDockGroup::BOTTOM */
|
||||
{"MemoryViewWidget", DefaultDockGroup::BOTTOM},
|
||||
{"BreakpointWidget", DefaultDockGroup::BOTTOM},
|
||||
{"ThreadWidget", DefaultDockGroup::BOTTOM},
|
||||
{"StackWidget", DefaultDockGroup::BOTTOM},
|
||||
{"SavedAddressesWidget", DefaultDockGroup::BOTTOM},
|
||||
{"GlobalVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
{"LocalVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
{"ParameterVariableTreeWidget", DefaultDockGroup::BOTTOM},
|
||||
/* DefaultDockGroup::TOP_LEFT */
|
||||
{"RegisterWidget", DefaultDockGroup::TOP_LEFT},
|
||||
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
|
||||
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const std::string& DockTables::hashDefaultLayouts()
|
||||
{
|
||||
static std::string hash;
|
||||
if (!hash.empty())
|
||||
return hash;
|
||||
|
||||
MD5Digest md5;
|
||||
|
||||
u32 hash_version = 1;
|
||||
md5.Update(&hash_version, sizeof(hash_version));
|
||||
|
||||
size_t layout_count = DEFAULT_DOCK_LAYOUTS.size();
|
||||
md5.Update(&layout_count, sizeof(layout_count));
|
||||
|
||||
for (const DefaultDockLayout& layout : DEFAULT_DOCK_LAYOUTS)
|
||||
hashDefaultLayout(layout, md5);
|
||||
|
||||
u8 digest[16];
|
||||
md5.Final(digest);
|
||||
hash = fmt::format(
|
||||
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5)
|
||||
{
|
||||
size_t layout_name_size = layout.name.size();
|
||||
md5.Update(&layout_name_size, sizeof(layout_name_size));
|
||||
md5.Update(layout.name.c_str(), layout.name.size());
|
||||
|
||||
const char* cpu_name = DebugInterface::cpuName(layout.cpu);
|
||||
size_t cpu_name_size = strlen(cpu_name);
|
||||
md5.Update(&cpu_name_size, sizeof(cpu_name_size));
|
||||
md5.Update(cpu_name, cpu_name_size);
|
||||
|
||||
size_t group_count = layout.groups.size();
|
||||
md5.Update(&group_count, sizeof(group_count));
|
||||
|
||||
for (const DefaultDockGroupDescription& group : layout.groups)
|
||||
hashDefaultGroup(group, md5);
|
||||
|
||||
size_t widget_count = layout.widgets.size();
|
||||
md5.Update(&widget_count, sizeof(widget_count));
|
||||
|
||||
for (const DefaultDockWidgetDescription& widget : layout.widgets)
|
||||
hashDefaultDockWidget(widget, md5);
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5)
|
||||
{
|
||||
const char* location = DockUtils::locationToString(group.location);
|
||||
size_t location_size = strlen(location);
|
||||
md5.Update(&location_size, sizeof(location_size));
|
||||
md5.Update(location, location_size);
|
||||
|
||||
u32 parent = static_cast<u32>(group.parent);
|
||||
md5.Update(&parent, sizeof(parent));
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5)
|
||||
{
|
||||
std::string type = widget.type.toStdString();
|
||||
size_t type_size = type.size();
|
||||
md5.Update(&type_size, sizeof(type_size));
|
||||
md5.Update(type.c_str(), type_size);
|
||||
|
||||
u32 group = static_cast<u32>(widget.group);
|
||||
md5.Update(&group, sizeof(group));
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DockUtils.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/KDDockWidgets.h>
|
||||
|
||||
class MD5Digest;
|
||||
|
||||
class DebugInterface;
|
||||
class DebuggerWidget;
|
||||
|
||||
namespace DockTables
|
||||
{
|
||||
struct DebuggerWidgetDescription
|
||||
{
|
||||
DebuggerWidget* (*create_widget)(DebugInterface& cpu);
|
||||
|
||||
// The untranslated string displayed as the dock widget tab text.
|
||||
const char* title;
|
||||
|
||||
// This is used to determine which group dock widgets of this type are
|
||||
// added to when they're opened from the Windows menu.
|
||||
DockUtils::PreferredLocation preferred_location;
|
||||
};
|
||||
|
||||
extern const std::map<QString, DebuggerWidgetDescription> DEBUGGER_WIDGETS;
|
||||
|
||||
enum class DefaultDockGroup
|
||||
{
|
||||
ROOT = -1,
|
||||
TOP_RIGHT = 0,
|
||||
BOTTOM = 1,
|
||||
TOP_LEFT = 2
|
||||
};
|
||||
|
||||
struct DefaultDockGroupDescription
|
||||
{
|
||||
KDDockWidgets::Location location;
|
||||
DefaultDockGroup parent;
|
||||
};
|
||||
|
||||
extern const std::vector<DefaultDockGroupDescription> DEFAULT_DOCK_GROUPS;
|
||||
|
||||
struct DefaultDockWidgetDescription
|
||||
{
|
||||
QString type;
|
||||
DefaultDockGroup group;
|
||||
};
|
||||
|
||||
struct DefaultDockLayout
|
||||
{
|
||||
std::string name;
|
||||
BreakPointCpu cpu;
|
||||
std::vector<DefaultDockGroupDescription> groups;
|
||||
std::vector<DefaultDockWidgetDescription> widgets;
|
||||
};
|
||||
|
||||
extern const std::vector<DefaultDockLayout> DEFAULT_DOCK_LAYOUTS;
|
||||
|
||||
// This is used to determine if the user has updated and we need to recreate
|
||||
// the default layouts.
|
||||
const std::string& hashDefaultLayouts();
|
||||
|
||||
void hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5);
|
||||
void hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5);
|
||||
void hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5);
|
||||
} // namespace DockTables
|
|
@ -0,0 +1,116 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockUtils.h"
|
||||
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/qtwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/Group.h>
|
||||
|
||||
DockUtils::DockWidgetPair DockUtils::dockWidgetFromName(QString unique_name)
|
||||
{
|
||||
KDDockWidgets::Vector<QString> names{unique_name};
|
||||
KDDockWidgets::Vector<KDDockWidgets::Core::DockWidget*> dock_widgets =
|
||||
KDDockWidgets::DockRegistry::self()->dockWidgets(names);
|
||||
if (dock_widgets.size() != 1 || !dock_widgets[0])
|
||||
return {};
|
||||
|
||||
return {dock_widgets[0], static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widgets[0]->view())};
|
||||
}
|
||||
|
||||
void DockUtils::insertDockWidgetAtPreferredLocation(
|
||||
KDDockWidgets::Core::DockWidget* dock_widget,
|
||||
PreferredLocation location,
|
||||
KDDockWidgets::QtWidgets::MainWindow* window)
|
||||
{
|
||||
int width = window->width();
|
||||
int height = window->height();
|
||||
int half_width = width / 2;
|
||||
int half_height = height / 2;
|
||||
|
||||
QPoint preferred_location;
|
||||
switch (location)
|
||||
{
|
||||
case DockUtils::TOP_LEFT:
|
||||
preferred_location = {0, 0};
|
||||
break;
|
||||
case DockUtils::TOP_MIDDLE:
|
||||
preferred_location = {half_width, 0};
|
||||
break;
|
||||
case DockUtils::TOP_RIGHT:
|
||||
preferred_location = {width, 0};
|
||||
break;
|
||||
case DockUtils::MIDDLE_LEFT:
|
||||
preferred_location = {0, half_height};
|
||||
break;
|
||||
case DockUtils::MIDDLE_MIDDLE:
|
||||
preferred_location = {half_width, half_height};
|
||||
break;
|
||||
case DockUtils::MIDDLE_RIGHT:
|
||||
preferred_location = {width, half_height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_LEFT:
|
||||
preferred_location = {0, height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_MIDDLE:
|
||||
preferred_location = {half_width, height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_RIGHT:
|
||||
preferred_location = {width, height};
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the dock group which is closest to the preferred location.
|
||||
KDDockWidgets::Core::Group* best_group = nullptr;
|
||||
int best_distance_squared = 0;
|
||||
|
||||
for (KDDockWidgets::Core::Group* group_controller : KDDockWidgets::DockRegistry::self()->groups())
|
||||
{
|
||||
if (group_controller->isFloating())
|
||||
continue;
|
||||
|
||||
auto group = static_cast<KDDockWidgets::QtWidgets::Group*>(group_controller->view());
|
||||
|
||||
QPoint local_midpoint = group->pos() + QPoint(group->width() / 2, group->height() / 2);
|
||||
QPoint midpoint = group->mapTo(window, local_midpoint);
|
||||
QPoint delta = midpoint - preferred_location;
|
||||
int distance_squared = delta.x() * delta.x() + delta.y() * delta.y();
|
||||
|
||||
if (!best_group || distance_squared < best_distance_squared)
|
||||
{
|
||||
best_group = group_controller;
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_group && best_group->dockWidgetCount() > 0)
|
||||
{
|
||||
KDDockWidgets::Core::DockWidget* other_dock_widget = best_group->dockWidgetAt(0);
|
||||
other_dock_widget->addDockWidgetAsTab(dock_widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widget->view());
|
||||
window->addDockWidget(dock_view, KDDockWidgets::Location_OnTop);
|
||||
}
|
||||
}
|
||||
|
||||
const char* DockUtils::locationToString(KDDockWidgets::Location location)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case KDDockWidgets::Location_None:
|
||||
return "none";
|
||||
case KDDockWidgets::Location_OnLeft:
|
||||
return "left";
|
||||
case KDDockWidgets::Location_OnTop:
|
||||
return "top";
|
||||
case KDDockWidgets::Location_OnRight:
|
||||
return "right";
|
||||
case KDDockWidgets::Location_OnBottom:
|
||||
return "bottom";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kddockwidgets/KDDockWidgets.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/MainWindow.h>
|
||||
|
||||
namespace DockUtils
|
||||
{
|
||||
struct DockWidgetPair
|
||||
{
|
||||
KDDockWidgets::Core::DockWidget* controller = nullptr;
|
||||
KDDockWidgets::QtWidgets::DockWidget* view = nullptr;
|
||||
};
|
||||
|
||||
DockWidgetPair dockWidgetFromName(QString unique_name);
|
||||
|
||||
enum PreferredLocation
|
||||
{
|
||||
TOP_LEFT,
|
||||
TOP_MIDDLE,
|
||||
TOP_RIGHT,
|
||||
MIDDLE_LEFT,
|
||||
MIDDLE_MIDDLE,
|
||||
MIDDLE_RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_MIDDLE,
|
||||
BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
void insertDockWidgetAtPreferredLocation(
|
||||
KDDockWidgets::Core::DockWidget* dock_widget,
|
||||
PreferredLocation location,
|
||||
KDDockWidgets::QtWidgets::MainWindow* window);
|
||||
|
||||
const char* locationToString(KDDockWidgets::Location location);
|
||||
} // namespace DockUtils
|
|
@ -0,0 +1,224 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockViews.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/core/TabBar.h>
|
||||
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createDockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags) const
|
||||
{
|
||||
return new DockWidget(unique_name, options, layout_saver_options, window_flags);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createTitleBar(
|
||||
KDDockWidgets::Core::TitleBar* controller,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockTitleBar(controller, parent);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createStack(
|
||||
KDDockWidgets::Core::Stack* controller,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockStack(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createTabBar(
|
||||
KDDockWidgets::Core::TabBar* tabBar,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockTabBar(tabBar, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockWidget::DockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags)
|
||||
: KDDockWidgets::QtWidgets::DockWidget(unique_name, options, layout_saver_options, window_flags)
|
||||
{
|
||||
connect(this, &DockWidget::isOpenChanged, this, &DockWidget::openStateChanged);
|
||||
}
|
||||
|
||||
void DockWidget::openStateChanged(bool open)
|
||||
{
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(sender());
|
||||
|
||||
KDDockWidgets::Core::DockWidget* controller = view->asController<KDDockWidgets::Core::DockWidget>();
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
if (!open && g_debugger_window)
|
||||
g_debugger_window->dockManager().dockWidgetClosed(controller);
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent)
|
||||
: KDDockWidgets::QtWidgets::TitleBar(controller, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DockTitleBar::mouseDoubleClickEvent(QMouseEvent* ev)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::TitleBar::mouseDoubleClickEvent(ev);
|
||||
else
|
||||
ev->ignore();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockStack::DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::Stack(controller, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DockStack::init()
|
||||
{
|
||||
KDDockWidgets::QtWidgets::Stack::init();
|
||||
|
||||
if (g_debugger_window)
|
||||
{
|
||||
bool locked = g_debugger_window->dockManager().isLayoutLocked();
|
||||
setTabsClosable(!locked);
|
||||
}
|
||||
}
|
||||
|
||||
void DockStack::mouseDoubleClickEvent(QMouseEvent* ev)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::Stack::mouseDoubleClickEvent(ev);
|
||||
else
|
||||
ev->ignore();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::TabBar(controller, parent)
|
||||
{
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::contextMenu);
|
||||
}
|
||||
|
||||
void DockTabBar::contextMenu(QPoint pos)
|
||||
{
|
||||
auto tab_bar = qobject_cast<KDDockWidgets::QtWidgets::TabBar*>(sender());
|
||||
int tab_index = tab_bar->tabAt(pos);
|
||||
|
||||
// Filter out the placeholder widget displayed when there are no layouts.
|
||||
if (!hasDebuggerWidget(tab_index))
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(tr("Dock Widget Menu"), tab_bar);
|
||||
|
||||
QMenu* set_target_menu = menu->addMenu(tr("Set Target"));
|
||||
|
||||
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);
|
||||
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<KDDockWidgets::Core::TabBar>();
|
||||
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<KDDockWidgets::QtWidgets::DockWidget*>(dock_controller->view());
|
||||
|
||||
DebuggerWidget* widget = qobject_cast<DebuggerWidget*>(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);
|
||||
});
|
||||
set_target_menu->addAction(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<KDDockWidgets::Core::TabBar>();
|
||||
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<KDDockWidgets::QtWidgets::DockWidget*>(dock_controller->view());
|
||||
|
||||
DebuggerWidget* widget = qobject_cast<DebuggerWidget*>(dock_view->widget());
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
if (!widget->setCpuOverride(std::nullopt))
|
||||
g_debugger_window->dockManager().recreateDebuggerWidget(dock_view->uniqueName());
|
||||
|
||||
g_debugger_window->dockManager().retranslateDockWidget(dock_controller);
|
||||
});
|
||||
set_target_menu->addAction(inherit_action);
|
||||
|
||||
menu->popup(tab_bar->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
bool DockTabBar::hasDebuggerWidget(int tab_index)
|
||||
{
|
||||
KDDockWidgets::Core::TabBar* tab_bar_controller = asController<KDDockWidgets::Core::TabBar>();
|
||||
if (!tab_bar_controller)
|
||||
return false;
|
||||
|
||||
KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index);
|
||||
if (!dock_controller)
|
||||
return false;
|
||||
|
||||
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_controller->view());
|
||||
|
||||
DebuggerWidget* widget = qobject_cast<DebuggerWidget*>(dock_view->widget());
|
||||
if (!widget)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DockTabBar::mouseDoubleClickEvent(QMouseEvent* ev)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::TabBar::mouseDoubleClickEvent(ev);
|
||||
else
|
||||
ev->ignore();
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kddockwidgets/qtwidgets/ViewFactory.h>
|
||||
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/views/Stack.h>
|
||||
#include <kddockwidgets/qtwidgets/views/TitleBar.h>
|
||||
#include <kddockwidgets/qtwidgets/views/TabBar.h>
|
||||
|
||||
class DockManager;
|
||||
|
||||
class DockViewFactory : public KDDockWidgets::QtWidgets::ViewFactory
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
KDDockWidgets::Core::View* createDockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options = {},
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options = {},
|
||||
Qt::WindowFlags window_flags = {}) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createTitleBar(
|
||||
KDDockWidgets::Core::TitleBar* controller,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createStack(
|
||||
KDDockWidgets::Core::Stack* controller,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createTabBar(
|
||||
KDDockWidgets::Core::TabBar* tabBar,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
};
|
||||
|
||||
class DockWidget : public KDDockWidgets::QtWidgets::DockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags);
|
||||
|
||||
protected:
|
||||
void openStateChanged(bool open);
|
||||
};
|
||||
|
||||
class DockTitleBar : public KDDockWidgets::QtWidgets::TitleBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QMouseEvent* ev) override;
|
||||
};
|
||||
|
||||
class DockStack : public KDDockWidgets::QtWidgets::Stack
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent = nullptr);
|
||||
|
||||
void init() override;
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QMouseEvent* ev) override;
|
||||
};
|
||||
|
||||
class DockTabBar : public KDDockWidgets::QtWidgets::TabBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void contextMenu(QPoint pos);
|
||||
bool hasDebuggerWidget(int tab_index);
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent* ev) override;
|
||||
};
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "LayoutEditorDialog.h"
|
||||
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
Q_DECLARE_METATYPE(LayoutEditorDialog::InitialState);
|
||||
|
||||
LayoutEditorDialog::LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_name_validator(name_validator)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setWindowTitle(tr("New Layout"));
|
||||
|
||||
setupInputWidgets(BREAKPOINT_EE, can_clone_current_layout);
|
||||
|
||||
onNameChanged();
|
||||
}
|
||||
|
||||
LayoutEditorDialog::LayoutEditorDialog(
|
||||
const std::string& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_name_validator(name_validator)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setWindowTitle(tr("Edit Layout"));
|
||||
|
||||
m_ui.nameEditor->setText(QString::fromStdString(name));
|
||||
|
||||
setupInputWidgets(cpu, {});
|
||||
|
||||
m_ui.initialStateLabel->hide();
|
||||
m_ui.initialStateEditor->hide();
|
||||
|
||||
onNameChanged();
|
||||
}
|
||||
|
||||
std::string LayoutEditorDialog::name()
|
||||
{
|
||||
return m_ui.nameEditor->text().toStdString();
|
||||
}
|
||||
|
||||
BreakPointCpu LayoutEditorDialog::cpu()
|
||||
{
|
||||
return static_cast<BreakPointCpu>(m_ui.cpuEditor->currentData().toInt());
|
||||
}
|
||||
|
||||
LayoutEditorDialog::InitialState LayoutEditorDialog::initial_state()
|
||||
{
|
||||
return m_ui.initialStateEditor->currentData().value<InitialState>();
|
||||
}
|
||||
|
||||
void LayoutEditorDialog::setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout)
|
||||
{
|
||||
connect(m_ui.nameEditor, &QLineEdit::textChanged, this, &LayoutEditorDialog::onNameChanged);
|
||||
|
||||
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);
|
||||
m_ui.cpuEditor->addItem(text, cpu);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_ui.cpuEditor->count(); i++)
|
||||
if (m_ui.cpuEditor->itemData(i).toInt() == cpu)
|
||||
m_ui.cpuEditor->setCurrentIndex(i);
|
||||
|
||||
for (size_t i = 0; i < DockTables::DEFAULT_DOCK_LAYOUTS.size(); i++)
|
||||
m_ui.initialStateEditor->addItem(
|
||||
tr("Create Default \"%1\" Layout").arg(tr(DockTables::DEFAULT_DOCK_LAYOUTS[i].name.c_str())),
|
||||
QVariant::fromValue(InitialState(DEFAULT_LAYOUT, i)));
|
||||
|
||||
m_ui.initialStateEditor->addItem(tr("Create Blank Layout"), QVariant::fromValue(InitialState(BLANK_LAYOUT, 0)));
|
||||
|
||||
if (can_clone_current_layout)
|
||||
m_ui.initialStateEditor->addItem(tr("Clone Current Layout"), QVariant::fromValue(InitialState(CLONE_LAYOUT, 0)));
|
||||
|
||||
m_ui.initialStateEditor->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void LayoutEditorDialog::onNameChanged()
|
||||
{
|
||||
QString error_message;
|
||||
|
||||
if (m_ui.nameEditor->text().isEmpty())
|
||||
{
|
||||
error_message = tr("Name is empty.");
|
||||
}
|
||||
else if (!m_name_validator(m_ui.nameEditor->text().toStdString()))
|
||||
{
|
||||
error_message = tr("A layout with that name already exists.");
|
||||
}
|
||||
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error_message.isEmpty());
|
||||
m_ui.errorMessage->setText(error_message);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_LayoutEditorDialog.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class LayoutEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using NameValidator = std::function<bool(const std::string&)>;
|
||||
|
||||
enum CreationMode
|
||||
{
|
||||
DEFAULT_LAYOUT,
|
||||
BLANK_LAYOUT,
|
||||
CLONE_LAYOUT,
|
||||
};
|
||||
|
||||
// Bundles together a creation mode and inital state.
|
||||
using InitialState = std::pair<CreationMode, size_t>;
|
||||
|
||||
// Create a "New Layout" dialog.
|
||||
LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent = nullptr);
|
||||
|
||||
// Create a "Edit Layout" dialog.
|
||||
LayoutEditorDialog(const std::string& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent = nullptr);
|
||||
|
||||
std::string name();
|
||||
BreakPointCpu cpu();
|
||||
InitialState initial_state();
|
||||
|
||||
private:
|
||||
void setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout);
|
||||
void onNameChanged();
|
||||
|
||||
Ui::LayoutEditorDialog m_ui;
|
||||
NameValidator m_name_validator;
|
||||
};
|
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LayoutEditorDialog</class>
|
||||
<widget class="QDialog" name="LayoutEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="cpuLabel">
|
||||
<property name="text">
|
||||
<string>Target</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="initialStateLabel">
|
||||
<property name="text">
|
||||
<string>Initial State</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cpuEditor"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nameEditor"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="initialStateEditor"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="errorMessage">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: red</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>LayoutEditorDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>LayoutEditorDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "NoLayoutsWidget.h"
|
||||
|
||||
NoLayoutsWidget::NoLayoutsWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
}
|
||||
|
||||
QPushButton* NoLayoutsWidget::createDefaultLayoutsButton()
|
||||
{
|
||||
return m_ui.createDefaultLayoutsButton;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_NoLayoutsWidget.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
class NoLayoutsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NoLayoutsWidget(QWidget* parent = nullptr);
|
||||
|
||||
QPushButton* createDefaultLayoutsButton();
|
||||
|
||||
private:
|
||||
Ui::NoLayoutsWidget m_ui;
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NoLayoutsWidget</class>
|
||||
<widget class="QWidget" name="NoLayoutsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="topSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>There are no layouts.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="leftSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="createDefaultLayoutsButton">
|
||||
<property name="text">
|
||||
<string>Create Default Layouts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="rightSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="bottomSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
// Container for a JSON value. This exists solely so that we can forward declare
|
||||
// it to avoid pulling in rapidjson for the entire debugger.
|
||||
class JsonValueWrapper
|
||||
{
|
||||
public:
|
||||
JsonValueWrapper(
|
||||
rapidjson::Value& value,
|
||||
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& allocator)
|
||||
: m_value(value)
|
||||
, m_allocator(allocator)
|
||||
{
|
||||
}
|
||||
|
||||
rapidjson::Value& value()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& allocator()
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
private:
|
||||
rapidjson::Value& m_value;
|
||||
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& m_allocator;
|
||||
};
|
|
@ -41,7 +41,7 @@ void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
|
|||
}
|
||||
}
|
||||
|
||||
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height)
|
||||
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
|
||||
{
|
||||
rowHeight = painter.fontMetrics().height() + 2;
|
||||
const s32 charWidth = painter.fontMetrics().averageCharWidth();
|
||||
|
@ -106,7 +106,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
|||
{
|
||||
case MemoryViewType::BYTE:
|
||||
{
|
||||
const u8 val = static_cast<u8>(m_cpu->read8(thisSegmentsStart, valid));
|
||||
const u8 val = static_cast<u8>(cpu.read8(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "??");
|
||||
|
@ -114,7 +114,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
|||
}
|
||||
case MemoryViewType::BYTEHW:
|
||||
{
|
||||
const u16 val = convertEndian<u16>(static_cast<u16>(m_cpu->read16(thisSegmentsStart, valid)));
|
||||
const u16 val = convertEndian<u16>(static_cast<u16>(cpu.read16(thisSegmentsStart, valid)));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????");
|
||||
|
@ -122,7 +122,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
|||
}
|
||||
case MemoryViewType::WORD:
|
||||
{
|
||||
const u32 val = convertEndian<u32>(m_cpu->read32(thisSegmentsStart, valid));
|
||||
const u32 val = convertEndian<u32>(cpu.read32(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????");
|
||||
|
@ -130,7 +130,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
|||
}
|
||||
case MemoryViewType::DWORD:
|
||||
{
|
||||
const u64 val = convertEndian<u64>(m_cpu->read64(thisSegmentsStart, valid));
|
||||
const u64 val = convertEndian<u64>(cpu.read64(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????????????");
|
||||
|
@ -153,7 +153,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
|||
painter.setPen(palette.text().color());
|
||||
|
||||
bool valid;
|
||||
const u8 value = m_cpu->read8(currentRowAddress + j, valid);
|
||||
const u8 value = cpu.read8(currentRowAddress + j, valid);
|
||||
if (valid)
|
||||
{
|
||||
QChar curChar = QChar::fromLatin1(value);
|
||||
|
@ -216,54 +216,54 @@ void MemoryViewTable::SelectAt(QPoint pos)
|
|||
}
|
||||
}
|
||||
|
||||
u128 MemoryViewTable::GetSelectedSegment()
|
||||
u128 MemoryViewTable::GetSelectedSegment(DebugInterface& cpu)
|
||||
{
|
||||
u128 val;
|
||||
switch (displayType)
|
||||
{
|
||||
case MemoryViewType::BYTE:
|
||||
val.lo = m_cpu->read8(selectedAddress);
|
||||
val.lo = cpu.read8(selectedAddress);
|
||||
break;
|
||||
case MemoryViewType::BYTEHW:
|
||||
val.lo = convertEndian(static_cast<u16>(m_cpu->read16(selectedAddress & ~1)));
|
||||
val.lo = convertEndian(static_cast<u16>(cpu.read16(selectedAddress & ~1)));
|
||||
break;
|
||||
case MemoryViewType::WORD:
|
||||
val.lo = convertEndian(m_cpu->read32(selectedAddress & ~3));
|
||||
val.lo = convertEndian(cpu.read32(selectedAddress & ~3));
|
||||
break;
|
||||
case MemoryViewType::DWORD:
|
||||
val._u64[0] = convertEndian(m_cpu->read64(selectedAddress & ~7));
|
||||
val._u64[0] = convertEndian(cpu.read64(selectedAddress & ~7));
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void MemoryViewTable::InsertIntoSelectedHexView(u8 value)
|
||||
void MemoryViewTable::InsertIntoSelectedHexView(u8 value, DebugInterface& cpu)
|
||||
{
|
||||
const u8 mask = selectedNibbleHI ? 0x0f : 0xf0;
|
||||
u8 curVal = m_cpu->read8(selectedAddress) & mask;
|
||||
u8 curVal = cpu.read8(selectedAddress) & mask;
|
||||
u8 newVal = value << (selectedNibbleHI ? 4 : 0);
|
||||
curVal |= newVal;
|
||||
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = curVal] {
|
||||
cpu->write8(address, val);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = curVal] {
|
||||
cpu.write8(address, val);
|
||||
QtHost::RunOnUIThread([this] { parent->update(); });
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryViewTable::InsertAtCurrentSelection(const QString& text)
|
||||
void MemoryViewTable::InsertAtCurrentSelection(const QString& text, DebugInterface& cpu)
|
||||
{
|
||||
if (!m_cpu->isValidAddress(selectedAddress))
|
||||
if (!cpu.isValidAddress(selectedAddress))
|
||||
return;
|
||||
|
||||
// If pasting into the hex view, also decode the input as hex bytes.
|
||||
// This approach prevents one from pasting on a nibble boundary, but that is almost always
|
||||
// user error, and we don't have an undo function in this view, so best to stay conservative.
|
||||
QByteArray input = selectedText ? text.toUtf8() : QByteArray::fromHex(text.toUtf8());
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, inBytes = input] {
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, inBytes = input] {
|
||||
u32 currAddr = address;
|
||||
for (int i = 0; i < inBytes.size(); i++)
|
||||
{
|
||||
cpu->write8(currAddr, inBytes[i]);
|
||||
cpu.write8(currAddr, inBytes[i]);
|
||||
currAddr = nextAddress(currAddr);
|
||||
QtHost::RunOnUIThread([this] { parent->update(); });
|
||||
}
|
||||
|
@ -343,9 +343,9 @@ void MemoryViewTable::BackwardSelection()
|
|||
|
||||
|
||||
// We need both key and keychar because `key` is easy to use, but is case insensitive
|
||||
bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu)
|
||||
{
|
||||
if (!m_cpu->isValidAddress(selectedAddress))
|
||||
if (!cpu.isValidAddress(selectedAddress))
|
||||
return false;
|
||||
|
||||
bool pressHandled = false;
|
||||
|
@ -356,8 +356,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
|||
{
|
||||
if (keyCharIsText || (!keychar.isNonCharacter() && keychar.category() != QChar::Other_Control))
|
||||
{
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = keychar.toLatin1()] {
|
||||
cpu->write8(address, val);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = keychar.toLatin1()] {
|
||||
cpu.write8(address, val);
|
||||
QtHost::RunOnUIThread([this] { UpdateSelectedAddress(selectedAddress + 1); parent->update(); });
|
||||
});
|
||||
pressHandled = true;
|
||||
|
@ -367,8 +367,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
|||
{
|
||||
case Qt::Key::Key_Backspace:
|
||||
case Qt::Key::Key_Escape:
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu] {
|
||||
cpu->write8(address, 0);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu] {
|
||||
cpu.write8(address, 0);
|
||||
QtHost::RunOnUIThread([this] {BackwardSelection(); parent->update(); });
|
||||
});
|
||||
pressHandled = true;
|
||||
|
@ -395,7 +395,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
|||
const u8 keyPressed = static_cast<u8>(QString(QChar(key)).toInt(&pressHandled, 16));
|
||||
if (pressHandled)
|
||||
{
|
||||
InsertIntoSelectedHexView(keyPressed);
|
||||
InsertIntoSelectedHexView(keyPressed, cpu);
|
||||
ForwardSelection();
|
||||
}
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
|||
{
|
||||
case Qt::Key::Key_Backspace:
|
||||
case Qt::Key::Key_Escape:
|
||||
InsertIntoSelectedHexView(0);
|
||||
InsertIntoSelectedHexView(0, cpu);
|
||||
BackwardSelection();
|
||||
pressHandled = true;
|
||||
break;
|
||||
|
@ -459,7 +459,6 @@ MemoryViewWidget::MemoryViewWidget(DebugInterface& cpu, QWidget* parent)
|
|||
this->setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::customMenuRequested);
|
||||
|
||||
m_table.SetCpu(&cpu);
|
||||
m_table.UpdateStartAddress(0x480000);
|
||||
|
||||
applyMonospaceFont();
|
||||
|
@ -476,7 +475,7 @@ void MemoryViewWidget::paintEvent(QPaintEvent* event)
|
|||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
m_table.DrawTable(painter, this->palette(), this->height());
|
||||
m_table.DrawTable(painter, this->palette(), this->height(), cpu());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::mousePressEvent(QMouseEvent* event)
|
||||
|
@ -580,7 +579,7 @@ void MemoryViewWidget::contextCopyByte()
|
|||
|
||||
void MemoryViewWidget::contextCopySegment()
|
||||
{
|
||||
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment().lo, 16).toUpper());
|
||||
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment(cpu()).lo, 16).toUpper());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::contextCopyCharacter()
|
||||
|
@ -590,7 +589,7 @@ void MemoryViewWidget::contextCopyCharacter()
|
|||
|
||||
void MemoryViewWidget::contextPaste()
|
||||
{
|
||||
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text());
|
||||
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text(), cpu());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::contextGoToAddress()
|
||||
|
@ -632,7 +631,7 @@ void MemoryViewWidget::wheelEvent(QWheelEvent* event)
|
|||
|
||||
void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0'))
|
||||
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
|
|
|
@ -29,7 +29,6 @@ enum class MemoryViewType
|
|||
class MemoryViewTable
|
||||
{
|
||||
QWidget* parent;
|
||||
DebugInterface* m_cpu;
|
||||
MemoryViewType displayType = MemoryViewType::BYTE;
|
||||
bool littleEndian = true;
|
||||
u32 rowCount;
|
||||
|
@ -46,7 +45,7 @@ class MemoryViewTable
|
|||
|
||||
bool selectedNibbleHI = false;
|
||||
|
||||
void InsertIntoSelectedHexView(u8 value);
|
||||
void InsertIntoSelectedHexView(u8 value, DebugInterface& cpu);
|
||||
|
||||
template <class T>
|
||||
T convertEndian(T in)
|
||||
|
@ -66,24 +65,23 @@ class MemoryViewTable
|
|||
|
||||
public:
|
||||
MemoryViewTable(QWidget* parent)
|
||||
: parent(parent){};
|
||||
: parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
u32 startAddress;
|
||||
u32 selectedAddress;
|
||||
|
||||
void SetCpu(DebugInterface* cpu)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
}
|
||||
void UpdateStartAddress(u32 start);
|
||||
void UpdateSelectedAddress(u32 selected, bool page = false);
|
||||
void DrawTable(QPainter& painter, const QPalette& palette, s32 height);
|
||||
void DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu);
|
||||
void SelectAt(QPoint pos);
|
||||
u128 GetSelectedSegment();
|
||||
void InsertAtCurrentSelection(const QString& text);
|
||||
u128 GetSelectedSegment(DebugInterface& cpu);
|
||||
void InsertAtCurrentSelection(const QString& text, DebugInterface& cpu);
|
||||
void ForwardSelection();
|
||||
void BackwardSelection();
|
||||
// Returns true if the keypress was handled
|
||||
bool KeyPress(int key, QChar keychar);
|
||||
bool KeyPress(int key, QChar keychar, DebugInterface& cpu);
|
||||
|
||||
MemoryViewType GetViewType()
|
||||
{
|
||||
|
@ -106,7 +104,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
class MemoryViewWidget final : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
#include "Settings/AchievementLoginDialog.h"
|
||||
#include "Settings/ControllerSettingsWindow.h"
|
||||
#include "Settings/GameListSettingsWidget.h"
|
||||
|
@ -620,12 +621,7 @@ void MainWindow::quit()
|
|||
|
||||
void MainWindow::destroySubWindows()
|
||||
{
|
||||
if (m_debugger_window)
|
||||
{
|
||||
m_debugger_window->close();
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
DebuggerWindow::destroyInstance();
|
||||
|
||||
if (m_controller_settings_window)
|
||||
{
|
||||
|
@ -850,12 +846,8 @@ void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
|
|||
{
|
||||
// If PauseOnEntry is enabled, we prompt the user to disable Hardcore Mode
|
||||
// or cancel the action later, so we should keep the debugger around
|
||||
if (m_debugger_window && !DebugInterface::getPauseOnEntry())
|
||||
{
|
||||
m_debugger_window->close();
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
if (g_debugger_window && !DebugInterface::getPauseOnEntry())
|
||||
DebuggerWindow::destroyInstance();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1145,7 +1137,7 @@ bool MainWindow::shouldMouseLock() const
|
|||
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
|
||||
return false;
|
||||
|
||||
bool windowsHidden = (!m_debugger_window || m_debugger_window->isHidden()) &&
|
||||
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
|
||||
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
|
||||
(!m_settings_window || m_settings_window->isHidden());
|
||||
|
||||
|
@ -1481,7 +1473,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
|||
connect(action, &QAction::triggered, [this, entry]() {
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
startGameListEntry(entry);
|
||||
getDebuggerWindow()->show();
|
||||
DebuggerWindow::getInstance()->show();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1821,10 +1813,10 @@ void MainWindow::updateTheme()
|
|||
{
|
||||
// The debugger hates theme changes.
|
||||
// We have unfortunately to destroy it and recreate it.
|
||||
const bool debugger_is_open = m_debugger_window ? m_debugger_window->isVisible() : false;
|
||||
const QSize debugger_size = m_debugger_window ? m_debugger_window->size() : QSize();
|
||||
const QPoint debugger_pos = m_debugger_window ? m_debugger_window->pos() : QPoint();
|
||||
if (m_debugger_window)
|
||||
const bool debugger_is_open = g_debugger_window ? g_debugger_window->isVisible() : false;
|
||||
const QSize debugger_size = g_debugger_window ? g_debugger_window->size() : QSize();
|
||||
const QPoint debugger_pos = g_debugger_window ? g_debugger_window->pos() : QPoint();
|
||||
if (g_debugger_window)
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Theme Change"),
|
||||
tr("Changing the theme will close the debugger window. Any unsaved data will be lost. Do you want to continue?"),
|
||||
|
@ -1837,16 +1829,15 @@ void MainWindow::updateTheme()
|
|||
QtHost::UpdateApplicationTheme();
|
||||
reloadThemeSpecificImages();
|
||||
|
||||
if (m_debugger_window)
|
||||
if (g_debugger_window)
|
||||
{
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
getDebuggerWindow(); // populates m_debugger_window
|
||||
m_debugger_window->resize(debugger_size);
|
||||
m_debugger_window->move(debugger_pos);
|
||||
DebuggerWindow::destroyInstance();
|
||||
DebuggerWindow::createInstance();
|
||||
g_debugger_window->resize(debugger_size);
|
||||
g_debugger_window->move(debugger_pos);
|
||||
if (debugger_is_open)
|
||||
{
|
||||
m_debugger_window->show();
|
||||
g_debugger_window->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2779,23 +2770,9 @@ void MainWindow::doSettings(const char* category /* = nullptr */)
|
|||
dlg->setCategory(category);
|
||||
}
|
||||
|
||||
DebuggerWindow* MainWindow::getDebuggerWindow()
|
||||
{
|
||||
if (!m_debugger_window)
|
||||
{
|
||||
// Setup KDDockWidgets.
|
||||
DockManager::configure_docking_system();
|
||||
|
||||
// Don't pass us (this) as the parent, otherwise the window is always on top of the mainwindow (on windows at least)
|
||||
m_debugger_window = new DebuggerWindow(nullptr);
|
||||
}
|
||||
|
||||
return m_debugger_window;
|
||||
}
|
||||
|
||||
void MainWindow::openDebugger()
|
||||
{
|
||||
DebuggerWindow* dwnd = getDebuggerWindow();
|
||||
DebuggerWindow* dwnd = DebuggerWindow::getInstance();
|
||||
dwnd->isVisible() ? dwnd->activateWindow() : dwnd->show();
|
||||
}
|
||||
|
||||
|
|
|
@ -260,8 +260,6 @@ private:
|
|||
InputRecordingViewer* getInputRecordingViewer();
|
||||
void updateInputRecordingActions(bool started);
|
||||
|
||||
DebuggerWindow* getDebuggerWindow();
|
||||
|
||||
void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);
|
||||
|
||||
QString getDiscDevicePath(const QString& title);
|
||||
|
@ -291,8 +289,6 @@ private:
|
|||
InputRecordingViewer* m_input_recording_viewer = nullptr;
|
||||
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||
|
||||
DebuggerWindow* m_debugger_window = nullptr;
|
||||
|
||||
QProgressBar* m_status_progress_widget = nullptr;
|
||||
QLabel* m_status_verbose_widget = nullptr;
|
||||
QLabel* m_status_renderer_widget = nullptr;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "Config.h"
|
||||
|
||||
#include <QtGui/QStandardItemModel>
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class SettingsWindow;
|
||||
|
||||
|
|
|
@ -47,9 +47,10 @@
|
|||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\demangler\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\ccc\src</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidjson\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)pcsx2</AdditionalIncludeDirectories>
|
||||
<!-- Needed for moc pch -->
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Breakpoints;$(ProjectDir)\Debugger\Memory;$(ProjectDir)\Debugger\SymbolTree</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Breakpoints;$(ProjectDir)\Debugger\Docking;$(ProjectDir)\Debugger\Memory;$(ProjectDir)\Debugger\SymbolTree</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
<ForcedIncludeFiles>PrecompiledHeader.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
|
||||
|
@ -113,7 +114,6 @@
|
|||
<ClCompile Include="Debugger\DebuggerWidget.cpp" />
|
||||
<ClCompile Include="Debugger\DebuggerWindow.cpp" />
|
||||
<ClCompile Include="Debugger\DisassemblyWidget.cpp" />
|
||||
<ClCompile Include="Debugger\DockManager.cpp" />
|
||||
<ClCompile Include="Debugger\RegisterWidget.cpp" />
|
||||
<ClCompile Include="Debugger\DebuggerSettingsManager.cpp" />
|
||||
<ClCompile Include="Debugger\StackModel.cpp" />
|
||||
|
@ -123,6 +123,13 @@
|
|||
<ClCompile Include="Debugger\Breakpoints\BreakpointDialog.cpp" />
|
||||
<ClCompile Include="Debugger\Breakpoints\BreakpointModel.cpp" />
|
||||
<ClCompile Include="Debugger\Breakpoints\BreakpointWidget.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\DockLayout.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\DockManager.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\DockTables.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\DockUtils.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\DockViews.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\LayoutEditorDialog.cpp" />
|
||||
<ClCompile Include="Debugger\Docking\NoLayoutsWidget.cpp" />
|
||||
<ClCompile Include="Debugger\Memory\MemorySearchWidget.cpp" />
|
||||
<ClCompile Include="Debugger\Memory\MemoryViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\Memory\SavedAddressesModel.cpp" />
|
||||
|
@ -220,7 +227,6 @@
|
|||
<QtMoc Include="Debugger\DebuggerWidget.h" />
|
||||
<QtMoc Include="Debugger\DebuggerWindow.h" />
|
||||
<QtMoc Include="Debugger\DisassemblyWidget.h" />
|
||||
<QtMoc Include="Debugger\DockManager.h" />
|
||||
<QtMoc Include="Debugger\RegisterWidget.h" />
|
||||
<QtMoc Include="Debugger\StackModel.h" />
|
||||
<QtMoc Include="Debugger\StackWidget.h" />
|
||||
|
@ -230,6 +236,13 @@
|
|||
<QtMoc Include="Debugger\Breakpoints\BreakpointDialog.h" />
|
||||
<QtMoc Include="Debugger\Breakpoints\BreakpointModel.h" />
|
||||
<QtMoc Include="Debugger\Breakpoints\BreakpointWidget.h" />
|
||||
<QtMoc Include="Debugger\Docking\DockLayout.h" />
|
||||
<QtMoc Include="Debugger\Docking\DockManager.h" />
|
||||
<QtMoc Include="Debugger\Docking\DockTables.h" />
|
||||
<QtMoc Include="Debugger\Docking\DockUtils.h" />
|
||||
<QtMoc Include="Debugger\Docking\DockViews.h" />
|
||||
<QtMoc Include="Debugger\Docking\LayoutEditorDialog.h" />
|
||||
<QtMoc Include="Debugger\Docking\NoLayoutsWidget.h" />
|
||||
<QtMoc Include="Debugger\Memory\MemorySearchWidget.h" />
|
||||
<QtMoc Include="Debugger\Memory\MemoryViewWidget.h" />
|
||||
<QtMoc Include="Debugger\Memory\SavedAddressesModel.h" />
|
||||
|
@ -285,7 +298,6 @@
|
|||
<ClCompile Include="$(IntDir)Debugger\moc_DebuggerWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_DebuggerWindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_DisassemblyWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_DockManager.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_RegisterWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_StackModel.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_StackWidget.cpp" />
|
||||
|
@ -294,6 +306,10 @@
|
|||
<ClCompile Include="$(IntDir)Debugger\Breakpoints\moc_BreakpointDialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Breakpoints\moc_BreakpointModel.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Breakpoints\moc_BreakpointWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_DockManager.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_DockViews.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_LayoutEditorDialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_NoLayoutsWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Memory\moc_MemorySearchWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Memory\moc_MemoryViewWidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)Debugger\Memory\moc_SavedAddressesModel.cpp" />
|
||||
|
@ -433,6 +449,12 @@
|
|||
<QtUi Include="Debugger\Breakpoints\BreakpointWidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Docking\LayoutEditorDialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Docking\NoLayoutsWidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Memory\MemorySearchWidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
|
|
|
@ -281,9 +281,6 @@
|
|||
<ClCompile Include="Debugger\DisassemblyWidget.cpp">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\DockManager.cpp">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\RegisterWidget.cpp">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClCompile>
|
||||
|
@ -308,6 +305,27 @@
|
|||
<ClCompile Include="Debugger\Breakpoints\BreakpointWidget.cpp">
|
||||
<Filter>Debugger\Breakpoints</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\DockLayout.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\DockManager.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\DockTables.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\DockUtils.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\DockViews.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\LayoutEditorDialog.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Docking\NoLayoutsWidget.cpp">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\Memory\MemorySearchWidget.cpp">
|
||||
<Filter>Debugger\Memory</Filter>
|
||||
</ClCompile>
|
||||
|
@ -332,9 +350,6 @@
|
|||
<ClCompile Include="$(IntDir)Debugger\moc_DisassemblyWidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_DockManager.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\moc_RegisterWidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
|
@ -359,6 +374,18 @@
|
|||
<ClCompile Include="$(IntDir)Debugger\Breakpoints\moc_BreakpointWidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_DockManager.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_DockViews.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_LayoutEditorDialog.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\Docking\moc_NoLayoutsWidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(IntDir)Debugger\Memory\moc_MemorySearchWidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
|
@ -580,9 +607,6 @@
|
|||
<QtMoc Include="Debugger\DisassemblyWidget.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\DockManager.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\RegisterWidget.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
|
@ -607,6 +631,27 @@
|
|||
<QtMoc Include="Debugger\Breakpoints\BreakpointWidget.h">
|
||||
<Filter>Debugger\Breakpoints</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\DockLayout.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\DockManager.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\DockTables.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\DockUtils.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\DockViews.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\LayoutEditorDialog.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Docking\NoLayoutsWidget.h">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\Memory\MemorySearchWidget.h">
|
||||
<Filter>Debugger\Memory</Filter>
|
||||
</QtMoc>
|
||||
|
@ -752,6 +797,12 @@
|
|||
<QtUi Include="Debugger\Breakpoints\BreakpointWidget.ui">
|
||||
<Filter>Debugger\Breakpoints</Filter>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Docking\LayoutEditorDialog.ui">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Docking\NoLayoutsWidget.ui">
|
||||
<Filter>Debugger\Docking</Filter>
|
||||
</QtUi>
|
||||
<QtUi Include="Debugger\Memory\MemorySearchWidget.ui">
|
||||
<Filter>Debugger\Memory</Filter>
|
||||
</QtUi>
|
||||
|
|
|
@ -1374,7 +1374,6 @@ namespace EmuFolders
|
|||
extern std::string AppRoot;
|
||||
extern std::string DataRoot;
|
||||
extern std::string Settings;
|
||||
extern std::string DebuggerSettings;
|
||||
extern std::string Bios;
|
||||
extern std::string Snapshots;
|
||||
extern std::string Savestates;
|
||||
|
@ -1390,6 +1389,8 @@ namespace EmuFolders
|
|||
extern std::string Textures;
|
||||
extern std::string InputProfiles;
|
||||
extern std::string Videos;
|
||||
extern std::string DebuggerLayouts;
|
||||
extern std::string DebuggerSettings;
|
||||
|
||||
/// Initializes critical folders (AppRoot, DataRoot, Settings). Call once on startup.
|
||||
void SetAppRoot();
|
||||
|
|
|
@ -158,6 +158,57 @@ bool DebugInterface::parseExpression(PostfixExpression& exp, u64& dest, std::str
|
|||
return parsePostfixExpression(exp, &funcs, dest, error);
|
||||
}
|
||||
|
||||
DebugInterface& DebugInterface::get(BreakPointCpu cpu)
|
||||
{
|
||||
switch (cpu)
|
||||
{
|
||||
case BREAKPOINT_EE:
|
||||
return r5900Debug;
|
||||
case BREAKPOINT_IOP:
|
||||
return r3000Debug;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
pxFailRel("DebugInterface::get called with invalid cpu enum.");
|
||||
return r5900Debug;
|
||||
}
|
||||
|
||||
const char* DebugInterface::cpuName(BreakPointCpu cpu)
|
||||
{
|
||||
switch (cpu)
|
||||
{
|
||||
case BREAKPOINT_EE:
|
||||
return "EE";
|
||||
case BREAKPOINT_IOP:
|
||||
return "IOP";
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
pxFailRel("DebugInterface::cpuName called with invalid cpu enum.");
|
||||
return "";
|
||||
}
|
||||
|
||||
const char* DebugInterface::longCpuName(BreakPointCpu cpu)
|
||||
{
|
||||
switch (cpu)
|
||||
{
|
||||
case BREAKPOINT_EE:
|
||||
return TRANSLATE("DebugInterface", "Emotion Engine");
|
||||
case BREAKPOINT_IOP:
|
||||
return TRANSLATE("DebugInteface", "Input Output Processor");
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
pxFailRel("DebugInterface::longCpuName called with invalid cpu enum.");
|
||||
return "";
|
||||
}
|
||||
|
||||
//
|
||||
// R5900DebugInterface
|
||||
//
|
||||
|
|
|
@ -33,6 +33,11 @@ enum BreakPointCpu
|
|||
BREAKPOINT_IOP_AND_EE = 0x03
|
||||
};
|
||||
|
||||
inline std::vector<BreakPointCpu> DEBUG_CPUS = {
|
||||
BREAKPOINT_EE,
|
||||
BREAKPOINT_IOP,
|
||||
};
|
||||
|
||||
class MemoryReader
|
||||
{
|
||||
public:
|
||||
|
@ -86,9 +91,6 @@ public:
|
|||
virtual SymbolImporter* GetSymbolImporter() const = 0;
|
||||
virtual std::vector<std::unique_ptr<BiosThread>> GetThreadList() const = 0;
|
||||
|
||||
bool evaluateExpression(const char* expression, u64& dest, std::string& error);
|
||||
bool initExpression(const char* exp, PostfixExpression& dest, std::string& error);
|
||||
bool parseExpression(PostfixExpression& exp, u64& dest, std::string& error);
|
||||
bool isAlive();
|
||||
bool isCpuPaused();
|
||||
void pauseCpu();
|
||||
|
@ -98,9 +100,17 @@ public:
|
|||
std::optional<u32> getCallerStackPointer(const ccc::Function& currentFunction);
|
||||
std::optional<u32> getStackFrameSize(const ccc::Function& currentFunction);
|
||||
|
||||
bool evaluateExpression(const char* expression, u64& dest, std::string& error);
|
||||
bool initExpression(const char* exp, PostfixExpression& dest, std::string& error);
|
||||
bool parseExpression(PostfixExpression& exp, u64& dest, std::string& error);
|
||||
|
||||
static void setPauseOnEntry(bool pauseOnEntry) { m_pause_on_entry = pauseOnEntry; };
|
||||
static bool getPauseOnEntry() { return m_pause_on_entry; }
|
||||
|
||||
static DebugInterface& get(BreakPointCpu cpu);
|
||||
static const char* cpuName(BreakPointCpu cpu);
|
||||
static const char* longCpuName(BreakPointCpu cpu);
|
||||
|
||||
private:
|
||||
static bool m_pause_on_entry;
|
||||
};
|
||||
|
|
|
@ -152,6 +152,7 @@ namespace EmuFolders
|
|||
std::string AppRoot;
|
||||
std::string DataRoot;
|
||||
std::string Settings;
|
||||
std::string DebuggerLayouts;
|
||||
std::string DebuggerSettings;
|
||||
std::string Bios;
|
||||
std::string Snapshots;
|
||||
|
@ -2245,6 +2246,8 @@ void EmuFolders::SetDefaults(SettingsInterface& si)
|
|||
si.SetStringValue("Folders", "Textures", "textures");
|
||||
si.SetStringValue("Folders", "InputProfiles", "inputprofiles");
|
||||
si.SetStringValue("Folders", "Videos", "videos");
|
||||
si.SetStringValue("Folders", "DebuggerLayouts", "debuggerlayouts");
|
||||
si.SetStringValue("Folders", "DebuggerSettings", "debuggersettings");
|
||||
}
|
||||
|
||||
static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* name, const char* def)
|
||||
|
@ -2271,6 +2274,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
|||
Textures = LoadPathFromSettings(si, DataRoot, "Textures", "textures");
|
||||
InputProfiles = LoadPathFromSettings(si, DataRoot, "InputProfiles", "inputprofiles");
|
||||
Videos = LoadPathFromSettings(si, DataRoot, "Videos", "videos");
|
||||
DebuggerLayouts = LoadPathFromSettings(si, Settings, "DebuggerLayouts", "debuggerlayouts");
|
||||
DebuggerSettings = LoadPathFromSettings(si, Settings, "DebuggerSettings", "debuggersettings");
|
||||
|
||||
Console.WriteLn("BIOS Directory: %s", Bios.c_str());
|
||||
|
@ -2288,6 +2292,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
|||
Console.WriteLn("Textures Directory: %s", Textures.c_str());
|
||||
Console.WriteLn("Input Profile Directory: %s", InputProfiles.c_str());
|
||||
Console.WriteLn("Video Dumping Directory: %s", Videos.c_str());
|
||||
Console.WriteLn("Debugger Layouts Directory: %s", DebuggerLayouts.c_str());
|
||||
Console.WriteLn("Debugger Settings Directory: %s", DebuggerSettings.c_str());
|
||||
}
|
||||
|
||||
|
@ -2304,11 +2309,12 @@ bool EmuFolders::EnsureFoldersExist()
|
|||
result = FileSystem::CreateDirectoryPath(Covers.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(GameSettings.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(UserResources.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(DebuggerSettings.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(Cache.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(Textures.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(InputProfiles.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(Videos.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(DebuggerLayouts.c_str(), false) && result;
|
||||
result = FileSystem::CreateDirectoryPath(DebuggerSettings.c_str(), false) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue