Debugger: Save and restore the state of the toolbars for each layout

This commit is contained in:
chaoticgd 2025-02-17 06:17:30 +00:00 committed by Ty
parent 59210dffa9
commit eb83cb70ea
9 changed files with 182 additions and 48 deletions

View File

@ -25,6 +25,8 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
g_debugger_window = this;
setupDefaultToolBarState();
m_dock_manager->loadLayouts();
connect(m_ui.actionRun, &QAction::triggered, this, &DebuggerWindow::onRunPause);
@ -34,6 +36,10 @@ 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.menuTools, &QMenu::aboutToShow, this, [this]() {
m_dock_manager->createToolsMenu(m_ui.menuTools);
});
connect(m_ui.menuWindows, &QMenu::aboutToShow, this, [this]() {
m_dock_manager->createWindowsMenu(m_ui.menuWindows);
});
@ -100,6 +106,11 @@ DockManager& DebuggerWindow::dockManager()
return *m_dock_manager;
}
void DebuggerWindow::clearToolBarState()
{
restoreState(m_default_toolbar_state);
}
// 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
void DebuggerWindow::setTabActiveStyle(BreakPointCpu enabledCpu)
@ -198,3 +209,18 @@ void DebuggerWindow::closeEvent(QCloseEvent* event)
g_debugger_window = nullptr;
deleteLater();
}
void DebuggerWindow::setupDefaultToolBarState()
{
// Hiding all the toolbars lets us save the default state of the window with
// all the toolbars hidden. The DockManager will show the appropriate ones
// later anyway.
for (QToolBar* toolbar : findChildren<QToolBar*>())
toolbar->hide();
m_default_toolbar_state = saveState();
for (QToolBar* toolbar : findChildren<QToolBar*>())
connect(toolbar, &QToolBar::topLevelChanged, m_dock_manager, &DockManager::updateToolBarLockState);
}

View File

@ -24,6 +24,8 @@ public:
DockManager& dockManager();
void clearToolBarState();
public slots:
void onVMStateChanged();
void onRunPause();
@ -36,6 +38,8 @@ protected:
void closeEvent(QCloseEvent* event);
private:
void setupDefaultToolBarState();
Ui::DebuggerWindow m_ui;
QAction* m_actionRunPause;
QAction* m_actionStepInto;
@ -44,6 +48,8 @@ private:
DockManager* m_dock_manager;
QByteArray m_default_toolbar_state;
void setTabActiveStyle(BreakPointCpu toggledCPU);
};

View File

@ -18,10 +18,13 @@
<normalon>:/icons/AppIcon64.png</normalon>
</iconset>
</property>
<widget class="QToolBar" name="debugToolBar">
<widget class="QToolBar" name="toolBarDebug">
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Debug</string>
</property>
<property name="movable">
<bool>true</bool>
</property>
@ -90,15 +93,21 @@
<addaction name="actionResetAllLayouts"/>
<addaction name="actionResetDefaultLayouts"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
<addaction name="menuDebug"/>
<addaction name="menuTools"/>
<addaction name="menuWindows"/>
<addaction name="menuLayouts"/>
</widget>
<widget class="QToolBar" name="viewToolBar">
<widget class="QToolBar" name="toolBarView">
<property name="windowTitle">
<string>toolBar</string>
<string>View</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>

View File

