mirror of https://github.com/PCSX2/pcsx2.git
926 lines
26 KiB
C++
926 lines
26 KiB
C++
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "DockLayout.h"
|
|
|
|
#include "Debugger/DebuggerView.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 = 2;
|
|
|
|
// Increment this whenever there is a non-breaking change to the JSON format.
|
|
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR = 0;
|
|
|
|
DockLayout::DockLayout(
|
|
QString name,
|
|
BreakPointCpu cpu,
|
|
bool is_default,
|
|
const std::string& base_name,
|
|
DockLayout::Index index)
|
|
: m_name(name)
|
|
, m_cpu(cpu)
|
|
, m_is_default(is_default)
|
|
, m_base_layout(base_name)
|
|
{
|
|
reset();
|
|
save(index);
|
|
}
|
|
|
|
DockLayout::DockLayout(
|
|
QString name,
|
|
BreakPointCpu cpu,
|
|
bool is_default,
|
|
DockLayout::Index index)
|
|
: m_name(name)
|
|
, m_cpu(cpu)
|
|
, m_is_default(is_default)
|
|
{
|
|
save(index);
|
|
}
|
|
|
|
DockLayout::DockLayout(
|
|
QString 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_next_id(layout_to_clone.m_next_id)
|
|
, 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)
|
|
{
|
|
auto widget_description = DockTables::DEBUGGER_VIEWS.find(widget_to_clone->metaObject()->className());
|
|
if (widget_description == DockTables::DEBUGGER_VIEWS.end())
|
|
continue;
|
|
|
|
DebuggerViewParameters parameters;
|
|
parameters.unique_name = unique_name;
|
|
parameters.id = widget_to_clone->id();
|
|
parameters.cpu = &DebugInterface::get(cpu);
|
|
parameters.cpu_override = widget_to_clone->cpuOverride();
|
|
|
|
DebuggerView* new_widget = widget_description->second.create_widget(parameters);
|
|
new_widget->setCustomDisplayName(widget_to_clone->customDisplayName());
|
|
new_widget->setPrimary(widget_to_clone->isPrimary());
|
|
m_widgets.emplace(unique_name, new_widget);
|
|
}
|
|
|
|
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 QString& DockLayout::name() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
void DockLayout::setName(QString 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)))
|
|
recreateDebuggerView(unique_name);
|
|
}
|
|
}
|
|
|
|
void DockLayout::freeze()
|
|
{
|
|
pxAssert(m_is_active);
|
|
m_is_active = false;
|
|
|
|
if (g_debugger_window)
|
|
m_toolbars = g_debugger_window->saveState();
|
|
|
|
// Store the geometry of all the dock widgets as JSON.
|
|
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
|
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()
|
|
{
|
|
pxAssert(!m_is_active);
|
|
m_is_active = true;
|
|
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
// 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();
|
|
}
|
|
else
|
|
{
|
|
// Create all the dock widgets.
|
|
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
|
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();
|
|
}
|
|
}
|
|
|
|
// Check that all the dock widgets have been restored correctly.
|
|
std::vector<QString> orphaned_debugger_views;
|
|
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_views.emplace_back(unique_name);
|
|
}
|
|
}
|
|
|
|
// Delete any debugger views that haven't been restored correctly.
|
|
for (const QString& unique_name : orphaned_debugger_views)
|
|
{
|
|
auto widget_iterator = m_widgets.find(unique_name);
|
|
pxAssert(widget_iterator != m_widgets.end());
|
|
|
|
setPrimaryDebuggerView(widget_iterator->second.get(), false);
|
|
delete widget_iterator->second.get();
|
|
m_widgets.erase(widget_iterator);
|
|
}
|
|
|
|
updateDockWidgetTitles();
|
|
}
|
|
|
|
bool DockLayout::canReset()
|
|
{
|
|
return DockTables::defaultLayout(m_base_layout) != nullptr;
|
|
}
|
|
|
|
void DockLayout::reset()
|
|
{
|
|
pxAssert(!m_is_active);
|
|
|
|
for (auto& [unique_name, widget] : m_widgets)
|
|
{
|
|
pxAssert(widget.get());
|
|
|
|
delete widget;
|
|
}
|
|
|
|
m_next_id = 0;
|
|
m_toolbars.clear();
|
|
m_widgets.clear();
|
|
m_geometry.clear();
|
|
|
|
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
|
|
if (!base_layout)
|
|
return;
|
|
|
|
for (size_t i = 0; i < base_layout->widgets.size(); i++)
|
|
{
|
|
auto iterator = DockTables::DEBUGGER_VIEWS.find(base_layout->widgets[i].type);
|
|
pxAssertRel(iterator != DockTables::DEBUGGER_VIEWS.end(), "Invalid default layout.");
|
|
const DockTables::DebuggerViewDescription& dock_description = iterator->second;
|
|
|
|
DebuggerViewParameters parameters;
|
|
std::tie(parameters.unique_name, parameters.id) =
|
|
generateNewUniqueName(base_layout->widgets[i].type.c_str());
|
|
parameters.cpu = &DebugInterface::get(m_cpu);
|
|
|
|
if (parameters.unique_name.isEmpty())
|
|
continue;
|
|
|
|
DebuggerView* widget = dock_description.create_widget(parameters);
|
|
widget->setPrimary(true);
|
|
m_widgets.emplace(parameters.unique_name, widget);
|
|
}
|
|
}
|
|
|
|
KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& name)
|
|
{
|
|
pxAssert(m_is_active);
|
|
pxAssert(KDDockWidgets::LayoutSaver::restoreInProgress());
|
|
|
|
auto widget_iterator = m_widgets.find(name);
|
|
if (widget_iterator == m_widgets.end())
|
|
return nullptr;
|
|
|
|
DebuggerView* widget = widget_iterator->second;
|
|
if (!widget)
|
|
return nullptr;
|
|
|
|
pxAssert(widget->uniqueName() == name);
|
|
|
|
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
|
KDDockWidgets::Config::self().viewFactory()->createDockWidget(name));
|
|
view->setWidget(widget);
|
|
|
|
return view->asController<KDDockWidgets::Core::DockWidget>();
|
|
}
|
|
|
|
void DockLayout::updateDockWidgetTitles()
|
|
{
|
|
if (!m_is_active)
|
|
return;
|
|
|
|
// Translate default debugger view names.
|
|
for (auto& [unique_name, widget] : m_widgets)
|
|
widget->retranslateDisplayName();
|
|
|
|
// Determine if any widgets have duplicate display names.
|
|
std::map<QString, std::vector<DebuggerView*>> display_name_to_widgets;
|
|
for (auto& [unique_name, widget] : m_widgets)
|
|
display_name_to_widgets[widget->displayNameWithoutSuffix()].emplace_back(widget.get());
|
|
|
|
for (auto& [display_name, widgets] : display_name_to_widgets)
|
|
{
|
|
std::sort(widgets.begin(), widgets.end(),
|
|
[&](const DebuggerView* lhs, const DebuggerView* rhs) {
|
|
return lhs->id() < rhs->id();
|
|
});
|
|
|
|
for (size_t i = 0; i < widgets.size(); i++)
|
|
{
|
|
std::optional<int> suffix_number;
|
|
if (widgets.size() != 1)
|
|
suffix_number = static_cast<int>(i + 1);
|
|
|
|
widgets[i]->setDisplayNameSuffixNumber(suffix_number);
|
|
}
|
|
}
|
|
|
|
// Propagate the new names from the debugger views to the dock widgets.
|
|
for (auto& [unique_name, widget] : m_widgets)
|
|
{
|
|
auto [controller, view] = DockUtils::dockWidgetFromName(widget->uniqueName());
|
|
if (!controller)
|
|
continue;
|
|
|
|
controller->setTitle(widget->displayName());
|
|
}
|
|
}
|
|
|
|
const std::map<QString, QPointer<DebuggerView>>& DockLayout::debuggerViews()
|
|
{
|
|
return m_widgets;
|
|
}
|
|
|
|
bool DockLayout::hasDebuggerView(const QString& unique_name)
|
|
{
|
|
return m_widgets.find(unique_name) != m_widgets.end();
|
|
}
|
|
|
|
size_t DockLayout::countDebuggerViewsOfType(const char* type)
|
|
{
|
|
size_t count = 0;
|
|
for (const auto& [unique_name, widget] : m_widgets)
|
|
{
|
|
if (strcmp(widget->metaObject()->className(), type) == 0)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void DockLayout::createDebuggerView(const std::string& type)
|
|
{
|
|
pxAssert(m_is_active);
|
|
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
auto description_iterator = DockTables::DEBUGGER_VIEWS.find(type);
|
|
pxAssert(description_iterator != DockTables::DEBUGGER_VIEWS.end());
|
|
|
|
const DockTables::DebuggerViewDescription& description = description_iterator->second;
|
|
|
|
DebuggerViewParameters parameters;
|
|
std::tie(parameters.unique_name, parameters.id) = generateNewUniqueName(type.c_str());
|
|
parameters.cpu = &DebugInterface::get(m_cpu);
|
|
|
|
if (parameters.unique_name.isEmpty())
|
|
return;
|
|
|
|
DebuggerView* widget = description.create_widget(parameters);
|
|
m_widgets.emplace(parameters.unique_name, widget);
|
|
|
|
setPrimaryDebuggerView(widget, countDebuggerViewsOfType(type.c_str()) == 0);
|
|
|
|
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
|
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
|
|
view->setWidget(widget);
|
|
|
|
KDDockWidgets::Core::DockWidget* controller = view->asController<KDDockWidgets::Core::DockWidget>();
|
|
pxAssert(controller);
|
|
|
|
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window);
|
|
updateDockWidgetTitles();
|
|
}
|
|
|
|
void DockLayout::recreateDebuggerView(const QString& unique_name)
|
|
{
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
auto debugger_view_iterator = m_widgets.find(unique_name);
|
|
pxAssert(debugger_view_iterator != m_widgets.end());
|
|
|
|
DebuggerView* old_debugger_view = debugger_view_iterator->second;
|
|
|
|
auto description_iterator = DockTables::DEBUGGER_VIEWS.find(old_debugger_view->metaObject()->className());
|
|
pxAssert(description_iterator != DockTables::DEBUGGER_VIEWS.end());
|
|
|
|
const DockTables::DebuggerViewDescription& description = description_iterator->second;
|
|
|
|
DebuggerViewParameters parameters;
|
|
parameters.unique_name = old_debugger_view->uniqueName();
|
|
parameters.id = old_debugger_view->id();
|
|
parameters.cpu = &DebugInterface::get(m_cpu);
|
|
parameters.cpu_override = old_debugger_view->cpuOverride();
|
|
|
|
DebuggerView* new_debugger_view = description.create_widget(parameters);
|
|
new_debugger_view->setCustomDisplayName(old_debugger_view->customDisplayName());
|
|
new_debugger_view->setPrimary(old_debugger_view->isPrimary());
|
|
debugger_view_iterator->second = new_debugger_view;
|
|
|
|
if (m_is_active)
|
|
{
|
|
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
|
if (view)
|
|
view->setWidget(new_debugger_view);
|
|
}
|
|
|
|
delete old_debugger_view;
|
|
}
|
|
|
|
void DockLayout::destroyDebuggerView(const QString& unique_name)
|
|
{
|
|
pxAssert(m_is_active);
|
|
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
auto debugger_view_iterator = m_widgets.find(unique_name);
|
|
if (debugger_view_iterator == m_widgets.end())
|
|
return;
|
|
|
|
setPrimaryDebuggerView(debugger_view_iterator->second.get(), false);
|
|
delete debugger_view_iterator->second.get();
|
|
m_widgets.erase(debugger_view_iterator);
|
|
|
|
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
|
if (!controller)
|
|
return;
|
|
|
|
controller->deleteLater();
|
|
|
|
updateDockWidgetTitles();
|
|
}
|
|
|
|
void DockLayout::setPrimaryDebuggerView(DebuggerView* widget, bool is_primary)
|
|
{
|
|
bool present = false;
|
|
for (auto& [unique_name, test_widget] : m_widgets)
|
|
{
|
|
if (test_widget.get() == widget)
|
|
{
|
|
present = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!present)
|
|
return;
|
|
|
|
if (is_primary)
|
|
{
|
|
// Set the passed widget as the primary widget.
|
|
for (auto& [unique_name, test_widget] : m_widgets)
|
|
{
|
|
if (strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
|
|
{
|
|
test_widget->setPrimary(test_widget.get() == widget);
|
|
}
|
|
}
|
|
}
|
|
else if (widget->isPrimary())
|
|
{
|
|
// Set an arbitrary widget as the primary widget.
|
|
bool next = true;
|
|
for (auto& [unique_name, test_widget] : m_widgets)
|
|
{
|
|
if (test_widget != widget &&
|
|
strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
|
|
{
|
|
test_widget->setPrimary(next);
|
|
next = false;
|
|
}
|
|
}
|
|
|
|
// If we haven't set another widget as the primary one we can't make
|
|
// this one not the primary one.
|
|
if (!next)
|
|
widget->setPrimary(false);
|
|
}
|
|
}
|
|
|
|
void DockLayout::deleteFile()
|
|
{
|
|
if (m_layout_file_path.empty())
|
|
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 (!g_debugger_window)
|
|
return false;
|
|
|
|
if (m_is_active)
|
|
{
|
|
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();
|
|
}
|
|
|
|
// Serialize the layout as JSON.
|
|
rapidjson::Document json(rapidjson::kObjectType);
|
|
rapidjson::Document geometry;
|
|
|
|
const char* cpu_name = DebugInterface::cpuName(m_cpu);
|
|
u32 default_layout_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("versionMajor", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator());
|
|
json.AddMember("versionMinor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator());
|
|
json.AddMember("defaultLayoutHash", default_layout_hash, json.GetAllocator());
|
|
|
|
std::string name_str = m_name.toStdString();
|
|
json.AddMember("name", rapidjson::Value().SetString(name_str.c_str(), name_str.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());
|
|
json.AddMember("nextId", m_next_id, 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());
|
|
}
|
|
|
|
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 dock_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());
|
|
object.AddMember("id", widget->id(), 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());
|
|
|
|
if (widget->cpuOverride().has_value())
|
|
{
|
|
const char* cpu_name = DebugInterface::cpuName(*widget->cpuOverride());
|
|
|
|
rapidjson::Value target;
|
|
target.SetString(cpu_name, strlen(cpu_name));
|
|
object.AddMember("target", target, json.GetAllocator());
|
|
}
|
|
|
|
JsonValueWrapper wrapper(object, json.GetAllocator());
|
|
widget->toJson(wrapper);
|
|
|
|
dock_widgets.PushBack(object, json.GetAllocator());
|
|
}
|
|
json.AddMember("dockWidgets", dock_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.toStdString());
|
|
|
|
// 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_active);
|
|
|
|
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("versionMajor");
|
|
if (version_major == json.MemberEnd() || !version_major->value.IsInt())
|
|
{
|
|
Console.Error("Debugger: Layout file '%s' has missing or invalid 'versionMajor' property.", path.c_str());
|
|
result = MAJOR_VERSION_MISMATCH;
|
|
return;
|
|
}
|
|
|
|
if (version_major->value.GetInt() != DEBUGGER_LAYOUT_FILE_VERSION_MAJOR)
|
|
{
|
|
result = MAJOR_VERSION_MISMATCH;
|
|
return;
|
|
}
|
|
|
|
auto version_minor = json.FindMember("versionMinor");
|
|
if (version_minor == json.MemberEnd() || !version_minor->value.IsInt())
|
|
{
|
|
Console.Error("Debugger: Layout file '%s' has missing or invalid 'versionMinor' property.", path.c_str());
|
|
result = MAJOR_VERSION_MISMATCH;
|
|
return;
|
|
}
|
|
|
|
auto default_layout_hash = json.FindMember("defaultLayoutHash");
|
|
if (default_layout_hash == json.MemberEnd() || !default_layout_hash->value.IsUint())
|
|
{
|
|
Console.Error("Debugger: Layout file '%s' has missing or invalid 'defaultLayoutHash' property.", path.c_str());
|
|
result = MAJOR_VERSION_MISMATCH;
|
|
return;
|
|
}
|
|
|
|
if (default_layout_hash->value.GetUint() != DockTables::hashDefaultLayouts())
|
|
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");
|
|
|
|
m_name.truncate(DockUtils::MAX_LAYOUT_NAME_SIZE);
|
|
|
|
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 next_id = json.FindMember("nextId");
|
|
if (next_id != json.MemberBegin() && next_id->value.IsUint64())
|
|
m_next_id = next_id->value.GetUint64();
|
|
|
|
auto base_layout = json.FindMember("baseLayout");
|
|
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 dock_widgets = json.FindMember("dockWidgets");
|
|
if (dock_widgets != json.MemberEnd() && dock_widgets->value.IsArray())
|
|
{
|
|
for (rapidjson::Value& object : dock_widgets->value.GetArray())
|
|
{
|
|
auto unique_name = object.FindMember("uniqueName");
|
|
if (unique_name == object.MemberEnd() || !unique_name->value.IsString())
|
|
continue;
|
|
|
|
auto id = object.FindMember("id");
|
|
if (id == object.MemberEnd() || !id->value.IsUint64())
|
|
continue;
|
|
|
|
auto widgets_iterator = m_widgets.find(unique_name->value.GetString());
|
|
if (widgets_iterator != m_widgets.end())
|
|
continue;
|
|
|
|
auto type = object.FindMember("type");
|
|
if (type == object.MemberEnd() || !type->value.IsString())
|
|
continue;
|
|
|
|
auto description = DockTables::DEBUGGER_VIEWS.find(type->value.GetString());
|
|
if (description == DockTables::DEBUGGER_VIEWS.end())
|
|
continue;
|
|
|
|
std::optional<BreakPointCpu> cpu_override;
|
|
|
|
auto target = object.FindMember("target");
|
|
if (target != object.MemberEnd() && target->value.IsString())
|
|
{
|
|
for (BreakPointCpu cpu : DEBUG_CPUS)
|
|
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
|
|
cpu_override = cpu;
|
|
}
|
|
|
|
DebuggerViewParameters parameters;
|
|
parameters.unique_name = unique_name->value.GetString();
|
|
parameters.id = id->value.GetUint64();
|
|
parameters.cpu = &DebugInterface::get(m_cpu);
|
|
parameters.cpu_override = cpu_override;
|
|
|
|
DebuggerView* widget = description->second.create_widget(parameters);
|
|
|
|
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;
|
|
|
|
validatePrimaryDebuggerViews();
|
|
}
|
|
|
|
void DockLayout::validatePrimaryDebuggerViews()
|
|
{
|
|
std::map<std::string, std::vector<DebuggerView*>> type_to_widgets;
|
|
for (const auto& [unique_name, widget] : m_widgets)
|
|
type_to_widgets[widget->metaObject()->className()].emplace_back(widget.get());
|
|
|
|
for (auto& [type, widgets] : type_to_widgets)
|
|
{
|
|
u32 primary_widgets = 0;
|
|
|
|
// Make sure at most one widget is marked as primary.
|
|
for (DebuggerView* widget : widgets)
|
|
{
|
|
if (widget->isPrimary())
|
|
{
|
|
if (primary_widgets != 0)
|
|
widget->setPrimary(false);
|
|
|
|
primary_widgets++;
|
|
}
|
|
}
|
|
|
|
// If none of the widgets were marked as primary, just set the first one
|
|
// as the primary one.
|
|
if (primary_widgets == 0)
|
|
widgets[0]->setPrimary(true);
|
|
}
|
|
}
|
|
|
|
void DockLayout::setupDefaultLayout()
|
|
{
|
|
pxAssert(m_is_active);
|
|
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
|
|
if (!base_layout)
|
|
return;
|
|
|
|
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(base_layout->groups.size(), nullptr);
|
|
|
|
for (const DockTables::DefaultDockWidgetDescription& dock_description : base_layout->widgets)
|
|
{
|
|
const DockTables::DefaultDockGroupDescription& group = base_layout->groups.at(dock_description.group);
|
|
|
|
DebuggerView* widget = nullptr;
|
|
for (auto& [unique_name, test_widget] : m_widgets)
|
|
if (test_widget->metaObject()->className() == dock_description.type)
|
|
widget = test_widget;
|
|
|
|
if (!widget)
|
|
continue;
|
|
|
|
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
|
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
|
|
view->setWidget(widget);
|
|
|
|
if (!groups.at(dock_description.group))
|
|
{
|
|
KDDockWidgets::QtWidgets::DockWidget* parent = nullptr;
|
|
if (group.parent >= 0 && group.parent < static_cast<s32>(groups.size()))
|
|
parent = groups.at(group.parent);
|
|
|
|
g_debugger_window->addDockWidget(view, group.location, parent);
|
|
|
|
groups.at(dock_description.group) = view;
|
|
}
|
|
else
|
|
{
|
|
groups.at(dock_description.group)->addDockWidgetAsTab(view);
|
|
}
|
|
}
|
|
|
|
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
|
group->setCurrentTabIndex(0);
|
|
}
|
|
|
|
std::pair<QString, u64> DockLayout::generateNewUniqueName(const char* type)
|
|
{
|
|
QString name;
|
|
u64 id;
|
|
|
|
do
|
|
{
|
|
if (m_next_id == INT_MAX)
|
|
return {QString(), 0};
|
|
|
|
id = m_next_id;
|
|
name = QStringLiteral("%1-%2").arg(type).arg(static_cast<qulonglong>(m_next_id));
|
|
m_next_id++;
|
|
} while (hasDebuggerView(name));
|
|
|
|
return {name, id};
|
|
}
|