Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,11 @@ if(KDDockWidgets_EXAMPLES)
add_subdirectory(examples/minimal)
add_subdirectory(examples/mdi)
add_subdirectory(examples/mdi_with_docking)
add_subdirectory(examples/misc)
set_compiler_flags(qtwidgets_dockwidgets)
set_compiler_flags(qtwidgets_minimal)
set_compiler_flags(qtwidgets_mdi_with_docking)
set_compiler_flags(qtwidgets_documents)

# Standalone layouting example
add_subdirectory(src/core/layouting/examples/qtwidgets/)
Expand Down
12 changes: 9 additions & 3 deletions code.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"args": [
"tst_affinityWithPersistentCentralGroup"
],
"cwd": "${command:cmake.buildDirectory}",
"env": {}
},
Expand All @@ -74,7 +76,9 @@
"type": "cppdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"args": [
"tst_affinityWithPersistentCentralGroup"
],
"cwd": "${command:cmake.buildDirectory}",
"MIMode": "gdb",
"setupCommands": [
Expand Down Expand Up @@ -104,7 +108,9 @@
"type": "cppvsdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"args": [
"tst_affinityWithPersistentCentralGroup"
],
"cwd": "${command:cmake.buildDirectory}",
"sourceFileMap": {
"C:/work/build/qt5_workdir/w/s": "${QTDIR}/../Src",
Expand Down
11 changes: 11 additions & 0 deletions examples/misc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file is part of KDDockWidgets.
#
# SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
# Author: Sergio Martins <sergio.martins@kdab.com>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#
# Contact KDAB at <info@kdab.com> for commercial licensing options.
#

add_subdirectory(documents)
35 changes: 35 additions & 0 deletions examples/misc/documents/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This file is part of KDDockWidgets.
#
# SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
# Author: Sergio Martins <sergio.martins@kdab.com>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#
# Contact KDAB at <info@kdab.com> for commercial licensing options.
#

cmake_minimum_required(VERSION 3.15)
project(qtwidgets_documents)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIRS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT TARGET kddockwidgets)
# For the purpose of our example, we're looking for Qt5 or Qt6 KDDW.
# For your own purposes, just chose the one you need.
find_package(KDDockWidgets QUIET)
if(NOT KDDockWidgets_FOUND)
find_package(KDDockWidgets-qt6 REQUIRED)
endif()
endif()

set(RESOURCES_EXAMPLE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../../dockwidgets/resources_example.qrc)

add_executable(qtwidgets_documents main.cpp ../../dockwidgets/MyWidget.cpp ${RESOURCES_EXAMPLE_SRC})

target_include_directories(qtwidgets_documents PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../dockwidgets)

target_link_libraries(qtwidgets_documents PRIVATE KDAB::kddockwidgets)
123 changes: 123 additions & 0 deletions examples/misc/documents/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
This file is part of KDDockWidgets.

SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>

SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only

Contact KDAB at <info@kdab.com> for commercial licensing options.
*/

#include "MyWidget.h"

#include <kddockwidgets/MainWindow.h>
#include <kddockwidgets/DockWidget.h>
#include <kddockwidgets/LayoutSaver.h>
#include <kddockwidgets/core/MainWindow.h>
#include <kddockwidgets/core/DockRegistry.h>

#include <QStyleFactory>
#include <QApplication>
#include <QMenuBar>

// clazy:excludeall=qstring-allocations

using namespace KDDockWidgets;

int main(int argc, char **argv)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
QApplication app(argc, argv);
QCoreApplication::setOrganizationName(QStringLiteral("KDAB"));
QCoreApplication::setApplicationName(QStringLiteral("Documents Example"));

KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);

qApp->setStyle(QStyleFactory::create(QStringLiteral("Fusion")));

// 1. Create a main window with a persistent central group
KDDockWidgets::QtWidgets::MainWindow mainWindow(QStringLiteral("MyMainWindow"),
MainWindowOption_HasCentralGroup);
mainWindow.setWindowTitle("Documents Example");
mainWindow.resize(1200, 800);
mainWindow.show();

// 2. Set up affinities: the main window supports both scopes
mainWindow.setAffinities({ "global", "projects" });
mainWindow.mainWindow()->setDocumentAffinity("projects");

// 3. Create project dock widgets (tabbed in the central group)
auto projectDock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("project1"));
projectDock1->setWidget(new MyWidget1());
projectDock1->setAffinities({ "projects" });
mainWindow.addDockWidgetAsTab(projectDock1);

auto projectDock2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("project2"));
projectDock2->setWidget(new MyWidget2());
projectDock2->setAffinities({ "projects" });
mainWindow.addDockWidgetAsTab(projectDock2);