@ -49,12 +49,12 @@ DockLayout::DockLayout(
for (size_t i = 0; i < default_layout.widgets.size(); i++)
{
auto iterator = DockTables::DEBUGGER_WIDGETS.find(default_layout.widgets[i].type);
auto iterator = DockTables::DEBUGGER_WIDGETS.find(QString::fromStdString(default_layout.widgets[i].type));
pxAssertRel(iterator != DockTables::DEBUGGER_WIDGETS.end(), "Invalid default layout.");
const DockTables::DebuggerWidgetDescription& dock_description = iterator->second;
DebuggerWidget* widget = dock_description.create_widget(debug_interface);
m_widgets.emplace(default_layout.widgets[i].type, widget);
m_widgets.emplace(QString::fromStdString(default_layout.widgets[i].type), widget);
}
save(index);
@ -82,6 +82,7 @@ DockLayout::DockLayout(
, m_cpu(cpu)
, m_is_default(is_default)
, m_base_layout(layout_to_clone.m_base_layout)
, m_toolbars(layout_to_clone.m_toolbars)
, m_geometry(layout_to_clone.m_geometry)
{
for (const auto& [unique_name, widget_to_clone] : layout_to_clone.m_widgets)
@ -154,6 +155,8 @@ void DockLayout::freeze()
pxAssert(!m_is_frozen);
m_is_frozen = true;
m_toolbars = g_debugger_window->saveState();
// Store the geometry of all the dock widgets as JSON.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
m_geometry = saver.serializeLayout();
@ -169,21 +172,36 @@ void DockLayout::freeze()
}
}
void DockLayout::thaw(DebuggerWindow* window)
void DockLayout::thaw()
{
pxAssert(m_is_frozen);
m_is_frozen = false;
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
// Restore the state of the toolbars.
if (m_toolbars.isEmpty())
{
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
if (base_layout)
{
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
if (base_layout->toolbars.contains(toolbar->objectName().toStdString()))
toolbar->show();
}
}
else
{
g_debugger_window->restoreState(m_toolbars);
}
if (m_geometry.isEmpty())
{
// This is a newly created layout with no geometry information.
setupDefaultLayout(window);
setupDefaultLayout();
return;
}
// Restore the geometry of the dock widgets we just recreated.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
if (!saver.restoreLayout(m_geometry))
{
// We've failed to restore the geometry, so just tear down whatever dock
@ -197,7 +215,7 @@ void DockLayout::thaw(DebuggerWindow* window)
delete dock;
}
setupDefaultLayout(window);
setupDefaultLayout();
return;
}
@ -301,7 +319,7 @@ bool DockLayout::hasDebuggerWidget(QString unique_name)
return m_widgets.find(unique_name) != m_widgets.end();
}
void DockLayout::toggleDebuggerWidget(QString unique_name, DebuggerWindow* window)
void DockLayout::toggleDebuggerWidget(QString unique_name)
{
pxAssert(!m_is_frozen);
@ -334,7 +352,7 @@ void DockLayout::toggleDebuggerWidget(QString unique_name, DebuggerWindow* windo
return;
}
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, window);
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window);
retranslateDockWidget(controller);
}
else
@ -386,11 +404,12 @@ void DockLayout::deleteFile()
if (!FileSystem::DeleteFilePath(m_layout_file_path.c_str()))
Console.Error("Debugger: Failed to delete layout file '%s'.", m_layout_file_path.c_str());
}
bool DockLayout::save(DockLayout::Index layout_index)
{
if (!m_is_frozen)
{
m_toolbars = g_debugger_window->saveState();
// Store the geometry of all the dock widgets as JSON.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
m_geometry = saver.serializeLayout();
@ -425,6 +444,14 @@ bool DockLayout::save(DockLayout::Index layout_index)
json.AddMember("baseLayout", base_layout, json.GetAllocator());
}
if (!m_toolbars.isEmpty())
{
std::string toolbars_str = m_toolbars.toBase64().toStdString();
rapidjson::Value toolbars;
toolbars.SetString(toolbars_str.data(), toolbars_str.size(), json.GetAllocator());
json.AddMember("toolbars", toolbars, json.GetAllocator());
}
rapidjson::Value widgets(rapidjson::kArrayType);
for (auto& [unique_name, widget] : m_widgets)
{
@ -584,6 +611,10 @@ void DockLayout::load(
if (base_layout != json.MemberEnd() && base_layout->value.IsString())
m_base_layout = base_layout->value.GetString();
auto toolbars = json.FindMember("toolbars");
if (toolbars != json.MemberEnd() && toolbars->value.IsString())
m_toolbars = QByteArray::fromBase64(toolbars->value.GetString());
auto widgets = json.FindMember("widgets");
if (widgets != json.MemberEnd() && widgets->value.IsArray())
{
@ -627,28 +658,25 @@ void DockLayout::load(
m_layout_file_path = path;
}
void DockLayout::setupDefaultLayout(DebuggerWindow* window)
void DockLayout::setupDefaultLayout()
{
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)
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
if (!base_layout)
return;
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(layout->groups.size(), nullptr);
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(base_layout->groups.size(), nullptr);
for (const DockTables::DefaultDockWidgetDescription& dock_description : layout->widgets)
for (const DockTables::DefaultDockWidgetDescription& dock_description : base_layout->widgets)
{
const DockTables::DefaultDockGroupDescription& group = layout->groups[static_cast<u32>(dock_description.group)];
const DockTables::DefaultDockGroupDescription& group =
base_layout->groups[static_cast<u32>(dock_description.group)];
auto widget_iterator = m_widgets.find(dock_description.type);
auto widget_iterator = m_widgets.find(QString::fromStdString(dock_description.type));
if (widget_iterator == m_widgets.end())
continue;
@ -665,7 +693,7 @@ void DockLayout::setupDefaultLayout(DebuggerWindow* window)
if (group.parent != DockTables::DefaultDockGroup::ROOT)
parent = groups[static_cast<u32>(group.parent)];
window->addDockWidget(view, group.location, parent);
g_debugger_window->addDockWidget(view, group.location, parent);
groups[static_cast<u32>(dock_description.group)] = view;
}

View File

@ -89,7 +89,7 @@ public:
void freeze();
// Restore the state of all the dock widgets from this layout.
void thaw(DebuggerWindow* window);
void thaw();
KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name);
void retranslateDockWidgets();
@ -97,7 +97,7 @@ public:
void dockWidgetClosed(KDDockWidgets::Core::DockWidget* dock_widget);
bool hasDebuggerWidget(QString unique_name);
void toggleDebuggerWidget(QString unique_name, DebuggerWindow* window);
void toggleDebuggerWidget(QString unique_name);
void recreateDebuggerWidget(QString unique_name);
void deleteFile();
@ -110,7 +110,7 @@ private:
DockLayout::LoadResult& result,
DockLayout::Index& index_last_session);
void setupDefaultLayout(DebuggerWindow* window);
void setupDefaultLayout();
// The name displayed in the user interface. Also used to determine the
// file name for the layout file.
@ -127,6 +127,10 @@ private:
// be used if the m_geometry variable above is empty.
std::string m_base_layout;
// The state of all the toolbars, saved and restored using
// QMainWindow::saveState and QMainWindow::restoreState respectively.
QByteArray m_toolbars;
// 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.

View File

@ -104,12 +104,17 @@ void DockManager::switchToLayout(DockLayout::Index layout_index)
layout.save(m_current_layout);
}
// Clear out the existing positions of toolbars so they don't affect where
// new toolbars appear for other layouts.
g_debugger_window->clearToolBarState();
updateToolBarLockState();
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);
layout.thaw();
}
}
@ -265,6 +270,26 @@ void DockManager::resetDefaultLayouts()
saveLayouts();
}
void DockManager::createToolsMenu(QMenu* menu)
{
menu->clear();
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
{
QAction* action = new QAction(menu);
action->setText(widget->windowTitle());
action->setCheckable(true);
action->setChecked(widget->isVisible());
connect(action, &QAction::triggered, this, [widget]() {
widget->setVisible(!widget->isVisible());
});
menu->addAction(action);
}
}
void DockManager::createWindowsMenu(QMenu* menu)
{
menu->clear();
@ -281,7 +306,7 @@ void DockManager::createWindowsMenu(QMenu* menu)
action->setCheckable(true);
action->setChecked(layout.hasDebuggerWidget(type));
connect(action, &QAction::triggered, this, [&layout, type]() {
layout.toggleDebuggerWidget(type, g_debugger_window);
layout.toggleDebuggerWidget(type);
});
menu->addAction(action);
}
@ -557,6 +582,8 @@ void DockManager::setLayoutLocked(bool locked)
{
m_layout_locked = locked;
updateToolBarLockState();
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
{
auto stack = static_cast<KDDockWidgets::QtWidgets::Stack*>(group->stack()->view());
@ -568,6 +595,12 @@ void DockManager::setLayoutLocked(bool locked)
}
}
void DockManager::updateToolBarLockState()
{
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
toolbar->setMovable(!m_layout_locked || toolbar->isFloating());
}
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
{
if (!g_debugger_window)

View File

@ -59,6 +59,7 @@ public:
void resetAllLayouts();
void resetDefaultLayouts();
void createToolsMenu(QMenu* menu);
void createWindowsMenu(QMenu* menu);
QWidget* createLayoutSwitcher(QWidget* menu_bar);
@ -76,6 +77,7 @@ public:
bool isLayoutLocked();
void setLayoutLocked(bool locked);
void updateToolBarLockState();
private:
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);

