Files
KDDockWidgets/src/LayoutSaver.cpp
Sergio Martins 05f93a98f0 LayoutSaver: Only close dock widgets that would be restored
It can happen that the JSON layout knew about less dock widgets
than there are, as there can be new ones now
2020-05-26 19:49:02 +01:00

907 lines
29 KiB
C++

/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* @brief Class to save and restore dock widget layouts.
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
#include "LayoutSaver.h"
#include "LayoutSaver_p.h"
#include "Config.h"
#include "DockRegistry_p.h"
#include "DockWidgetBase.h"
#include "DropArea_p.h"
#include "Logging_p.h"
#include "Frame_p.h"
#include "Position_p.h"
#include "multisplitter/Item_p.h"
#include "FrameworkWidgetFactory.h"
#include "MainWindow.h"
#include <qmath.h>
#include <QDebug>
#include <QSettings>
#include <QApplication>
#include <QFile>
#include <memory>
using namespace KDDockWidgets;
QHash<QString, LayoutSaver::DockWidget::Ptr> LayoutSaver::DockWidget::s_dockWidgets;
LayoutSaver::Layout* LayoutSaver::Layout::s_currentLayoutBeingRestored = nullptr;
class KDDockWidgets::LayoutSaver::Private
{
public:
struct RAIIIsRestoring
{
RAIIIsRestoring()
{
LayoutSaver::Private::s_restoreInProgress = true;
}
~RAIIIsRestoring()
{
LayoutSaver::Private::s_restoreInProgress = false;
}
Q_DISABLE_COPY(RAIIIsRestoring)
};
Private(RestoreOptions options)
: m_dockRegistry(DockRegistry::self())
, m_restoreOptions(options)
{
}
bool matchesAffinity(const QStringList &affinities) const {
return m_affinityNames.isEmpty() || affinities.isEmpty() || DockRegistry::self()->affinitiesMatch(m_affinityNames, affinities);
}
template <typename T>
void deserializeWindowGeometry(const T &saved, QWidgetOrQuick *topLevel);
void deleteEmptyFrames();
void clearRestoredProperty();
std::unique_ptr<QSettings> settings() const;
DockRegistry *const m_dockRegistry;
const RestoreOptions m_restoreOptions;
QStringList m_affinityNames;
static bool s_restoreInProgress;
};
bool LayoutSaver::Private::s_restoreInProgress = false;
static QVariantList stringListToVariant(const QStringList &strs)
{
QVariantList variantList;
variantList.reserve(strs.size());
for (const QString &str : strs)
variantList.push_back(str);
return variantList;
}
static QStringList variantToStringList(const QVariantList &variantList)
{
QStringList stringList;
stringList.reserve(variantList.size());
for (const QVariant &variant : variantList)
stringList.push_back(variant.toString());
return stringList;
}
LayoutSaver::LayoutSaver(RestoreOptions options)
: d(new Private(options))
{
}
LayoutSaver::~LayoutSaver()
{
delete d;
}
bool LayoutSaver::saveToFile(const QString &jsonFilename)
{
const QByteArray data = serializeLayout();
QFile f(jsonFilename);
if (!f.open(QIODevice::WriteOnly)) {
qWarning() << Q_FUNC_INFO << "Failed to open" << jsonFilename << f.errorString();
return false;
}
f.write(data);
return true;
}
bool LayoutSaver::restoreFromFile(const QString &jsonFilename)
{
QFile f(jsonFilename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << Q_FUNC_INFO << "Failed to open" << jsonFilename << f.errorString();
return false;
}
const QByteArray data = f.readAll();
const bool result = restoreLayout(data);
return result;
}
QByteArray LayoutSaver::serializeLayout() const
{
if (!d->m_dockRegistry->isSane()) {
qWarning() << Q_FUNC_INFO << "Refusing to serialize this layout. Check previous warnings.";
return {};
}
LayoutSaver::Layout layout;
// Just a simplification. One less type of windows to handle.
d->m_dockRegistry->ensureAllFloatingWidgetsAreMorphed();
const MainWindowBase::List mainWindows = d->m_dockRegistry->mainwindows();
layout.mainWindows.reserve(mainWindows.size());
for (MainWindowBase *mainWindow : mainWindows) {
if (d->matchesAffinity(mainWindow->affinities()))
layout.mainWindows.push_back(mainWindow->serialize());
}
const QVector<KDDockWidgets::FloatingWindow*> floatingWindows = d->m_dockRegistry->nestedwindows();
layout.floatingWindows.reserve(floatingWindows.size());
for (KDDockWidgets::FloatingWindow *floatingWindow : floatingWindows) {
if (d->matchesAffinity(floatingWindow->affinities()))
layout.floatingWindows.push_back(floatingWindow->serialize());
}
// Closed dock widgets also have interesting things to save, like geometry and placeholder info
const DockWidgetBase::List closedDockWidgets = d->m_dockRegistry->closedDockwidgets();
layout.closedDockWidgets.reserve(closedDockWidgets.size());
for (DockWidgetBase *dockWidget : closedDockWidgets) {
if (d->matchesAffinity(dockWidget->affinities()))
layout.closedDockWidgets.push_back(dockWidget->serialize());
}
// Save the placeholder info. We do it last, as we also restore it last, since we need all items to be created
// before restoring the placeholders
const DockWidgetBase::List dockWidgets = d->m_dockRegistry->dockwidgets();
layout.allDockWidgets.reserve(dockWidgets.size());
for (DockWidgetBase *dockWidget : dockWidgets) {
if (d->matchesAffinity(dockWidget->affinities())) {
auto dw = dockWidget->serialize();
dw->lastPosition = dockWidget->lastPositions().serialize();
layout.allDockWidgets.push_back(dw);
}
}
return layout.toJson();
}
bool LayoutSaver::restoreLayout(const QByteArray &data)
{
d->clearRestoredProperty();
if (data.isEmpty())
return true;
Private::RAIIIsRestoring isRestoring;
struct FrameCleanup {
FrameCleanup(LayoutSaver *saver)
: m_saver(saver)
{
}
~FrameCleanup()
{
m_saver->d->deleteEmptyFrames();
}
LayoutSaver *const m_saver;
};
FrameCleanup cleanup(this);
LayoutSaver::Layout layout;
if (!layout.fromJson(data)) {
qWarning() << Q_FUNC_INFO << "Failed to parse json data";
return false;
}
if (!layout.isValid()) {
return false;
}
if (d->m_restoreOptions & RestoreOption_RelativeToMainWindow)
layout.scaleSizes();
// Hide all dockwidgets and unparent them from any layout before starting restore
// We only close the stuff that the loaded JSON knows about. Unknown widgets might be newer.
d->m_dockRegistry->clear(d->m_dockRegistry->dockWidgets(layout.dockWidgetNames()),
d->m_dockRegistry->mainWindows(layout.mainWindowNames()),
d->m_affinityNames);
// 1. Restore main windows
for (const LayoutSaver::MainWindow &mw : qAsConst(layout.mainWindows)) {
MainWindowBase *mainWindow = d->m_dockRegistry->mainWindowByName(mw.uniqueName);
if (!mainWindow ) {
if (auto mwFunc = Config::self().mainWindowFactoryFunc()) {
mainWindow = mwFunc(mw.uniqueName);
} else {
qWarning() << "Failed to restore layout create MainWindow with name" << mw.uniqueName << "first";
return false;
}
}
if (!d->matchesAffinity(mainWindow->affinities()))
continue;
if (!(d->m_restoreOptions & RestoreOption_RelativeToMainWindow))
d->deserializeWindowGeometry(mw, mainWindow->window()); // window(), as the MainWindow can be embedded
if (!mainWindow->deserialize(mw))
return false;
}
// 2. Restore FloatingWindows
for (const LayoutSaver::FloatingWindow &fw : qAsConst(layout.floatingWindows)) {
if (!d->matchesAffinity(fw.affinities))
continue;
MainWindowBase *parent = fw.parentIndex == -1 ? nullptr
: DockRegistry::self()->mainwindows().at(fw.parentIndex);
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(parent);
d->deserializeWindowGeometry(fw, floatingWindow);
if (!floatingWindow->deserialize(fw)) {
qWarning() << Q_FUNC_INFO << "Failed to deserialize floating window";
return false;
}
}
// 3. Restore closed dock widgets. They remain closed but acquire geometry and placeholder properties
for (const auto &dw : qAsConst(layout.closedDockWidgets)) {
if (d->matchesAffinity(dw->affinities)) {
DockWidgetBase::deserialize(dw);
}
}
// 4. Restore the placeholder info, now that the Items have been created
for (const auto &dw : qAsConst(layout.allDockWidgets)) {
if (!d->matchesAffinity(dw->affinities))
continue;
if (DockWidgetBase *dockWidget = d->m_dockRegistry->dockByName(dw->uniqueName)) {
dockWidget->lastPositions().deserialize(dw->lastPosition);
} else {
qWarning() << Q_FUNC_INFO << "Couldn't find dock widget" << dw->uniqueName;
}
}
return true;
}
void LayoutSaver::setAffinityNames(const QStringList &affinityNames)
{
d->m_affinityNames = affinityNames;
if (affinityNames.contains(QString())) {
// Any window with empty affinity will also be subject to save/restore
d->m_affinityNames << QString();
}
}
DockWidgetBase::List LayoutSaver::restoredDockWidgets() const
{
const DockWidgetBase::List &allDockWidgets = DockRegistry::self()->dockwidgets();
DockWidgetBase::List result;
result.reserve(allDockWidgets.size());
for (DockWidgetBase *dw : allDockWidgets) {
if (dw->property("kddockwidget_was_restored").toBool())
result.push_back(dw);
}
return result;
}
void LayoutSaver::Private::clearRestoredProperty()
{
const DockWidgetBase::List &allDockWidgets = DockRegistry::self()->dockwidgets();
for (DockWidgetBase *dw : allDockWidgets) {
dw->setProperty("kddockwidget_was_restored", QVariant());
}
}
template <typename T>
void LayoutSaver::Private::deserializeWindowGeometry(const T &saved, QWidgetOrQuick *topLevel)
{
topLevel->setGeometry(saved.geometry);
topLevel->setVisible(saved.isVisible);
}
void LayoutSaver::Private::deleteEmptyFrames()
{
// After a restore it can happen that some DockWidgets didn't exist, so weren't restored.
// Delete their frame now.
for (auto frame : m_dockRegistry->frames()) {
if (!frame->beingDeletedLater() && frame->isEmpty() && !frame->isCentralFrame())
delete frame;
}
}
std::unique_ptr<QSettings> LayoutSaver::Private::settings() const
{
auto settings = std::unique_ptr<QSettings>(new QSettings(qApp->organizationName(),
qApp->applicationName()));
settings->beginGroup(QStringLiteral("KDDockWidgets::LayoutSaver"));
return settings;
}
bool LayoutSaver::restoreInProgress()
{
return Private::s_restoreInProgress;
}
bool LayoutSaver::Layout::isValid() const
{
if (serializationVersion != KDDOCKWIDGETS_SERIALIZATION_VERSION) {
qWarning() << Q_FUNC_INFO << "Serialization format is too old"
<< serializationVersion << "current=" << KDDOCKWIDGETS_SERIALIZATION_VERSION;
return false;
}
for (auto &m : mainWindows) {
if (!m.isValid())
return false;
}
for (auto &m : floatingWindows) {
if (!m.isValid())
return false;
}
for (auto &m : allDockWidgets) {
if (!m->isValid())
return false;
}
return true;
}
QByteArray LayoutSaver::Layout::toJson() const
{
QJsonDocument doc = QJsonDocument::fromVariant(toVariantMap());
return doc.toJson();
}
bool LayoutSaver::Layout::fromJson(const QByteArray &jsonData)
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
if (error.error == QJsonParseError::NoError) {
fromVariantMap(doc.toVariant().toMap());
return true;
}
return false;
}
QVariantMap LayoutSaver::Layout::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("serializationVersion"), serializationVersion);
map.insert(QStringLiteral("mainWindows"), toVariantList<LayoutSaver::MainWindow>(mainWindows));
map.insert(QStringLiteral("floatingWindows"), toVariantList<LayoutSaver::FloatingWindow>(floatingWindows));
map.insert(QStringLiteral("closedDockWidgets"), ::dockWidgetNames(closedDockWidgets));
map.insert(QStringLiteral("allDockWidgets"), toVariantList(allDockWidgets));
map.insert(QStringLiteral("screenInfo"), toVariantList<LayoutSaver::ScreenInfo>(screenInfo));
return map;
}
void LayoutSaver::Layout::fromVariantMap(const QVariantMap &map)
{
allDockWidgets.clear();
const QVariantList dockWidgetsV = map.value(QStringLiteral("allDockWidgets")).toList();
for (const QVariant &v : dockWidgetsV) {
const QVariantMap dwV = v.toMap();
const QString name = dwV.value(QStringLiteral("uniqueName")).toString();
auto dw = LayoutSaver::DockWidget::dockWidgetForName(name);
dw->fromVariantMap(dwV);
allDockWidgets.push_back(dw);
}
closedDockWidgets.clear();
const QVariantList closedDockWidgetsV = map.value(QStringLiteral("closedDockWidgets")).toList();
closedDockWidgets.reserve(closedDockWidgetsV.size());
for (const QVariant &v : closedDockWidgetsV) {
closedDockWidgets.push_back(LayoutSaver::DockWidget::dockWidgetForName(v.toString()));
}
serializationVersion = map.value(QStringLiteral("serializationVersion")).toInt();
mainWindows = fromVariantList<LayoutSaver::MainWindow>(map.value(QStringLiteral("mainWindows")).toList());
floatingWindows = fromVariantList<LayoutSaver::FloatingWindow>(map.value(QStringLiteral("floatingWindows")).toList());
screenInfo = fromVariantList<LayoutSaver::ScreenInfo>(map.value(QStringLiteral("screenInfo")).toList());
}
void LayoutSaver::Layout::scaleSizes()
{
if (mainWindows.isEmpty())
return;
for (auto &mw : mainWindows)
mw.scaleSizes();
for (auto &fw : floatingWindows) {
LayoutSaver::MainWindow mw = mainWindowForIndex(fw.parentIndex);
if (mw.scalingInfo.isValid())
fw.scaleSizes(mw.scalingInfo);
}
const ScalingInfo firstScalingInfo = mainWindows.constFirst().scalingInfo;
if (firstScalingInfo.isValid()) {
for (auto &dw : allDockWidgets) {
// TODO: Determine the best main window. This only interesting for closed dock widget geometry
// which was previously floating. But they still have some other main window as parent.
dw->scaleSizes(firstScalingInfo);
}
}
}
LayoutSaver::MainWindow LayoutSaver::Layout::mainWindowForIndex(int index) const
{
if (index < 0 || index >= mainWindows.size())
return {};
return mainWindows.at(index);
}
QStringList LayoutSaver::Layout::mainWindowNames() const
{
QStringList names;
names.reserve(mainWindows.size());
for (const auto &mw : mainWindows) {
names << mw.uniqueName;
}
return names;
}
QStringList LayoutSaver::Layout::dockWidgetNames() const
{
QStringList names;
names.reserve(allDockWidgets.size());
for (const auto &dw : allDockWidgets) {
names << dw->uniqueName;
}
return names;
}
bool LayoutSaver::Frame::isValid() const
{
if (isNull)
return true;
if (!geometry.isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid geometry";
return false;
}
if (id.isEmpty()) {
qWarning() << Q_FUNC_INFO << "Invalid id";
return false;
}
if (options > 3) {
qWarning() << Q_FUNC_INFO << "Invalid options" << options;
return false;
}
if (!dockWidgets.isEmpty()) {
if (currentTabIndex >= dockWidgets.size() || currentTabIndex < 0) {
qWarning() << Q_FUNC_INFO << "Invalid tab index" << currentTabIndex << dockWidgets.size();
return false;
}
}
for (auto &dw : dockWidgets) {
if (!dw->isValid())
return false;
}
return true;
}
void LayoutSaver::Frame::scaleSizes(const ScalingInfo &scalingInfo)
{
scalingInfo.applyFactorsTo(geometry);
}
QVariantMap LayoutSaver::Frame::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("id"), id);
map.insert(QStringLiteral("isNull"), isNull);
map.insert(QStringLiteral("objectName"), objectName);
map.insert(QStringLiteral("geometry"), Layouting::rectToMap(geometry));
map.insert(QStringLiteral("options"), options);
map.insert(QStringLiteral("currentTabIndex"), currentTabIndex);
map.insert(QStringLiteral("dockWidgets"), dockWidgetNames(dockWidgets));
return map;
}
void LayoutSaver::Frame::fromVariantMap(const QVariantMap &map)
{
if (map.isEmpty()) {
isNull = true;
dockWidgets.clear();
return;
}
id = map.value(QStringLiteral("id")).toString();
isNull = map.value(QStringLiteral("isNull")).toBool();
objectName = map.value(QStringLiteral("objectName")).toString();
geometry = Layouting::mapToRect(map.value(QStringLiteral("geometry")).toMap());
options = map.value(QStringLiteral("options")).toUInt();
currentTabIndex = map.value(QStringLiteral("currentTabIndex")).toInt();
const QVariantList dockWidgetsV = map.value(QStringLiteral("dockWidgets")).toList();
dockWidgets.clear();
dockWidgets.reserve(dockWidgetsV.size());
for (const auto &variant : dockWidgetsV) {
DockWidget::Ptr dw = DockWidget::dockWidgetForName(variant.toString());
dockWidgets.push_back(dw);
}
}
bool LayoutSaver::DockWidget::isValid() const
{
return !uniqueName.isEmpty();
}
void LayoutSaver::DockWidget::scaleSizes(const ScalingInfo &scalingInfo)
{
lastPosition.scaleSizes(scalingInfo);
}
QVariantMap LayoutSaver::DockWidget::toVariantMap() const
{
QVariantMap map;
if (!affinities.isEmpty())
map.insert(QStringLiteral("affinities"), stringListToVariant(affinities));
map.insert(QStringLiteral("uniqueName"), uniqueName);
map.insert(QStringLiteral("lastPosition"), lastPosition.toVariantMap());
return map;
}
void LayoutSaver::DockWidget::fromVariantMap(const QVariantMap &map)
{
affinities = variantToStringList(map.value(QStringLiteral("affinities")).toList());
// Compatibility hack. Old json format had a single "affinityName" instead of an "affinities" list:
const QString affinityName = map.value(QStringLiteral("affinityName")).toString();
if (!affinityName.isEmpty() && !affinities.contains(affinityName)) {
affinities.push_back(affinityName);
}
uniqueName = map.value(QStringLiteral("uniqueName")).toString();
lastPosition.fromVariantMap(map.value(QStringLiteral("lastPosition")).toMap());
}
bool LayoutSaver::FloatingWindow::isValid() const
{
if (!multiSplitterLayout.isValid())
return false;
if (!geometry.isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid geometry";
return false;
}
return true;
}
void LayoutSaver::FloatingWindow::scaleSizes(const ScalingInfo &scalingInfo)
{
scalingInfo.applyFactorsTo(/*by-ref*/geometry);
multiSplitterLayout.scaleSizes(scalingInfo);
}
QVariantMap LayoutSaver::FloatingWindow::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("multiSplitterLayout"), multiSplitterLayout.toVariantMap());
map.insert(QStringLiteral("parentIndex"), parentIndex);
map.insert(QStringLiteral("geometry"), Layouting::rectToMap(geometry));
map.insert(QStringLiteral("screenIndex"), screenIndex);
map.insert(QStringLiteral("screenSize"), Layouting::sizeToMap(screenSize));
map.insert(QStringLiteral("isVisible"), isVisible);
if (!affinities.isEmpty())
map.insert(QStringLiteral("affinityName"), stringListToVariant(affinities));
return map;
}
void LayoutSaver::FloatingWindow::fromVariantMap(const QVariantMap &map)
{
multiSplitterLayout.fromVariantMap(map.value(QStringLiteral("multiSplitterLayout")).toMap());
parentIndex = map.value(QStringLiteral("parentIndex")).toInt();
geometry = Layouting::mapToRect(map.value(QStringLiteral("geometry")).toMap());
screenIndex = map.value(QStringLiteral("screenIndex")).toInt();
screenSize = Layouting::mapToSize(map.value(QStringLiteral("screenSize")).toMap());
isVisible = map.value(QStringLiteral("isVisible")).toBool();
affinities = variantToStringList(map.value(QStringLiteral("affinities")).toList());
// Compatibility hack. Old json format had a single "affinityName" instead of an "affinities" list:
const QString affinityName = map.value(QStringLiteral("affinityName")).toString();
if (!affinityName.isEmpty() && !affinities.contains(affinityName)) {
affinities.push_back(affinityName);
}
}
bool LayoutSaver::MainWindow::isValid() const
{
if (!multiSplitterLayout.isValid())
return false;
if (options != MainWindowOption_None && options != MainWindowOption_HasCentralFrame) {
qWarning() << Q_FUNC_INFO << "Invalid option" << options;
return false;
}
return true;
}
void LayoutSaver::MainWindow::scaleSizes()
{
if (scalingInfo.isValid()) {
// Doesn't happen, it's called only once
Q_ASSERT(false);
return;
}
scalingInfo = ScalingInfo(uniqueName, geometry);
if (scalingInfo.isValid())
multiSplitterLayout.scaleSizes(scalingInfo);
}
QVariantMap LayoutSaver::MainWindow::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("options"), int(options));
map.insert(QStringLiteral("multiSplitterLayout"), multiSplitterLayout.toVariantMap());
map.insert(QStringLiteral("uniqueName"), uniqueName);
map.insert(QStringLiteral("geometry"), Layouting::rectToMap(geometry));
map.insert(QStringLiteral("screenIndex"), screenIndex);
map.insert(QStringLiteral("screenSize"), Layouting::sizeToMap(screenSize));
map.insert(QStringLiteral("isVisible"), isVisible);
map.insert(QStringLiteral("affinities"), stringListToVariant(affinities));
return map;
}
void LayoutSaver::MainWindow::fromVariantMap(const QVariantMap &map)
{
options = KDDockWidgets::MainWindowOptions(map.value(QStringLiteral("options")).toInt());
multiSplitterLayout.fromVariantMap(map.value(QStringLiteral("multiSplitterLayout")).toMap());
uniqueName = map.value(QStringLiteral("uniqueName")).toString();
geometry = Layouting::mapToRect(map.value(QStringLiteral("geometry")).toMap());
screenIndex = map.value(QStringLiteral("screenIndex")).toInt();
screenSize = Layouting::mapToSize(map.value(QStringLiteral("screenSize")).toMap());
isVisible = map.value(QStringLiteral("isVisible")).toBool();
affinities = variantToStringList(map.value(QStringLiteral("affinities")).toList());
// Compatibility hack. Old json format had a single "affinityName" instead of an "affinities" list:
const QString affinityName = map.value(QStringLiteral("affinityName")).toString();
if (!affinityName.isEmpty() && !affinities.contains(affinityName)) {
affinities.push_back(affinityName);
}
}
bool LayoutSaver::MultiSplitterLayout::isValid() const
{
if (layout.isEmpty())
return false;
/*if (!size.isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid size";
return false;
}*/
return true;
}
void LayoutSaver::MultiSplitterLayout::scaleSizes(const ScalingInfo &)
{
// scalingInfo.applyFactorsTo(/*by-ref*/size);
//for (LayoutSaver::Item &item : items) TODO
// item.scaleSizes(scalingInfo);
}
QVariantMap LayoutSaver::MultiSplitterLayout::toVariantMap() const
{
QVariantMap result;
result.insert(QStringLiteral("layout"), layout);
QVariantMap framesV;
for (auto &frame : frames)
framesV.insert(frame.id, frame.toVariantMap());
result.insert(QStringLiteral("frames"), framesV);
return result;
}
void LayoutSaver::MultiSplitterLayout::fromVariantMap(const QVariantMap &map)
{
layout = map.value(QStringLiteral("layout")).toMap();
const QVariantMap framesV = map.value(QStringLiteral("frames")).toMap();
frames.clear();
for (const QVariant &frameV : framesV) {
LayoutSaver::Frame frame;
frame.fromVariantMap(frameV.toMap());
frames.insert(frame.id, frame);
}
}
void LayoutSaver::Position::scaleSizes(const ScalingInfo &scalingInfo)
{
scalingInfo.applyFactorsTo(/*by-ref*/lastFloatingGeometry);
}
QVariantMap LayoutSaver::Position::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("lastFloatingGeometry"), Layouting::rectToMap(lastFloatingGeometry));
map.insert(QStringLiteral("tabIndex"), tabIndex);
map.insert(QStringLiteral("wasFloating"), wasFloating);
map.insert(QStringLiteral("placeholders"), toVariantList<LayoutSaver::Placeholder>(placeholders));
return map;
}
void LayoutSaver::Position::fromVariantMap(const QVariantMap &map)
{
lastFloatingGeometry = Layouting::mapToRect(map.value(QStringLiteral("lastFloatingGeometry")).toMap());
tabIndex = map.value(QStringLiteral("tabIndex")).toInt();
wasFloating = map.value(QStringLiteral("wasFloating")).toBool();
placeholders = fromVariantList<LayoutSaver::Placeholder>(map.value(QStringLiteral("placeholders")).toList());
}
QVariantMap LayoutSaver::ScreenInfo::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("index"), index);
map.insert(QStringLiteral("geometry"), Layouting::rectToMap(geometry));
map.insert(QStringLiteral("name"), name);
map.insert(QStringLiteral("devicePixelRatio"), devicePixelRatio);
return map;
}
void LayoutSaver::ScreenInfo::fromVariantMap(const QVariantMap &map)
{
index = map.value(QStringLiteral("index")).toInt();
geometry = Layouting::mapToRect(map.value(QStringLiteral("geometry")).toMap());
name = map.value(QStringLiteral("name")).toString();
devicePixelRatio = map.value(QStringLiteral("devicePixelRatio")).toDouble();
}
QVariantMap LayoutSaver::Placeholder::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("isFloatingWindow"), isFloatingWindow);
map.insert(QStringLiteral("itemIndex"), itemIndex);
if (isFloatingWindow)
map.insert(QStringLiteral("indexOfFloatingWindow"), indexOfFloatingWindow);
else
map.insert(QStringLiteral("mainWindowUniqueName"), mainWindowUniqueName);
return map;
}
void LayoutSaver::Placeholder::fromVariantMap(const QVariantMap &map)
{
isFloatingWindow = map.value(QStringLiteral("isFloatingWindow")).toBool();
indexOfFloatingWindow = map.value(QStringLiteral("indexOfFloatingWindow"), -1).toInt();
itemIndex = map.value(QStringLiteral("itemIndex")).toInt();
mainWindowUniqueName = map.value(QStringLiteral("mainWindowUniqueName")).toString();
}
LayoutSaver::ScalingInfo::ScalingInfo(const QString &mainWindowId, QRect savedMainWindowGeo)
{
auto mainWindow = DockRegistry::self()->mainWindowByName(mainWindowId);
if (!mainWindow) {
qWarning() << Q_FUNC_INFO << "Failed to find main window with name" << mainWindowName;
return;
}
if (!savedMainWindowGeo.isValid() || savedMainWindowGeo.isNull()) {
qWarning() << Q_FUNC_INFO << "Invalid saved main window geometry" << savedMainWindowGeo;
return;
}
if (!mainWindow->geometry().isValid() || mainWindow->geometry().isNull()) {
qWarning() << Q_FUNC_INFO << "Invalid main window geometry" << mainWindow->geometry();
return;
}
this->mainWindowName = mainWindowId;
this->savedMainWindowGeometry = savedMainWindowGeo;
realMainWindowGeometry = mainWindow->window()->geometry(); // window() as our main window might be embedded
widthFactor = double(realMainWindowGeometry.width()) / savedMainWindowGeo.width();
heightFactor = double(realMainWindowGeometry.height()) / savedMainWindowGeo.height();
}
void LayoutSaver::ScalingInfo::translatePos(QPoint &pt) const
{
const int deltaX = pt.x() - savedMainWindowGeometry.x();
const int deltaY = pt.y() - savedMainWindowGeometry.y();
const double newDeltaX = deltaX * widthFactor;
const double newDeltaY = deltaY * heightFactor;
pt.setX(qCeil(savedMainWindowGeometry.x() + newDeltaX));
pt.setY(qCeil(savedMainWindowGeometry.y() + newDeltaY));
}
void LayoutSaver::ScalingInfo::applyFactorsTo(QPoint &pt) const
{
pt.setX(qCeil(pt.x() * widthFactor));
pt.setY(qCeil(pt.y() * heightFactor));
}
void LayoutSaver::ScalingInfo::applyFactorsTo(QSize &sz) const
{
sz.setWidth(int(widthFactor * sz.width()));
sz.setHeight(int(heightFactor * sz.height()));
}
void LayoutSaver::ScalingInfo::applyFactorsTo(QRect &rect) const
{
if (rect.isEmpty())
return;
QPoint pos = rect.topLeft();
QSize size = rect.size();
applyFactorsTo(/*by-ref*/size);
applyFactorsTo(/*by-ref*/pos);
rect.moveTopLeft(pos);
rect.setSize(size);
}