// 4. Create global dock widgets (docked to the sides)
auto dockedGlobal = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("dockedGlobal"));
dockedGlobal->setWidget(new MyWidget3());
dockedGlobal->setAffinities({ "global" });
mainWindow.mainWindow()->addDockWidgetToSide(dockedGlobal->asDockWidgetController(), KDDockWidgets::Location_OnLeft);

auto dockedGlobal2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("dockedGlobal2"));
dockedGlobal2->setWidget(new MyWidget1());
dockedGlobal2->setAffinities({ "global" });
mainWindow.mainWindow()->addDockWidgetToSide(dockedGlobal2->asDockWidgetController(), KDDockWidgets::Location_OnRight);

// 5. Create floating dock widgets
auto floatingGlobal = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("floatingGlobal"));
floatingGlobal->setWidget(new MyWidget2());
floatingGlobal->setAffinities({ "global" });
floatingGlobal->open();

auto floatingProjects = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("floatingProjects"));
floatingProjects->setWidget(new MyWidget3());
floatingProjects->setAffinities({ "projects" });
floatingProjects->open();

// 6. Create menu bar with save/load actions for each affinity scope
QMenuBar *menuBar = mainWindow.menuBar();
QMenu *layoutMenu = menuBar->addMenu(QStringLiteral("Layout"));

layoutMenu->addAction(QStringLiteral("Save Project"), [&] {
KDDockWidgets::LayoutSaver saver;
saver.setAffinityNames({ "projects" });
saver.saveToFile(QStringLiteral("projects_layout.json"));
});

layoutMenu->addAction(QStringLiteral("Load Project"), [&] {
KDDockWidgets::LayoutSaver saver;
saver.setAffinityNames({ "projects" });
saver.restoreFromFile(QStringLiteral("projects_layout.json"));
});

layoutMenu->addSeparator();

layoutMenu->addAction(QStringLiteral("Save Global"), [&] {
KDDockWidgets::LayoutSaver saver;
saver.setAffinityNames({ "global" });
saver.saveToFile(QStringLiteral("global_layout.json"));
});

layoutMenu->addAction(QStringLiteral("Load Global"), [&] {
KDDockWidgets::LayoutSaver saver;
saver.setAffinityNames({ "global" });
saver.restoreFromFile(QStringLiteral("global_layout.json"));
});

layoutMenu->addSeparator();

layoutMenu->addAction(QStringLiteral("Close all"), [&] {
KDDockWidgets::DockRegistry::self()->clear();
});

return app.exec();
}
107 changes: 90 additions & 17 deletions src/LayoutSaver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "LayoutSaver.h"
#include "Config.h"
#include "core/DropArea.h"
#include "core/ViewFactory.h"
#include "core/LayoutSaver_p.h"
#include "core/Logging_p.h"
Expand All @@ -39,6 +40,7 @@
#include <iostream>
#include <fstream>
#include <cmath>
#include <qtestsupport_core.h>
#include <utility>

