// 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 #include #include #include #include #include #include #include #include #include #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(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()) 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(dock->view()); view->setWidget(new QWidget()); delete dock; } setupDefaultLayout(); } } // Check that all the dock widgets have been restored correctly. std::vector 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::Config::self().viewFactory()->createDockWidget(name)); view->setWidget(widget); return view->asController(); } 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> 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 suffix_number; if (widgets.size() != 1) suffix_number = static_cast(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>& 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::Config::self().viewFactory()->createDockWidget(widget->uniqueName())); view->setWidget(widget); KDDockWidgets::Core::DockWidget* controller = view->asController(); 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(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 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 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 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 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> 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 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::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(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 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(m_next_id)); m_next_id++; } while (hasDebuggerView(name)); return {name, id}; }