diff --git a/examples/dockwidgets/MyMainWindow.cpp b/examples/dockwidgets/MyMainWindow.cpp index 9bfc6eb7..829ad884 100644 --- a/examples/dockwidgets/MyMainWindow.cpp +++ b/examples/dockwidgets/MyMainWindow.cpp @@ -50,9 +50,11 @@ static MyWidget *newMyWidget() } MyMainWindow::MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options, - bool dockWidget0IsNonClosable, const QString &affinityName, QWidget *parent) + bool dockWidget0IsNonClosable, bool restoreIsRelative, + const QString &affinityName, QWidget *parent) : MainWindow(uniqueName, options, parent) , m_dockWidget0IsNonClosable(dockWidget0IsNonClosable) + , m_restoreIsRelative(restoreIsRelative) { // qApp->installEventFilter(this); @@ -85,7 +87,10 @@ MyMainWindow::MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowO }); auto restoreLayoutAction = fileMenu->addAction(QStringLiteral("Restore Layout")); - connect(restoreLayoutAction, &QAction::triggered, this, [] { + connect(restoreLayoutAction, &QAction::triggered, this, [this] { + KDDockWidgets::RestoreOptions options = KDDockWidgets::RestoreOption_None; + if (m_restoreIsRelative) + options |= KDDockWidgets::RestoreOption_RelativeToMainWindow; KDDockWidgets::LayoutSaver saver; saver.restoreFromFile(QStringLiteral("mylayout.json")); }); diff --git a/examples/dockwidgets/MyMainWindow.h b/examples/dockwidgets/MyMainWindow.h index e75c016b..b39a6115 100644 --- a/examples/dockwidgets/MyMainWindow.h +++ b/examples/dockwidgets/MyMainWindow.h @@ -27,7 +27,7 @@ class MyMainWindow : public KDDockWidgets::MainWindow Q_OBJECT public: explicit MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options, - bool dockWidget0IsNonClosable, + bool dockWidget0IsNonClosable, bool restoreIsRelative, const QString &affinityName = {}, // Usually not needed. Just here to show the feature. QWidget *parent = nullptr); @@ -36,4 +36,5 @@ private: KDDockWidgets::DockWidgetBase* newDockWidget(); QMenu *m_toggleMenu = nullptr; const bool m_dockWidget0IsNonClosable; + const bool m_restoreIsRelative; }; diff --git a/examples/dockwidgets/main.cpp b/examples/dockwidgets/main.cpp index 29b415a3..324e603d 100644 --- a/examples/dockwidgets/main.cpp +++ b/examples/dockwidgets/main.cpp @@ -70,6 +70,9 @@ int main(int argc, char **argv) QCommandLineOption nonClosableDockWidget("n", QCoreApplication::translate("main", "DockWidget #0 will be non-closable")); parser.addOption(nonClosableDockWidget); + QCommandLineOption relativeRestore("s", QCoreApplication::translate("main", "Don't restore main window geometry, restore dock widgets in relative sizes")); + parser.addOption(relativeRestore); + #if defined(DOCKS_DEVELOPER_MODE) QCommandLineOption noCentralFrame("c", QCoreApplication::translate("main", "No central frame")); parser.addOption(noCentralFrame); @@ -111,8 +114,9 @@ int main(int argc, char **argv) KDDockWidgets::Config::self().setFlags(flags); const bool nonClosableDockWidget0 = parser.isSet(nonClosableDockWidget); + const bool restoreIsRelative = parser.isSet(relativeRestore); - MyMainWindow mainWindow(QStringLiteral("MyMainWindow"), options, nonClosableDockWidget0); + MyMainWindow mainWindow(QStringLiteral("MyMainWindow"), options, nonClosableDockWidget0, restoreIsRelative); mainWindow.setWindowTitle("Main Window 1"); mainWindow.resize(1200, 1200); mainWindow.show(); @@ -124,7 +128,7 @@ int main(int argc, char **argv) : QString(); auto mainWindow2 = new MyMainWindow(QStringLiteral("MyMainWindow-2"), options, - nonClosableDockWidget0, affinity); + nonClosableDockWidget0, restoreIsRelative, affinity); if (affinity.isEmpty()) mainWindow2->setWindowTitle("Main Window 2"); else diff --git a/src/KDDockWidgets.h b/src/KDDockWidgets.h index 364cd680..2dcaa7ad 100644 --- a/src/KDDockWidgets.h +++ b/src/KDDockWidgets.h @@ -59,6 +59,13 @@ namespace KDDockWidgets }; Q_DECLARE_FLAGS(FrameOptions, FrameOption) + enum RestoreOption { + RestoreOption_None = 0, + RestoreOption_RelativeToMainWindow = 1, ///< Skips restoring the main window geometry and the restored dock widgets will use relative sizing. + ///< Loading layouts won't change the main window geometry and just use whatever the user has at the moment. + }; + Q_DECLARE_FLAGS(RestoreOptions, RestoreOption) + ///@internal inline Location oppositeLocation(Location loc) { diff --git a/src/LayoutSaver.cpp b/src/LayoutSaver.cpp index ff13707f..0d0773b1 100644 --- a/src/LayoutSaver.cpp +++ b/src/LayoutSaver.cpp @@ -38,6 +38,7 @@ #include "multisplitter/Item_p.h" #include "FrameworkWidgetFactory.h" +#include #include #include #include @@ -102,8 +103,9 @@ public: Q_DISABLE_COPY(RAIIIsRestoring) }; - Private() + Private(RestoreOptions options) : m_dockRegistry(DockRegistry::self()) + , m_restoreOptions(options) { } @@ -114,13 +116,14 @@ public: std::unique_ptr settings() const; DockRegistry *const m_dockRegistry; + const RestoreOptions m_restoreOptions; static bool s_restoreInProgress; }; bool LayoutSaver::Private::s_restoreInProgress = false; LayoutSaver::LayoutSaver() - : d(new Private()) + : d(new Private(RestoreOption_None)) { } @@ -153,6 +156,7 @@ bool LayoutSaver::restoreFromFile(const QString &jsonFilename) const QByteArray data = f.readAll(); const bool result = restoreLayout(data); + return result; } @@ -230,6 +234,9 @@ bool LayoutSaver::restoreLayout(const QByteArray &data) return false; } + if (d->m_restoreOptions & RestoreOption_RelativeToMainWindow) + layout.scaleSizes(); + // Hide all dockwidgets and unparent them from any layout before starting restore d->m_dockRegistry->clear(/*deleteStaticAnchors=*/true); @@ -241,7 +248,8 @@ bool LayoutSaver::restoreLayout(const QByteArray &data) return false; } - d->deserializeWindowGeometry(mw, mainWindow->window()); // window() as the MainWindow can be embedded + if (!(d->m_restoreOptions & RestoreOption_RelativeToMainWindow)) + d->deserializeWindowGeometry(mw, mainWindow->window()); // window(), as the MainWindow can be embedded if (!mainWindow->deserialize(mw)) return false; @@ -420,6 +428,35 @@ void LayoutSaver::Layout::fromVariantMap(const QVariantMap &map) screenInfo = fromVariantList(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); + } + + 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(mainWindows.constFirst().scalingInfo); + } +} + +LayoutSaver::MainWindow LayoutSaver::Layout::mainWindowForIndex(int index) const +{ + if (index < 0 || index >= mainWindows.size()) + return {}; + + return mainWindows.at(index); +} + bool LayoutSaver::Item::isValid(const LayoutSaver::MultiSplitterLayout &layout) const { if (!frame.isValid()) @@ -440,6 +477,13 @@ bool LayoutSaver::Item::isValid(const LayoutSaver::MultiSplitterLayout &layout) return true; } +void LayoutSaver::Item::scaleSizes(const ScalingInfo &scalingInfo) +{ + scalingInfo.applyFactorsTo(geometry); + if (!frame.isNull) + frame.scaleSizes(scalingInfo); +} + QVariantMap LayoutSaver::Item::toVariantMap() const { QVariantMap map; @@ -498,13 +542,17 @@ bool LayoutSaver::Frame::isValid() const return true; } +void LayoutSaver::Frame::scaleSizes(const ScalingInfo &scalingInfo) +{ + scalingInfo.applyFactorsTo(geometry); +} + QVariantMap LayoutSaver::Frame::toVariantMap() const { QVariantMap map; map.insert(QStringLiteral("isNull"), isNull); map.insert(QStringLiteral("objectName"), objectName); map.insert(QStringLiteral("geometry"), rectToMap(geometry)); - map.insert(QStringLiteral("layoutSize"), sizeToMap(layoutSize)); map.insert(QStringLiteral("options"), options); map.insert(QStringLiteral("currentTabIndex"), currentTabIndex); @@ -524,7 +572,6 @@ void LayoutSaver::Frame::fromVariantMap(const QVariantMap &map) isNull = map.value(QStringLiteral("isNull")).toBool(); objectName = map.value(QStringLiteral("objectName")).toString(); geometry = mapToRect(map.value(QStringLiteral("geometry")).toMap()); - layoutSize = mapToSize(map.value(QStringLiteral("layoutSize")).toMap()); options = map.value(QStringLiteral("options")).toUInt(); currentTabIndex = map.value(QStringLiteral("currentTabIndex")).toInt(); @@ -543,6 +590,11 @@ 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; @@ -654,6 +706,22 @@ void LayoutSaver::Anchor::fromVariantMap(const QVariantMap &map) side2Items.push_back(v.toInt()); } +void LayoutSaver::Anchor::scaleSizes(const ScalingInfo &scalingInfo) +{ + const QPoint pos = geometry.topLeft(); + + if (isVertical()) { + geometry.moveLeft(int(pos.x() * scalingInfo.widthFactor)); + } else { + geometry.moveTop(int(pos.y() * scalingInfo.heightFactor)); + } +} + +bool LayoutSaver::Anchor::isVertical() const +{ + return orientation == Qt::Vertical; +} + bool LayoutSaver::FloatingWindow::isValid() const { if (!multiSplitterLayout.isValid()) @@ -667,6 +735,12 @@ bool LayoutSaver::FloatingWindow::isValid() const return true; } +void LayoutSaver::FloatingWindow::scaleSizes(const ScalingInfo &scalingInfo) +{ + scalingInfo.applyFactorsTo(/*by-ref*/geometry); + multiSplitterLayout.scaleSizes(scalingInfo); +} + QVariantMap LayoutSaver::FloatingWindow::toVariantMap() const { QVariantMap map; @@ -703,6 +777,20 @@ bool LayoutSaver::MainWindow::isValid() const 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; @@ -749,6 +837,15 @@ bool LayoutSaver::MultiSplitterLayout::isValid() const return true; } +void LayoutSaver::MultiSplitterLayout::scaleSizes(const ScalingInfo &scalingInfo) +{ + scalingInfo.applyFactorsTo(/*by-ref*/size); + for (LayoutSaver::Anchor &anchor : anchors) + anchor.scaleSizes(scalingInfo); + for (LayoutSaver::Item &item : items) + item.scaleSizes(scalingInfo); +} + QVariantMap LayoutSaver::MultiSplitterLayout::toVariantMap() const { QVariantMap map; @@ -769,6 +866,11 @@ void LayoutSaver::MultiSplitterLayout::fromVariantMap(const QVariantMap &map) size = mapToSize(map.value(QStringLiteral("size")).toMap()); } +void LayoutSaver::LastPosition::scaleSizes(const ScalingInfo &scalingInfo) +{ + scalingInfo.applyFactorsTo(/*by-ref*/lastFloatingGeometry); +} + QVariantMap LayoutSaver::LastPosition::toVariantMap() const { QVariantMap map; @@ -828,3 +930,64 @@ void LayoutSaver::Placeholder::fromVariantMap(const QVariantMap &map) 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->geometry(); + 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(qCeil(widthFactor * sz.width())); + sz.setHeight(qCeil(heightFactor * sz.height())); +} + +void LayoutSaver::ScalingInfo::applyFactorsTo(QRect &rect) const +{ + QPoint pos = rect.topLeft(); + QSize size = rect.size(); + + applyFactorsTo(/*by-ref*/size); + applyFactorsTo(/*by-ref*/pos); + + rect.moveTopLeft(pos); + rect.setSize(size); +} diff --git a/src/LayoutSaver.h b/src/LayoutSaver.h index 30c078f4..c5a6806e 100644 --- a/src/LayoutSaver.h +++ b/src/LayoutSaver.h @@ -30,6 +30,8 @@ #include "docks_export.h" +#include "KDDockWidgets.h" + QT_BEGIN_NAMESPACE class QByteArray; QT_END_NAMESPACE @@ -101,6 +103,7 @@ public: struct Anchor; struct Frame; struct Placeholder; + struct ScalingInfo; struct ScreenInfo; private: diff --git a/src/LayoutSaver_p.h b/src/LayoutSaver_p.h index 69b20288..4c18cc7c 100644 --- a/src/LayoutSaver_p.h +++ b/src/LayoutSaver_p.h @@ -39,7 +39,7 @@ /** * Bump whenever the format changes, so we can still load old layouts. * version 1: Initial version - * version 2: Introduced Frame::layoutSize, MainWindow::screenSize and FloatingWindow::screenSize + * version 2: Introduced MainWindow::screenSize and FloatingWindow::screenSize */ #define KDDOCKWIDGETS_SERIALIZATION_VERSION 2 @@ -85,6 +85,28 @@ struct LayoutSaver::Placeholder QString mainWindowUniqueName; }; +///@brief contains info about how a main window is scaled. +///Used for RestoreOption_RelativeToMainWindow +struct LayoutSaver::ScalingInfo +{ + ScalingInfo() = default; + explicit ScalingInfo(const QString &mainWindowId, QRect savedMainWindowGeo); + + bool isValid() const { + return heightFactor > 0 && widthFactor > 0; + } + + void translatePos(QPoint &) const; + void applyFactorsTo(QPoint &) const; + void applyFactorsTo(QSize &) const; + void applyFactorsTo(QRect &) const; + + QString mainWindowName; + QRect savedMainWindowGeometry; + QRect realMainWindowGeometry; + double heightFactor = -1; + double widthFactor = -1; +}; struct LayoutSaver::LastPosition { @@ -93,6 +115,9 @@ struct LayoutSaver::LastPosition bool wasFloating; LayoutSaver::Placeholder::List placeholders; + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &scalingInfo); + QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); }; @@ -106,6 +131,9 @@ struct DOCKS_EXPORT LayoutSaver::DockWidget bool isValid() const; + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &scalingInfo); + static Ptr dockWidgetForName(const QString &name) { auto dw = s_dockWidgets.value(name); @@ -153,13 +181,15 @@ struct LayoutSaver::Frame { bool isValid() const; + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &scalingInfo); + QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); bool isNull = true; QString objectName; QRect geometry; - QSize layoutSize; // for relative-size restoring unsigned int options; int currentTabIndex; @@ -171,6 +201,8 @@ struct LayoutSaver::Item typedef QVector List; bool isValid(const MultiSplitterLayout &) const; + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &scalingInfo); QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); @@ -194,6 +226,9 @@ struct LayoutSaver::Anchor QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); + void scaleSizes(const ScalingInfo &); + + bool isVertical() const; QString objectName; QRect geometry; @@ -209,6 +244,8 @@ struct LayoutSaver::Anchor struct LayoutSaver::MultiSplitterLayout { bool isValid() const; + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &scalingInfo); QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); @@ -224,6 +261,10 @@ struct LayoutSaver::FloatingWindow typedef QVector List; bool isValid() const; + + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(const ScalingInfo &); + QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); @@ -241,6 +282,10 @@ public: typedef QVector List; bool isValid() const; + + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(); + QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); @@ -251,8 +296,12 @@ public: int screenIndex; QSize screenSize; // for relative-size restoring bool isVisible; + + ScalingInfo scalingInfo; }; +///@brief we serialize some info about screens, so eventually we can make restore smarter when switching screens +///Not used currently, but nice to have in the json already struct LayoutSaver::ScreenInfo { typedef QVector List; @@ -289,16 +338,21 @@ public: } bool isValid() const; - bool fillFrom(const QByteArray &serialized); + bool fillFrom(const QByteArray &serialized); QByteArray toJson() const; bool fromJson(const QByteArray &jsonData); QVariantMap toVariantMap() const; void fromVariantMap(const QVariantMap &map); + /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. + void scaleSizes(); + friend QDataStream &operator>>(QDataStream &ds, LayoutSaver::Frame *frame); static LayoutSaver::Layout* s_currentLayoutBeingRestored; + LayoutSaver::MainWindow mainWindowForIndex(int index) const; + int serializationVersion = KDDOCKWIDGETS_SERIALIZATION_VERSION; LayoutSaver::MainWindow::List mainWindows; LayoutSaver::FloatingWindow::List floatingWindows; @@ -361,7 +415,8 @@ inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Frame *frame) ds >> frame->geometry; if (LayoutSaver::Layout::s_currentLayoutBeingRestored->serializationVersion >= 2) { - ds >> frame->layoutSize; + QSize sz; + ds >> sz; // deprecated field, just discard } ds >> frame->options; diff --git a/src/private/Frame.cpp b/src/private/Frame.cpp index e6eea554..8392da77 100644 --- a/src/private/Frame.cpp +++ b/src/private/Frame.cpp @@ -519,9 +519,6 @@ LayoutSaver::Frame Frame::serialize() const frame.options = options(); frame.currentTabIndex = currentTabIndex(); - if (m_dropArea) - frame.layoutSize = m_dropArea->multiSplitterLayout()->size(); - for (DockWidgetBase *dock : docks) frame.dockWidgets.push_back(dock->serialize());