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

© 2019-2023 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 on Wed Nov 1 2023 00:02:31 for KDDockWidgets API Documentation by doxygen 1.9.8