View File

@ -48,14 +48,14 @@ const std::map<QString, DockTables::DebuggerWidgetDescription> DockTables::DEBUG
const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUTS = {
{
QT_TRANSLATE_NOOP("DebuggerLayout", "R5900"),
BREAKPOINT_EE,
{
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R5900"),
.cpu = BREAKPOINT_EE,
.groups = {
/* [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},
},
{
.widgets = {
/* DefaultDockGroup::TOP_RIGHT */
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
/* DefaultDockGroup::BOTTOM */
@ -72,16 +72,19 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarDebug",
},
},
{
QT_TRANSLATE_NOOP("DebuggerLayout", "R3000"),
BREAKPOINT_IOP,
{
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R3000"),
.cpu = BREAKPOINT_IOP,
.groups = {
/* [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},
},
{
.widgets = {
/* DefaultDockGroup::TOP_RIGHT */
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
/* DefaultDockGroup::BOTTOM */
@ -98,9 +101,21 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarDebug",
},
},
};
const DockTables::DefaultDockLayout* DockTables::defaultLayout(const std::string& name)
{
for (const DockTables::DefaultDockLayout& default_layout : DockTables::DEFAULT_DOCK_LAYOUTS)
if (default_layout.name == name)
return &default_layout;
return nullptr;
}
const std::string& DockTables::hashDefaultLayouts()
{
static std::string hash;
@ -112,7 +127,7 @@ const std::string& DockTables::hashDefaultLayouts()
u32 hash_version = 1;
md5.Update(&hash_version, sizeof(hash_version));
size_t layout_count = DEFAULT_DOCK_LAYOUTS.size();
u32 layout_count = static_cast<u32>(DEFAULT_DOCK_LAYOUTS.size());
md5.Update(&layout_count, sizeof(layout_count));
for (const DefaultDockLayout& layout : DEFAULT_DOCK_LAYOUTS)
@ -130,32 +145,41 @@ const std::string& DockTables::hashDefaultLayouts()
void DockTables::hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5)
{
size_t layout_name_size = layout.name.size();
u32 layout_name_size = static_cast<u32>(layout.name.size());
md5.Update(&layout_name_size, sizeof(layout_name_size));
md5.Update(layout.name.c_str(), layout.name.size());
md5.Update(layout.name.data(), layout_name_size);
const char* cpu_name = DebugInterface::cpuName(layout.cpu);
size_t cpu_name_size = strlen(cpu_name);
u32 cpu_name_size = static_cast<u32>(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();
u32 group_count = static_cast<u32>(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();
u32 widget_count = static_cast<u32>(layout.widgets.size());
md5.Update(&widget_count, sizeof(widget_count));
for (const DefaultDockWidgetDescription& widget : layout.widgets)
hashDefaultDockWidget(widget, md5);
u32 toolbar_count = static_cast<u32>(layout.toolbars.size());
md5.Update(&toolbar_count, sizeof(toolbar_count));
for (const std::string& toolbar : layout.toolbars)
{
u32 toolbar_size = toolbar.size();
md5.Update(&toolbar_size, sizeof(toolbar_size));
md5.Update(toolbar.data(), toolbar.size());
}
}
void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5)
{
const char* location = DockUtils::locationToString(group.location);
size_t location_size = strlen(location);
u32 location_size = static_cast<u32>(strlen(location));
md5.Update(&location_size, sizeof(location_size));
md5.Update(location, location_size);
@ -165,10 +189,9 @@ void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5D
void DockTables::hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5)
{
std::string type = widget.type.toStdString();
size_t type_size = type.size();
u32 type_size = static_cast<u32>(widget.type.size());
md5.Update(&type_size, sizeof(type_size));
md5.Update(type.c_str(), type_size);
md5.Update(widget.type.data(), type_size);
u32 group = static_cast<u32>(widget.group);
md5.Update(&group, sizeof(group));

View File

@ -48,7 +48,7 @@ namespace DockTables
struct DefaultDockWidgetDescription
{
QString type;
std::string type;
DefaultDockGroup group;
};
@ -58,10 +58,13 @@ namespace DockTables
BreakPointCpu cpu;
std::vector<DefaultDockGroupDescription> groups;
std::vector<DefaultDockWidgetDescription> widgets;
std::set<std::string> toolbars;
};
extern const std::vector<DefaultDockLayout> DEFAULT_DOCK_LAYOUTS;
const DefaultDockLayout* defaultLayout(const std::string& name);
// This is used to determine if the user has updated and we need to recreate
// the default layouts.
const std::string& hashDefaultLayouts();