/**
Expand Down Expand Up @@ -441,13 +443,13 @@ QByteArray LayoutSaver::serializeLayout() const
layout.mainWindows.reserve(mainWindows.size());
for (auto mainWindow : mainWindows) {
if (d->matchesAffinity(mainWindow->affinities()))
layout.mainWindows.push_back(mainWindow->serialize());
layout.mainWindows.push_back(mainWindow->serialize(d->m_affinityNames));
}

const auto floatingWindows = Core::floatingWindowsForAffinity(d->m_affinityNames);
layout.floatingWindows.reserve(floatingWindows.size());
for (Core::FloatingWindow *floatingWindow : floatingWindows) {
layout.floatingWindows.push_back(floatingWindow->serialize());
layout.floatingWindows.push_back(floatingWindow->serialize(d->m_affinityNames));
}

// Closed dock widgets also have interesting things to save, like geometry and placeholder info
Expand All @@ -474,6 +476,49 @@ QByteArray LayoutSaver::serializeLayout() const
return layout.toJson();
}

namespace {

bool isDocumentMode(const Core::MainWindow::List &mainWindows,
const QVector<QString> &affinities)
{
if (mainWindows.size() != 1) {
// more than 1 window not supported (yet, until someone needs it ?)
return false;
}

if (affinities.size() != 1) {
// Not supported
return false;
}

auto mainWindow = mainWindows.first();
const QString &documentAffinity = mainWindow->documentAffinity();
if (documentAffinity.isEmpty()) {
return false;
}

if ((mainWindow->options() & MainWindowOption_HasCentralGroup) == 0) {
// we need to be in document mode
return false;
}

return true;
}

bool isRestoringDocuments(const Core::MainWindow::List &mainWindows,
const QVector<QString> &affinities)
{
return isDocumentMode(mainWindows, affinities) && affinities.first() == mainWindows.first()->documentAffinity();
}

// bool isRestoringNonDocuments(const Core::MainWindow::List &mainWindows,
// const QVector<QString> &affinities)
// {
// return isDocumentMode(mainWindows, affinities) && affinities.first() != mainWindows.first()->documentAffinity();
// }

}

bool LayoutSaver::restoreLayout(const QByteArray &data)
{
LayoutSaver::DockWidget::s_dockWidgets.clear();
Expand Down Expand Up @@ -513,18 +558,25 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
layout.scaleSizes(d->m_restoreOptions);

d->floatWidgetsWhichSkipRestore(layout.mainWindowNames());
d->floatUnknownWidgets(layout);

Private::RAIIIsRestoring isRestoring;

// Hide all dockwidgets and unparent them from any layout before starting restore
// We only close the stuff that the loaded JSON knows about. Unknown widgets might be newer.

d->m_dockRegistry->clear(d->m_dockRegistry->dockWidgets(layout.dockWidgetsToClose()),
d->m_dockRegistry->mainWindows(layout.mainWindowNames()),
d->m_affinityNames);
auto dockWidgetsToClose = d->m_dockRegistry->dockWidgets(layout.dockWidgetsToClose());
auto mainWindowsToConsider = d->m_dockRegistry->mainWindows(layout.mainWindowNames());
const bool isRestoringDocuments = ::isRestoringDocuments(mainWindowsToConsider, d->m_affinityNames);

if (!isDocumentMode(mainWindowsToConsider, d->m_affinityNames))
d->floatUnknownWidgets(layout);

d->m_dockRegistry->clear(dockWidgetsToClose,
mainWindowsToConsider,
d->m_affinityNames, isRestoringDocuments);

// 1. Restore main windows

for (const LayoutSaver::MainWindow &mw : std::as_const(layout.mainWindows)) {
auto mainWindow = d->m_dockRegistry->mainWindowByName(mw.uniqueName);
if (!mainWindow) {
Expand All @@ -539,20 +591,32 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
if (!d->matchesAffinity(mainWindow->affinities()))
continue;

if (!(d->m_restoreOptions & InternalRestoreOption::SkipMainWindowGeometry) && !mainWindow->isInDockWidget()) {
Window::Ptr window = mainWindow->view()->window();
if (window->windowState() == WindowState::Maximized) {
// Restoring geometry needs to be done in normal state.
// Qt doesn't support restoring normal geometry on maximized windows.
window->setWindowState(WindowState::None);
if (isRestoringDocuments) {
// In 'document mode' we only restore the central tabs
if (const auto centralGroup = mw.multiSplitterLayout.centralGroup(); !centralGroup.isNull) {
for (const auto &savedDock : centralGroup.dockWidgets) {
Core::DockWidget *dw = d->m_dockRegistry->dockByName(
savedDock->uniqueName, DockRegistry::DockByNameFlag::CreateIfNotFound);
if (dw)
mainWindow->addDockWidgetAsTab(dw);
}
}
} else {
if (!(d->m_restoreOptions & InternalRestoreOption::SkipMainWindowGeometry) && !mainWindow->isInDockWidget()) {
Window::Ptr window = mainWindow->view()->window();
if (window->windowState() == WindowState::Maximized) {
// Restoring geometry needs to be done in normal state.
// Qt doesn't support restoring normal geometry on maximized windows.
window->setWindowState(WindowState::None);
}

d->deserializeWindowGeometry(mw, window);
window->setWindowState(mw.windowState);
}
d->deserializeWindowGeometry(mw, window);
window->setWindowState(mw.windowState);
}

if (!mainWindow->deserialize(mw))
return false;
if (!mainWindow->deserialize(mw))
return false;
}
}

// 2. Restore FloatingWindows
Expand Down Expand Up @@ -1186,6 +1250,15 @@ bool LayoutSaver::MultiSplitter::skipsRestore() const
[](auto it) { return it.second.skipsRestore(); });
}

LayoutSaver::Group LayoutSaver::MultiSplitter::centralGroup() const
{
for (const auto &[id, group] : groups) {
if (group.options & FrameOption_IsCentralFrame)
return group;
}
return {};
}

void LayoutSaver::Position::scaleSizes(const ScalingInfo &scalingInfo)
{
scalingInfo.applyFactorsTo(/*by-ref*/ lastFloatingGeometry);
Expand Down
Loading
Loading