KDDockWidgets API Documentation 2.0
Loading...
Searching...
No Matches
LayoutSaver.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
19#include "LayoutSaver.h"
20#include "Config.h"
21#include "core/ViewFactory.h"
22#include "core/LayoutSaver_p.h"
23#include "core/Logging_p.h"
24#include "core/Position_p.h"
25#include "core/Utils_p.h"
26#include "core/View_p.h"
27
28#include "core/DockRegistry.h"
29#include "core/Platform.h"
30#include "core/Layout.h"
31#include "core/Group.h"
32#include "core/FloatingWindow.h"
33#include "core/DockWidget.h"
34#include "core/DockWidget_p.h"
35#include "core/MainWindow.h"
36#include "core/nlohmann_helpers_p.h"
37#include "core/layouting/Item_p.h"
38
39#include <iostream>
40#include <fstream>
41#include <cmath>
42#include <utility>
43
64using namespace KDDockWidgets;
65using namespace KDDockWidgets::Core;
66
67std::map<QString, LayoutSaver::DockWidget::Ptr> LayoutSaver::DockWidget::s_dockWidgets;
68LayoutSaver::Layout *LayoutSaver::Layout::s_currentLayoutBeingRestored = nullptr;
69
70
71inline InternalRestoreOptions internalRestoreOptions(RestoreOptions options)
72{
73 InternalRestoreOptions ret = {};
74 if (options.testFlag(RestoreOption_RelativeToMainWindow)) {
75 ret.setFlag(InternalRestoreOption::SkipMainWindowGeometry);
76 ret.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry);
77 options.setFlag(RestoreOption_RelativeToMainWindow, false);
78 }
79 if (options.testFlag(RestoreOption_AbsoluteFloatingDockWindows)) {
80 ret.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry, false);
81 options.setFlag(RestoreOption_AbsoluteFloatingDockWindows, false);
82 }
83
84 if (options != RestoreOption_None) {
85 KDDW_ERROR("Unknown options={}", int(options));
86 }
87
88 return ret;
89}
90
91bool LayoutSaver::Private::s_restoreInProgress = false;
92
93namespace KDDockWidgets {
94
95template<typename T>
96T jsonValue(const nlohmann::json &json, const char *name, const T &defaultValue)
97{
98 try {
99 // value can throw if the type has "null" value, for instance.
100 return json.value(name, defaultValue);
101 } catch (...) {
102 return defaultValue;
103 }
104}
105
106template<typename Type>
107void to_json(nlohmann::json &json, const typename Type::List &list)
108{
109 for (const auto &l : list) {
110 json.push_back(l);
111 }
112}
113
114void to_json(nlohmann::json &json, const LayoutSaver::Group &f)
115{
116 json["id"] = f.id;
117 json["isNull"] = f.isNull;
118 json["objectName"] = f.objectName;
119 json["geometry"] = f.geometry;
120 json["options"] = f.options;
121 json["currentTabIndex"] = f.currentTabIndex;
122 json["mainWindowUniqueName"] = f.mainWindowUniqueName;
123 json["dockWidgets"] = dockWidgetNames(f.dockWidgets);
124}
125void from_json(const nlohmann::json &json, LayoutSaver::Group &f)
126{
127 f.id = jsonValue(json, "id", QString());
128 f.isNull = jsonValue(json, "isNull", true);
129 f.objectName = jsonValue(json, "objectName", QString());
130 f.geometry = jsonValue(json, "geometry", Rect());
131 f.options = jsonValue(json, "options", QFlags<FrameOption>::Int {});
132 f.currentTabIndex = jsonValue(json, "currentTabIndex", 0);
133 f.mainWindowUniqueName = jsonValue(json, "mainWindowUniqueName", QString());
134
135 auto it = json.find("dockWidgets");
136 if (it == json.end())
137 return;
138
139 auto &dockWidgets = *it;
140 f.dockWidgets.reserve(( int )dockWidgets.size());
141 for (const auto &d : dockWidgets) {
142 LayoutSaver::DockWidget::Ptr dw =
143 LayoutSaver::DockWidget::dockWidgetForName(d.get<QString>());
144 f.dockWidgets.push_back(dw);
145 }
146}
147
148void to_json(nlohmann::json &json, const LayoutSaver::MultiSplitter &s)
149{
150 json["layout"] = s.layout;
151 auto &groups = json["frames"];
152 for (const auto &it : s.groups) {
153 const auto &group = it.second;
154 groups[group.id.toStdString()] = group;
155 }
156}
157
158void from_json(const nlohmann::json &json, LayoutSaver::MultiSplitter &s)
159{
160 s.groups.clear();
161 s.layout = jsonValue(json, "layout", nlohmann::json::object());
162 auto it = json.find("frames");
163 if (it == json.end())
164 return;
165 if (it->is_null())
166 return;
167
168 auto &frms = *it;
169 if (!frms.is_object())
170 KDDW_ERROR("Unexpected not object");
171
172 for (const auto &kv : frms.items()) {
173 QString key = QString::fromStdString(kv.key());
174 auto group = kv.value().get<LayoutSaver::Group>();
175 s.groups[key] = group;
176 }
177}
178
179void to_json(nlohmann::json &json, const LayoutSaver::MainWindow &mw)
180{
181 json["options"] = int(mw.options);
182 json["multiSplitterLayout"] = mw.multiSplitterLayout;
183 json["uniqueName"] = mw.uniqueName;
184 json["geometry"] = mw.geometry;
185 json["normalGeometry"] = mw.normalGeometry;
186 json["screenIndex"] = mw.screenIndex;
187 json["screenSize"] = mw.screenSize;
188 json["isVisible"] = mw.isVisible;
189 json["affinities"] = mw.affinities;
190 json["windowState"] = mw.windowState;
191
194 const Vector<QString> dockWidgets = mw.dockWidgetsForSideBar(loc);
195 if (!dockWidgets.isEmpty()) {
196 std::string key = std::string("sidebar-") + std::to_string(( int )loc);
197 json[key] = dockWidgets;
198 }
199 }
200}
201
202void from_json(const nlohmann::json &json, LayoutSaver::MainWindow &mw)
203{
204 mw.options = static_cast<decltype(mw.options)>(jsonValue(json, "options", 0));
205 mw.multiSplitterLayout = jsonValue(json, "multiSplitterLayout", LayoutSaver::MultiSplitter());
206 mw.uniqueName = jsonValue(json, "uniqueName", QString());
207 mw.geometry = jsonValue(json, "geometry", Rect());
208 mw.normalGeometry = jsonValue(json, "normalGeometry", Rect());
209 mw.screenIndex = jsonValue(json, "screenIndex", 0);
210 mw.screenSize = jsonValue(json, "screenSize", Size(800, 600));
211 mw.isVisible = jsonValue(json, "isVisible", false);
212 mw.affinities = jsonValue(json, "affinities", Vector<QString>());
213 mw.windowState = ( WindowState )jsonValue(json, "windowState", 0);
214
215 // Compatibility hack. Old json format had a single "affinityName" instead of an "affinities"
216 // list:
217 if (json.find("affinityName") != json.end()) {
218 QString affinityName = json["affinityName"].get<QString>();
219 if (!mw.affinities.contains(affinityName)) {
220 mw.affinities.push_back(affinityName);
221 }
222 }
223
226 std::string key = std::string("sidebar-") + std::to_string(( int )loc);
227 auto it = json.find(key);
228 if (it == json.end())
229 continue;
230 auto &val = *it;
231 if (val.is_array() && !val.empty()) {
232 mw.dockWidgetsPerSideBar[loc] = val.get<Vector<QString>>();
233 }
234 }
235}
236
237void to_json(nlohmann::json &json, const LayoutSaver::FloatingWindow &window)
238{
239 json["multiSplitterLayout"] = window.multiSplitterLayout;
240 json["parentIndex"] = window.parentIndex;
241 json["geometry"] = window.geometry;
242 json["normalGeometry"] = window.normalGeometry;
243 json["screenIndex"] = window.screenIndex;
244 json["screenSize"] = window.screenSize;
245 json["flags"] = window.flags;
246 json["isVisible"] = window.isVisible;
247 json["windowState"] = window.windowState;
248
249 if (!window.affinities.isEmpty()) {
250 json["affinities"] = window.affinities;
251 }
252}
253
254void from_json(const nlohmann::json &json, LayoutSaver::FloatingWindow &window)
255{
256 window.multiSplitterLayout = jsonValue(json, "multiSplitterLayout", LayoutSaver::MultiSplitter());
257 window.parentIndex = jsonValue(json, "parentIndex", -1);
258 window.geometry = jsonValue(json, "geometry", Rect());
259 window.normalGeometry = jsonValue(json, "normalGeometry", Rect());
260 window.screenIndex = jsonValue(json, "screenIndex", 0);
261 window.screenSize = jsonValue(json, "screenSize", Size(800, 600));
262 window.isVisible = jsonValue(json, "isVisible", false);
263 window.flags = jsonValue(json, "flags", int(FloatingWindowFlag::FromGlobalConfig));
264 window.windowState = ( WindowState )jsonValue(json, "windowState", 0);
265 window.affinities = jsonValue(json, "affinities", Vector<QString>());
266
267 // Compatibility hack. Old json format had a single "affinityName" instead of an "affinities"
268 // list:
269 const QString affinityName = jsonValue(json, "affinityName", QString());
270 if (!affinityName.isEmpty() && !window.affinities.contains(affinityName)) {
271 window.affinities.push_back(affinityName);
272 }
273}
274
275void to_json(nlohmann::json &json, const LayoutSaver::ScreenInfo &screenInfo)
276{
277 json["index"] = screenInfo.index;
278 json["geometry"] = screenInfo.geometry;
279 json["name"] = screenInfo.name;
280 json["devicePixelRatio"] = screenInfo.devicePixelRatio;
281}
282void from_json(const nlohmann::json &j, LayoutSaver::ScreenInfo &screenInfo)
283{
284 screenInfo.index = j.value("index", 0);
285 screenInfo.geometry = j.value("geometry", Rect());
286 screenInfo.name = j.value("name", QString());
287 screenInfo.devicePixelRatio = j.value("devicePixelRatio", 1.0);
288}
289
290void to_json(nlohmann::json &json, const LayoutSaver::Placeholder &placeHolder)
291{
292 json["isFloatingWindow"] = placeHolder.isFloatingWindow;
293 json["itemIndex"] = placeHolder.itemIndex;
294 if (placeHolder.isFloatingWindow)
295 json["indexOfFloatingWindow"] = placeHolder.indexOfFloatingWindow;
296 else
297 json["mainWindowUniqueName"] = placeHolder.mainWindowUniqueName;
298}
299
300void from_json(const nlohmann::json &json, LayoutSaver::Placeholder &placeHolder)
301{
302 placeHolder.isFloatingWindow = jsonValue(json, "isFloatingWindow", false);
303 placeHolder.itemIndex = jsonValue(json, "itemIndex", 0);
304 placeHolder.indexOfFloatingWindow = jsonValue(json, "indexOfFloatingWindow", -1);
305 placeHolder.mainWindowUniqueName = jsonValue(json, "mainWindowUniqueName", QString());
306}
307
308void to_json(nlohmann::json &json, const LayoutSaver::Position &pos)
309{
310 json["lastFloatingGeometry"] = pos.lastFloatingGeometry;
311 json["lastOverlayedGeometries"] = pos.lastOverlayedGeometries;
312 json["tabIndex"] = pos.tabIndex;
313 json["wasFloating"] = pos.wasFloating;
314 json["placeholders"] = pos.placeholders;
315}
316
317void from_json(const nlohmann::json &json, LayoutSaver::Position &pos)
318{
319 pos.lastFloatingGeometry = jsonValue(json, "lastFloatingGeometry", Rect());
320 pos.lastOverlayedGeometries = jsonValue(json, "lastOverlayedGeometries", std::unordered_map<KDDockWidgets::SideBarLocation, Rect>());
321 pos.tabIndex = jsonValue(json, "tabIndex", 0);
322 pos.wasFloating = jsonValue(json, "wasFloating", false);
323 pos.placeholders = jsonValue(json, "placeholders", LayoutSaver::Placeholder::List());
324}
325
326void to_json(nlohmann::json &json, const LayoutSaver::DockWidget &dw)
327{
328 if (!dw.affinities.isEmpty())
329 json["affinities"] = dw.affinities;
330 json["uniqueName"] = dw.uniqueName;
331 json["lastPosition"] = dw.lastPosition;
332}
333void from_json(const nlohmann::json &json, LayoutSaver::DockWidget &dw)
334{
335 auto it = json.find("affinities");
336 if (it != json.end())
337 dw.affinities = it->get<Vector<QString>>();
338
339 dw.uniqueName = jsonValue(json, "uniqueName", QString());
340 if (dw.uniqueName.isEmpty())
341 KDDW_ERROR("Unexpected no uniqueName for dockWidget");
342
343 dw.lastPosition = jsonValue(json, "lastPosition", LayoutSaver::Position());
344}
345
346void to_json(nlohmann::json &json, const typename LayoutSaver::DockWidget::List &list)
347{
348 for (const auto &mw : list) {
349 json.push_back(*mw);
350 }
351}
352
353void from_json(const nlohmann::json &json, typename LayoutSaver::DockWidget::List &list)
354{
355 list.clear();
356 for (const auto &v : json) {
357 auto it = v.find("uniqueName");
358 if (it == v.end()) {
359 KDDW_ERROR("Unexpected no uniqueName");
360 continue;
361 }
362 QString uniqueName = it->get<QString>();
363 auto dw = LayoutSaver::DockWidget::dockWidgetForName(uniqueName);
364 from_json(v, *dw);
365 list.push_back(dw);
366 }
367}
368
369}
370
371LayoutSaver::LayoutSaver(RestoreOptions options)
372 : d(new Private(options))
373{
374 d->m_dockRegistry->registerLayoutSaver();
375}
376
378{
379 d->m_dockRegistry->unregisterLayoutSaver();
380 delete d;
381}
382
383bool LayoutSaver::saveToFile(const QString &jsonFilename)
384{
385 const QByteArray data = serializeLayout();
386
387 std::ofstream file(jsonFilename.toStdString(), std::ios::binary);
388 if (!file.is_open()) {
389 KDDW_ERROR("Failed to open {}", jsonFilename);
390 return false;
391 }
392
393 file.write(data.constData(), data.size());
394 file.close();
395 return true;
396}
397
398bool LayoutSaver::restoreFromFile(const QString &jsonFilename)
399{
400 bool ok = false;
401 const QByteArray data = Platform::instance()->readFile(jsonFilename, /*by-ref*/ ok);
402
403 if (!ok)
404 return false;
405
406 return restoreLayout(data);
407}
408
410{
411 if (!d->m_dockRegistry->isSane()) {
412 KDDW_ERROR("Refusing to serialize this layout. Check previous warnings.");
413 return {};
414 }
415
416 LayoutSaver::Layout layout;
417
418 // Just a simplification. One less type of windows to handle.
419 d->m_dockRegistry->ensureAllFloatingWidgetsAreMorphed();
420
421 const auto mainWindows = d->m_dockRegistry->mainwindows();
422 layout.mainWindows.reserve(mainWindows.size());
423 for (auto mainWindow : mainWindows) {
424 if (d->matchesAffinity(mainWindow->affinities()))
425 layout.mainWindows.push_back(mainWindow->serialize());
426 }
427
428 const Vector<Core::FloatingWindow *> floatingWindows =
429 d->m_dockRegistry->floatingWindows(/*includeBeingDeleted=*/false, /*honourSkipped=*/true);
430 layout.floatingWindows.reserve(floatingWindows.size());
431 for (Core::FloatingWindow *floatingWindow : floatingWindows) {
432 if (d->matchesAffinity(floatingWindow->affinities()))
433 layout.floatingWindows.push_back(floatingWindow->serialize());
434 }
435
436 // Closed dock widgets also have interesting things to save, like geometry and placeholder info
437 const Core::DockWidget::List closedDockWidgets = d->m_dockRegistry->closedDockwidgets(/*honourSkipped=*/true);
438 layout.closedDockWidgets.reserve(closedDockWidgets.size());
439 for (Core::DockWidget *dockWidget : closedDockWidgets) {
440 if (d->matchesAffinity(dockWidget->affinities()))
441 layout.closedDockWidgets.push_back(dockWidget->d->serialize());
442 }
443
444 // Save the placeholder info. We do it last, as we also restore it last, since we need all items
445 // to be created before restoring the placeholders
446
447 const Core::DockWidget::List dockWidgets = d->m_dockRegistry->dockwidgets();
448 layout.allDockWidgets.reserve(dockWidgets.size());
449 for (Core::DockWidget *dockWidget : dockWidgets) {
450 const bool skipsRestore = dockWidget->layoutSaverOptions() & LayoutSaverOption::Skip;
451
452 if (!skipsRestore && d->matchesAffinity(dockWidget->affinities())) {
453 auto dw = dockWidget->d->serialize();
454 dw->lastPosition = dockWidget->d->lastPosition()->serialize();
455 layout.allDockWidgets.push_back(dw);
456 }
457 }
458
459 return layout.toJson();
460}
461
463{
464 LayoutSaver::DockWidget::s_dockWidgets.clear();
465 d->clearRestoredProperty();
466 if (data.isEmpty())
467 return true;
468
469 struct FrameCleanup
470 {
471 explicit FrameCleanup(LayoutSaver *saver)
472 : m_saver(saver)
473 {
474 }
475
476 ~FrameCleanup()
477 {
478 m_saver->d->deleteEmptyGroups();
479 }
480
481 FrameCleanup(const FrameCleanup &) = delete;
482 FrameCleanup &operator=(const FrameCleanup) = delete;
483
484 LayoutSaver *const m_saver;
485 };
486
487 FrameCleanup cleanup(this);
488 LayoutSaver::Layout layout;
489 if (!layout.fromJson(data)) {
490 KDDW_ERROR("Failed to parse json data");
491 return false;
492 }
493
494 if (!layout.isValid()) {
495 return false;
496 }
497
498 layout.scaleSizes(d->m_restoreOptions);
499
500 d->floatWidgetsWhichSkipRestore(layout.mainWindowNames());
501 d->floatUnknownWidgets(layout);
502
503 Private::RAIIIsRestoring isRestoring;
504
505 // Hide all dockwidgets and unparent them from any layout before starting restore
506 // We only close the stuff that the loaded JSON knows about. Unknown widgets might be newer.
507
508 d->m_dockRegistry->clear(d->m_dockRegistry->dockWidgets(layout.dockWidgetsToClose()),
509 d->m_dockRegistry->mainWindows(layout.mainWindowNames()),
510 d->m_affinityNames);
511
512 // 1. Restore main windows
513 for (const LayoutSaver::MainWindow &mw : std::as_const(layout.mainWindows)) {
514 auto mainWindow = d->m_dockRegistry->mainWindowByName(mw.uniqueName);
515 if (!mainWindow) {
516 if (auto mwFunc = Config::self().mainWindowFactoryFunc()) {
517 mainWindow = mwFunc(mw.uniqueName, mw.options);
518 } else {
519 KDDW_ERROR("Failed to restore layout create MainWindow with name {} first");
520 return false;
521 }
522 }
523
524 if (!d->matchesAffinity(mainWindow->affinities()))
525 continue;
526
527 if (!(d->m_restoreOptions & InternalRestoreOption::SkipMainWindowGeometry)) {
528 Window::Ptr window = mainWindow->view()->window();
529 d->deserializeWindowGeometry(mw, window);
530 if (mw.windowState != WindowState::None) {
531 if (auto w = mainWindow->view()->window()) {
532 w->setWindowState(mw.windowState);
533 }
534 }
535 }
536
537 if (!mainWindow->deserialize(mw))
538 return false;
539 }
540
541 // 2. Restore FloatingWindows
542 for (LayoutSaver::FloatingWindow &fw : layout.floatingWindows) {
543 if (!d->matchesAffinity(fw.affinities) || fw.skipsRestore())
544 continue;
545
546 auto parent =
547 fw.parentIndex == -1 ? nullptr : DockRegistry::self()->mainwindows().at(fw.parentIndex);
548
549 auto floatingWindow =
550 new Core::FloatingWindow({}, parent, static_cast<FloatingWindowFlags>(fw.flags));
551 fw.floatingWindowInstance = floatingWindow;
552 d->deserializeWindowGeometry(fw, floatingWindow->view()->window());
553 if (!floatingWindow->deserialize(fw)) {
554 KDDW_ERROR("Failed to deserialize floating window");
555 return false;
556 }
557 }
558
559 // 3. Restore closed dock widgets. They remain closed but acquire geometry and placeholder
560 // properties
561 for (const auto &dw : std::as_const(layout.closedDockWidgets)) {
562 if (d->matchesAffinity(dw->affinities)) {
564 }
565 }
566
567 // 4. Restore the placeholder info, now that the Items have been created
568 for (const auto &dw : std::as_const(layout.allDockWidgets)) {
569 if (!d->matchesAffinity(dw->affinities))
570 continue;
571
572 if (Core::DockWidget *dockWidget = d->m_dockRegistry->dockByName(
574 dockWidget->d->lastPosition()->deserialize(dw->lastPosition);
575 } else {
576 KDDW_ERROR("Couldn't find dock widget {}", dw->uniqueName);
577 }
578 }
579
580 return true;
581}
582
584{
585 d->m_affinityNames = affinityNames;
586 if (affinityNames.contains(QString())) {
587 // Any window with empty affinity will also be subject to save/restore
588 d->m_affinityNames.push_back(QString());
589 }
590}
591
592LayoutSaver::Private *LayoutSaver::dptr() const
593{
594 return d;
595}
596
598{
599 const Core::DockWidget::List &allDockWidgets = DockRegistry::self()->dockwidgets();
601 result.reserve(allDockWidgets.size());
602 for (Core::DockWidget *dw : allDockWidgets) {
603 if (dw->d->m_wasRestored)
604 result.push_back(dw);
605 }
606
607 return result;
608}
609
610void LayoutSaver::Private::clearRestoredProperty()
611{
612 const Core::DockWidget::List &allDockWidgets = DockRegistry::self()->dockwidgets();
613 for (Core::DockWidget *dw : allDockWidgets) {
614 dw->d->m_wasRestored = false;
615 }
616}
617
618template<typename T>
619void LayoutSaver::Private::deserializeWindowGeometry(const T &saved, Window::Ptr window)
620{
621 // Not simply calling QWidget::setGeometry() here.
622 // For QtQuick we need to modify the QWindow's geometry.
623
624 Rect geometry = saved.geometry;
625 if (!isNormalWindowState(saved.windowState)) {
626 // The window will be maximized. We first set its geometry to normal
627 // Later it's maximized and will remember this value
628 geometry = saved.normalGeometry;
629 }
630
632
633 window->setGeometry(geometry);
634 window->setVisible(saved.isVisible);
635}
636
637LayoutSaver::Private::Private(RestoreOptions options)
638 : m_dockRegistry(DockRegistry::self())
639 , m_restoreOptions(internalRestoreOptions(options))
640{
641}
642
643bool LayoutSaver::Private::matchesAffinity(const Vector<QString> &affinities) const
644{
645 return m_affinityNames.isEmpty() || affinities.isEmpty()
646 || DockRegistry::self()->affinitiesMatch(m_affinityNames, affinities);
647}
648
649void LayoutSaver::Private::floatWidgetsWhichSkipRestore(const Vector<QString> &mainWindowNames)
650{
651 // Widgets with the LayoutSaverOptions::Skip flag skip restore completely.
652 // If they were visible before they need to remain visible now.
653 // If they were previously docked we need to float them, as the main window they were on will
654 // be loading a new layout.
655
656 const auto mainWindows = DockRegistry::self()->mainWindows(mainWindowNames);
657 for (auto mw : mainWindows) {
658 const Core::DockWidget::List docks = mw->layout()->dockWidgets();
659 for (auto dw : docks) {
660 if (dw->skipsRestore()) {
661 dw->setFloating(true);
662 }
663 }
664 }
665}
666
667void LayoutSaver::Private::floatUnknownWidgets(const LayoutSaver::Layout &layout)
668{
669 // An old *.json layout file might have not know about existing dock widgets
670 // When restoring such a file, we need to float any visible dock widgets which it doesn't know
671 // about so we can restore the MainWindow layout properly
672
673 const auto mainWindows = DockRegistry::self()->mainWindows(layout.mainWindowNames());
674 for (auto mw : mainWindows) {
675 const Core::DockWidget::List docks = mw->layout()->dockWidgets();
676 for (Core::DockWidget *dw : docks) {
677 if (!layout.containsDockWidget(dw->uniqueName())) {
678 dw->setFloating(true);
679 }
680 }
681 }
682}
683
684void LayoutSaver::Private::deleteEmptyGroups() const
685{
686 // After a restore it can happen that some DockWidgets didn't exist, so weren't restored.
687 // Delete their group now.
688
689 const auto groups = m_dockRegistry->groups();
690 for (auto group : groups) {
691 if (!group->beingDeletedLater() && group->isEmpty() && !group->isCentralFrame()) {
692 if (auto item = group->layoutItem()) {
693 item->turnIntoPlaceholder();
694 } else {
695 // This doesn't happen. But the warning will make the tests fail if there's a regression.
696 KDDW_ERROR("Expected item for frame");
697 }
698 delete group;
699 }
700 }
701}
702
704{
705 return Private::s_restoreInProgress;
706}
707
708bool LayoutSaver::Layout::isValid() const
709{
710 if (serializationVersion != KDDOCKWIDGETS_SERIALIZATION_VERSION) {
711 KDDW_ERROR("Serialization format is too old {}, current={}", serializationVersion, KDDOCKWIDGETS_SERIALIZATION_VERSION);
712 return false;
713 }
714
715 for (auto &m : mainWindows) {
716 if (!m.isValid())
717 return false;
718 }
719
720 for (auto &m : floatingWindows) {
721 if (!m.isValid())
722 return false;
723 }
724
725 for (auto &m : allDockWidgets) {
726 if (!m->isValid())
727 return false;
728 }
729
730 return true;
731}
732
733namespace KDDockWidgets {
734void to_json(nlohmann::json &j, const LayoutSaver::Layout &layout)
735{
736 j["serializationVersion"] = layout.serializationVersion;
737 j["mainWindows"] = layout.mainWindows;
738 j["floatingWindows"] = layout.floatingWindows;
739 j["closedDockWidgets"] = ::dockWidgetNames(layout.closedDockWidgets);
740 j["allDockWidgets"] = layout.allDockWidgets;
741 j["screenInfo"] = layout.screenInfo;
742}
743
744void from_json(const nlohmann::json &j, LayoutSaver::Layout &layout)
745{
746 layout.serializationVersion = j.value("serializationVersion", 0);
747 layout.mainWindows = j.value("mainWindows", LayoutSaver::MainWindow::List {});
748 layout.allDockWidgets = j.value("allDockWidgets", LayoutSaver::DockWidget::List {});
749
750 layout.closedDockWidgets.clear();
751
752 const auto closedDockWidgets = j.value("closedDockWidgets", Vector<QString>());
753 for (const QString &name : closedDockWidgets) {
754 layout.closedDockWidgets.push_back(
755 LayoutSaver::DockWidget::dockWidgetForName(name));
756 }
757
758 layout.floatingWindows = j.value("floatingWindows", LayoutSaver::FloatingWindow::List {});
759 layout.screenInfo = j.value("screenInfo", LayoutSaver::ScreenInfo::List {});
760}
761}
762
763QByteArray LayoutSaver::Layout::toJson() const
764{
765 nlohmann::json json = *this;
766 return QByteArray::fromStdString(json.dump(4));
767}
768
769bool LayoutSaver::Layout::fromJson(const QByteArray &jsonData)
770{
771 nlohmann::json json = nlohmann::json::parse(jsonData, nullptr, /*allow_exceptions=*/false);
772 if (json.is_discarded()) {
773 return false;
774 }
775
776 try {
777 from_json(json, *this);
778 } catch (const std::exception &e) {
779 KDDW_ERROR("LayoutSaver::Layout::fromJson: Caught exception: {}", e.what());
780 return false;
781 } catch (...) {
782 KDDW_ERROR("LayoutSaver::Layout::fromJson: Caught exception.");
783 return false;
784 }
785
786 return true;
787}
788
789void LayoutSaver::Layout::scaleSizes(InternalRestoreOptions options)
790{
791 if (mainWindows.isEmpty())
792 return;
793
794 const bool skipsMainWindowGeometry = options & InternalRestoreOption::SkipMainWindowGeometry;
795 if (!skipsMainWindowGeometry) {
796 // No scaling to do. All windows will be restored with the exact size specified in the
797 // saved JSON layouts.
798 return;
799 }
800
801 // We won't restore MainWindow's geometry, we use whatever the user has now, meaning
802 // we need to scale all dock widgets inside the layout, as the layout might not have
803 // the same size as specified in the saved JSON layout
804 for (auto &mw : mainWindows)
805 mw.scaleSizes();
806
807
808 // MainWindow has a different size than the one in JSON, so we also restore FloatingWindows
809 // relatively to the user set new MainWindow size
810 const bool useRelativeSizesForFloatingWidgets =
811 options & InternalRestoreOption::RelativeFloatingWindowGeometry;
812
813 if (useRelativeSizesForFloatingWidgets) {
814 for (auto &fw : floatingWindows) {
815 LayoutSaver::MainWindow mw = mainWindowForIndex(fw.parentIndex);
816 if (mw.scalingInfo.isValid())
817 fw.scaleSizes(mw.scalingInfo);
818 }
819 }
820
821 const ScalingInfo firstScalingInfo = mainWindows.constFirst().scalingInfo;
822 if (firstScalingInfo.isValid()) {
823 for (auto &dw : allDockWidgets) {
824 // TODO: Determine the best main window. This only interesting for closed dock
825 // widget geometry which was previously floating. But they still have some other
826 // main window as parent.
827 dw->scaleSizes(firstScalingInfo);
828 }
829 }
830}
831
832LayoutSaver::MainWindow LayoutSaver::Layout::mainWindowForIndex(int index) const
833{
834 if (index < 0 || index >= mainWindows.size())
835 return {};
836
837 return mainWindows.at(index);
838}
839
840LayoutSaver::FloatingWindow LayoutSaver::Layout::floatingWindowForIndex(int index) const
841{
842 if (index < 0 || index >= floatingWindows.size())
843 return {};
844
845 return floatingWindows.at(index);
846}
847
848Vector<QString> LayoutSaver::Layout::mainWindowNames() const
849{
850 Vector<QString> names;
851 names.reserve(mainWindows.size());
852 for (const auto &mw : mainWindows) {
853 names.push_back(mw.uniqueName);
854 }
855
856 return names;
857}
858
859Vector<QString> LayoutSaver::Layout::dockWidgetNames() const
860{
861 Vector<QString> names;
862 names.reserve(allDockWidgets.size());
863 for (const auto &dw : allDockWidgets) {
864 names.push_back(dw->uniqueName);
865 }
866
867 return names;
868}
869
870Vector<QString> LayoutSaver::Layout::dockWidgetsToClose() const
871{
872 // Before restoring a layout we close all dock widgets, unless they're a floating window with
873 // the DontCloseBeforeRestore flag
874
875 Vector<QString> names;
876 names.reserve(allDockWidgets.size());
877 auto registry = DockRegistry::self();
878 for (const auto &dw : allDockWidgets) {
879 if (Core::DockWidget *dockWidget = registry->dockByName(dw->uniqueName)) {
880
881 bool doClose = true;
882
883 if (dockWidget->skipsRestore()) {
884 if (auto fw = dockWidget->floatingWindow()) {
885 if (fw->allDockWidgetsHave(LayoutSaverOption::Skip)) {
886 // All dock widgets in this floating window skips float, so we can honour it
887 // for all.
888 doClose = false;
889 }
890 }
891 }
892
893 if (doClose)
894 names.push_back(dw->uniqueName);
895 }
896 }
897
898 return names;
899}
900
901bool LayoutSaver::Layout::containsDockWidget(const QString &uniqueName) const
902{
903 return std::find_if(allDockWidgets.cbegin(), allDockWidgets.cend(),
904 [uniqueName](const std::shared_ptr<LayoutSaver::DockWidget> &dock) {
905 return dock->uniqueName == uniqueName;
906 })
907 != allDockWidgets.cend();
908}
909
910bool LayoutSaver::Group::isValid() const
911{
912 if (isNull)
913 return true;
914
915 if (!geometry.isValid()) {
916 KDDW_ERROR("Invalid geometry");
917 return false;
918 }
919
920 if (id.isEmpty()) {
921 KDDW_ERROR("Invalid id");
922 return false;
923 }
924
925 if (!dockWidgets.isEmpty()) {
926 if (currentTabIndex >= dockWidgets.size() || currentTabIndex < 0) {
927
928 if (dockWidgets.isEmpty() || KDDockWidgets::Config::self().layoutSaverUsesStrictMode()) {
929 KDDW_ERROR("Invalid tab index = {}, size = {}", currentTabIndex, dockWidgets.size());
930 return false;
931 }
932
933 // Layout seems corrupted. Use 0 as current tab and let's continue.
934 KDDW_WARN("Invalid tab index = {}, size = {}", currentTabIndex, dockWidgets.size());
935 const_cast<LayoutSaver::Group *>(this)->currentTabIndex = 0;
936 }
937 }
938
939 for (auto &dw : dockWidgets) {
940 if (!dw->isValid())
941 return false;
942 }
943
944 return true;
945}
946
947bool LayoutSaver::Group::hasSingleDockWidget() const
948{
949 return dockWidgets.size() == 1;
950}
951
952bool LayoutSaver::Group::skipsRestore() const
953{
954 return std::all_of(dockWidgets.cbegin(), dockWidgets.cend(),
955 [](LayoutSaver::DockWidget::Ptr dw) { return dw->skipsRestore(); });
956}
957
958LayoutSaver::DockWidget::Ptr LayoutSaver::Group::singleDockWidget() const
959{
960 if (!hasSingleDockWidget())
961 return {};
962
963 return dockWidgets.first();
964}
965
966bool LayoutSaver::DockWidget::isValid() const
967{
968 return !uniqueName.isEmpty();
969}
970
971void LayoutSaver::DockWidget::scaleSizes(const ScalingInfo &scalingInfo)
972{
973 lastPosition.scaleSizes(scalingInfo);
974}
975
976bool LayoutSaver::DockWidget::skipsRestore() const
977{
978 if (Core::DockWidget *dw = DockRegistry::self()->dockByName(uniqueName))
979 return dw->skipsRestore();
980
981 return false;
982}
983
984bool LayoutSaver::FloatingWindow::isValid() const
985{
986 if (!multiSplitterLayout.isValid())
987 return false;
988
989 if (!geometry.isValid()) {
990 KDDW_ERROR("Invalid geometry");
991 return false;
992 }
993
994 return true;
995}
996
997bool LayoutSaver::FloatingWindow::hasSingleDockWidget() const
998{
999 return multiSplitterLayout.hasSingleDockWidget();
1000}
1001
1002LayoutSaver::DockWidget::Ptr LayoutSaver::FloatingWindow::singleDockWidget() const
1003{
1004 return multiSplitterLayout.singleDockWidget();
1005}
1006
1007bool LayoutSaver::FloatingWindow::skipsRestore() const
1008{
1009 return multiSplitterLayout.skipsRestore();
1010}
1011
1012void LayoutSaver::FloatingWindow::scaleSizes(const ScalingInfo &scalingInfo)
1013{
1014 scalingInfo.applyFactorsTo(/*by-ref*/ geometry);
1015}
1016
1017bool LayoutSaver::MainWindow::isValid() const
1018{
1019 return multiSplitterLayout.isValid();
1020}
1021
1022Vector<QString> LayoutSaver::MainWindow::dockWidgetsForSideBar(SideBarLocation loc) const
1023{
1024 auto it = dockWidgetsPerSideBar.find(loc);
1025 return it == dockWidgetsPerSideBar.cend() ? Vector<QString>() : it->second;
1026}
1027
1028void LayoutSaver::MainWindow::scaleSizes()
1029{
1030 if (scalingInfo.isValid()) {
1031 // Doesn't happen, it's called only once
1032 assert(false);
1033 return;
1034 }
1035
1036 scalingInfo = ScalingInfo(uniqueName, geometry, screenIndex);
1037}
1038
1039bool LayoutSaver::MultiSplitter::isValid() const
1040{
1041 return layout.is_object() && !layout.empty();
1042}
1043
1044bool LayoutSaver::MultiSplitter::hasSingleDockWidget() const
1045{
1046 return groups.size() == 1 && groups.cbegin()->second.hasSingleDockWidget();
1047}
1048
1049LayoutSaver::DockWidget::Ptr LayoutSaver::MultiSplitter::singleDockWidget() const
1050{
1051 if (!hasSingleDockWidget())
1052 return {};
1053
1054 auto group = groups.begin()->second;
1055 return group.singleDockWidget();
1056}
1057
1058bool LayoutSaver::MultiSplitter::skipsRestore() const
1059{
1060 return std::all_of(groups.cbegin(), groups.cend(),
1061 [](auto it) { return it.second.skipsRestore(); });
1062}
1063
1064void LayoutSaver::Position::scaleSizes(const ScalingInfo &scalingInfo)
1065{
1066 scalingInfo.applyFactorsTo(/*by-ref*/ lastFloatingGeometry);
1067}
1068
1069namespace {
1070
1071Core::Screen::Ptr screenForMainWindow(Core::MainWindow *mw)
1072{
1073 return mw->view()->d->screen();
1074}
1075
1076}
1077
1078LayoutSaver::ScalingInfo::ScalingInfo(const QString &mainWindowId, Rect savedMainWindowGeo,
1079 int screenIndex)
1080{
1081 auto mainWindow = DockRegistry::self()->mainWindowByName(mainWindowId);
1082 if (!mainWindow) {
1083 KDDW_ERROR("Failed to find main window with name {}", mainWindowId);
1084 return;
1085 }
1086
1087 if (!savedMainWindowGeo.isValid() || savedMainWindowGeo.isNull()) {
1088 KDDW_ERROR("Invalid saved main window geometry {}", savedMainWindowGeo);
1089 return;
1090 }
1091
1092 if (!mainWindow->geometry().isValid() || mainWindow->geometry().isNull()) {
1093 KDDW_ERROR("Invalid main window geometry {}", mainWindow->geometry());
1094 return;
1095 }
1096
1097 const int currentScreenIndex =
1098 Platform::instance()->screens().indexOf(screenForMainWindow(mainWindow));
1099
1100 this->mainWindowName = mainWindowId;
1101 this->savedMainWindowGeometry = savedMainWindowGeo;
1102 realMainWindowGeometry =
1103 mainWindow->window()->d->windowGeometry(); // window() as our main window might be embedded
1104 widthFactor = double(realMainWindowGeometry.width()) / savedMainWindowGeo.width();
1105 heightFactor = double(realMainWindowGeometry.height()) / savedMainWindowGeo.height();
1106 mainWindowChangedScreen = currentScreenIndex != screenIndex;
1107}
1108
1109void LayoutSaver::ScalingInfo::translatePos(Point &pt) const
1110{
1111 const int deltaX = pt.x() - savedMainWindowGeometry.x();
1112 const int deltaY = pt.y() - savedMainWindowGeometry.y();
1113
1114 const double newDeltaX = deltaX * widthFactor;
1115 const double newDeltaY = deltaY * heightFactor;
1116
1117 pt.setX(int(std::ceil(savedMainWindowGeometry.x() + newDeltaX)));
1118 pt.setY(int(std::ceil(savedMainWindowGeometry.y() + newDeltaY)));
1119}
1120
1121void LayoutSaver::ScalingInfo::applyFactorsTo(Point &pt) const
1122{
1123 translatePos(pt);
1124}
1125
1126void LayoutSaver::ScalingInfo::applyFactorsTo(Size &sz) const
1127{
1128 sz.setWidth(int(widthFactor * sz.width()));
1129 sz.setHeight(int(heightFactor * sz.height()));
1130}
1131
1132void LayoutSaver::ScalingInfo::applyFactorsTo(Rect &rect) const
1133{
1134 if (rect.isEmpty())
1135 return;
1136
1137 Point pos = rect.topLeft();
1138 Size size = rect.size();
1139
1140 applyFactorsTo(/*by-ref*/ size);
1141
1142
1143 if (!mainWindowChangedScreen) {
1144 // Don't play with floating window position if the main window changed screen.
1145 // There's too many corner cases that push the floating windows out of bounds, and
1146 // we're not even considering monitors with different HDPI. We can support only the simple
1147 // case. For complex cases we'll try to guarantee the window is placed somewhere reasonable.
1148 applyFactorsTo(/*by-ref*/ pos);
1149 }
1150
1151 rect.moveTopLeft(pos);
1152 rect.setSize(size);
1153}
1154
1155LayoutSaver::Private::RAIIIsRestoring::RAIIIsRestoring()
1156{
1157 LayoutSaver::Private::s_restoreInProgress = true;
1158}
1159
1160LayoutSaver::Private::RAIIIsRestoring::~RAIIIsRestoring()
1161{
1162 LayoutSaver::Private::s_restoreInProgress = false;
1163}
Application-wide config to tune certain behaviours of the framework.
InternalRestoreOptions internalRestoreOptions(RestoreOptions options)
Class to save and restore dockwidget layouts.
A widget that supports an arbitrary number of splitters (called Separators) in any combination of ver...
MainWindowFactoryFunc mainWindowFactoryFunc() const
Returns the MainWindowFactoryFunc. nullptr by default.
Definition Config.cpp:150
static Config & self()
returns the singleton Config instance
Definition Config.cpp:87
bool layoutSaverUsesStrictMode() const
Definition Config.cpp:387
View * view() const
Returns the view associated with this controller, if any.
The DockWidget base-class. DockWidget and Core::DockWidget are only split in two so we can share some...
static DockWidget * deserialize(const std::shared_ptr< LayoutSaver::DockWidget > &)
Constructs a dock widget from its serialized form.
static void ensureRectIsOnScreen(Rect &geometry)
The MainWindow base-class. MainWindow and MainWindowBase are only split in two so we can share some c...
virtual QByteArray readFile(const QString &, bool &ok) const
static Platform * instance()
Returns the platform singleton.
virtual Vector< std::shared_ptr< Screen > > screens() const =0
Returns all available screens.
bool affinitiesMatch(const Vector< QString > &affinities1, const Vector< QString > &affinities2) const
Q_INVOKABLE KDDockWidgets::Core::MainWindow * mainWindowByName(const QString &) const
static DockRegistry * self()
Vector< Core::MainWindow * > mainWindows(const Vector< QString > &names)
overload returning only the ones with the specified names
Vector< Core::DockWidget * > dockwidgets() const
returns all DockWidget instances
Vector< Core::MainWindow * > mainwindows() const
returns all MainWindow instances
LayoutSaver allows to save or restore layouts.
Definition LayoutSaver.h:56
static bool restoreInProgress()
returns whether a restore (restoreLayout) is in progress
void setAffinityNames(const Vector< QString > &affinityNames)
Sets the list of affinity names for which restore and save will be applied on. Allows to save/restore...
bool saveToFile(const QString &jsonFilename)
saves the layout to JSON file
QByteArray serializeLayout() const
saves the layout into a byte array
Vector< Core::DockWidget * > restoredDockWidgets() const
returns a list of dock widgets which were restored since the last restoreLayout() or restoreFromFile(...
bool restoreLayout(const QByteArray &)
restores the layout from a byte array All MainWindows and DockWidgets should have been created before...
bool restoreFromFile(const QString &jsonFilename)
restores the layout from a JSON file
LayoutSaver(RestoreOptions options=RestoreOption_None)
Constructor. Construction on the stack is suggested.
The DockWidget controller that's shared between QtWidgets and QtQuick frontends.
The MainWindow base-class that's shared between QtWidgets and QtQuick stack.
Class to abstract QAction, so code still works with QtQuick and Flutter.
@ RestoreOption_RelativeToMainWindow
@ RestoreOption_AbsoluteFloatingDockWindows
Skips scaling of floating dock windows relative to the main window.
SideBarLocation
Each main window supports 4 sidebars.
void from_json(const nlohmann::json &json, LayoutSaver::Group &f)
T jsonValue(const nlohmann::json &json, const char *name, const T &defaultValue)
void to_json(nlohmann::json &json, const typename Type::List &list)
const char * constData() const const
QByteArray fromStdString(const std::string &str)
bool isEmpty() const const
int size() const const
QString fromStdString(const std::string &str)
bool isEmpty() const const
void push_back(QChar ch)
std::string toStdString() const const

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated by doxygen 1.9.8