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