pcsx2/pcsx2-qt/Debugger/Docking/DockLayout.cpp

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};
}