diff --git a/examples/dockwidgets/MyMainWindow.cpp b/examples/dockwidgets/MyMainWindow.cpp index d7528323..cd85f1b3 100644 --- a/examples/dockwidgets/MyMainWindow.cpp +++ b/examples/dockwidgets/MyMainWindow.cpp @@ -49,8 +49,9 @@ static MyWidget *newMyWidget() } } -MyMainWindow::MyMainWindow(KDDockWidgets::MainWindowOptions options, QWidget *parent) - : MainWindow(QStringLiteral("MyMainWindow"), options, parent) +MyMainWindow::MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options, + const QString &affinityName, QWidget *parent) + : MainWindow(uniqueName, options, parent) { // qApp->installEventFilter(this); @@ -88,6 +89,7 @@ MyMainWindow::MyMainWindow(KDDockWidgets::MainWindowOptions options, QWidget *pa saver.restoreFromDisk(); }); + setAffinityName(affinityName); createDockWidgets(); } @@ -124,6 +126,7 @@ KDDockWidgets::DockWidgetBase *MyMainWindow::newDockWidget() { static int count = 0; auto dock = new KDDockWidgets::DockWidget(QStringLiteral("DockWidget #%1").arg(count)); + dock->setAffinityName(affinityName()); // optional, just to show the feature. Pass -mi to the example to see incompatible dock widgets if (count == 1) dock->setIcon(QIcon::fromTheme(QStringLiteral("mail-message"))); diff --git a/examples/dockwidgets/MyMainWindow.h b/examples/dockwidgets/MyMainWindow.h index bbbd0dec..1d002321 100644 --- a/examples/dockwidgets/MyMainWindow.h +++ b/examples/dockwidgets/MyMainWindow.h @@ -26,7 +26,9 @@ class MyMainWindow : public KDDockWidgets::MainWindow { Q_OBJECT public: - explicit MyMainWindow(KDDockWidgets::MainWindowOptions options, QWidget *parent = nullptr); + explicit MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options, + const QString &affinityName = {}, // Usually not needed. Just here to show the feature. + QWidget *parent = nullptr); private: void createDockWidgets(); diff --git a/examples/dockwidgets/main.cpp b/examples/dockwidgets/main.cpp index 828d1260..af3a668e 100644 --- a/examples/dockwidgets/main.cpp +++ b/examples/dockwidgets/main.cpp @@ -58,6 +58,12 @@ int main(int argc, char **argv) QCommandLineOption lazyResizeOption("l", QCoreApplication::translate("main", "Use lazy resize")); parser.addOption(lazyResizeOption); + QCommandLineOption multipleMainWindows("m", QCoreApplication::translate("main", "Shows two multiple main windows")); + parser.addOption(multipleMainWindows); + + QCommandLineOption incompatibleMainWindows("i", QCoreApplication::translate("main", "Only usable with -m. Make the two main windows incompatible with each other. (Illustrates (MainWindowBase::setAffinityName))")); + parser.addOption(incompatibleMainWindows); + #if defined(DOCKS_DEVELOPER_MODE) QCommandLineOption noCentralFrame("c", QCoreApplication::translate("main", "No central frame")); parser.addOption(noCentralFrame); @@ -88,11 +94,33 @@ int main(int argc, char **argv) if (parser.isSet(lazyResizeOption)) flags |= KDDockWidgets::Config::Flag_LazyResize; + if (parser.isSet(incompatibleMainWindows) && !parser.isSet(multipleMainWindows)) { + qWarning() << "Error: Argument -i requires -m"; + return 1; + } + KDDockWidgets::Config::self().setFlags(flags); - MyMainWindow mainWindow(options); + MyMainWindow mainWindow(QStringLiteral("MyMainWindow"), options); + mainWindow.setWindowTitle("Main Window 1"); mainWindow.resize(1200, 1200); mainWindow.show(); + if (parser.isSet(multipleMainWindows)) { + // By default a dock widget can dock into any main window. + // By setting an affinity name we can prevent that. Dock widgets of different affinities are incompatible. + const QString affinity = parser.isSet(incompatibleMainWindows) ? QStringLiteral("affinity1") + : QString(); + + auto mainWindow2 = new MyMainWindow(QStringLiteral("MyMainWindow-2"), options, affinity); + if (affinity.isEmpty()) + mainWindow2->setWindowTitle("Main Window 2"); + else + mainWindow2->setWindowTitle("Main Window 2 (different affinity)"); + + mainWindow2->resize(1200, 1200); + mainWindow2->show(); + } + return app.exec(); } diff --git a/src/DockWidgetBase.cpp b/src/DockWidgetBase.cpp index 36349185..e2c94ef0 100644 --- a/src/DockWidgetBase.cpp +++ b/src/DockWidgetBase.cpp @@ -99,6 +99,7 @@ public: void saveTabIndex(); const QString name; + QString affinityName; QString title; QIcon icon; QWidget *widget = nullptr; @@ -143,6 +144,12 @@ void DockWidgetBase::addDockWidgetAsTab(DockWidgetBase *other) return; } + if (other->affinityName() != affinityName()) { + qWarning() << Q_FUNC_INFO << "Refusing to dock widget with incompatible affinity." + << other->affinityName() << affinityName(); + return; + } + Frame *frame = this->frame(); if (frame) { @@ -171,6 +178,12 @@ void DockWidgetBase::addDockWidgetToContainingWindow(DockWidgetBase *other, Loca return; } + if (other->affinityName() != affinityName()) { + qWarning() << Q_FUNC_INFO << "Refusing to dock widget with incompatible affinity." + << other->affinityName() << affinityName(); + return; + } + if (isWindow()) morphIntoFloatingWindow(); @@ -325,6 +338,26 @@ bool DockWidgetBase::isOpen() const return d->toggleAction->isChecked(); } +QString DockWidgetBase::affinityName() const +{ + return d->affinityName; +} + +void DockWidgetBase::setAffinityName(const QString &name) +{ + if (d->affinityName == name) + return; + + if (!d->affinityName.isEmpty()) { + qWarning() << Q_FUNC_INFO + << "Affinity is already set, refusing to change." + << "Submit a feature request with a good justification."; + return; + } + + d->affinityName = name; +} + FloatingWindow *DockWidgetBase::morphIntoFloatingWindow() { qCDebug(creation) << "DockWidget::morphIntoFloatingWindow() this=" << this diff --git a/src/DockWidgetBase.h b/src/DockWidgetBase.h index a5d6c9c5..f8946e3d 100644 --- a/src/DockWidgetBase.h +++ b/src/DockWidgetBase.h @@ -51,6 +51,7 @@ class DockRegistry; class LayoutSaver; class TabWidget; class TitleBar; +class MainWindowBase; /** * @brief The DockWidget base-class. DockWidget and DockWidgetBase are only @@ -229,6 +230,30 @@ public: */ bool isOpen() const; + /** + * @brief Sets the affinity name. Dock widgets can only dock into dock widgets of the same affinity. + * + * By default the affinity is empty and a dock widget can dock into any main window and into any + * floating window. Usually you won't ever need to call + * this function, unless you have requirements where certain dock widgets can only dock into + * certain other dock widgets and main windows. @sa MainWindowBase::setAffinityName(). + * + * Note: Call this function right after creating your dock widget, before adding to a main window and + * before restoring any layout. + * + * Note: Currently you can only call this function once, to keep the code simple and avoid + * edge cases. This will only be changed if a good use case comes up that requires changing + * affinities multiple times. + * + * @p name The affinity name. + */ + void setAffinityName(const QString &name); + + /** + * @brief Returns the affinity name. Empty by default. + */ + QString affinityName() const; + Q_SIGNALS: ///@brief signal emitted when the parent changed void parentChanged(); diff --git a/src/MainWindowBase.cpp b/src/MainWindowBase.cpp index 52c95ab9..4c42364b 100644 --- a/src/MainWindowBase.cpp +++ b/src/MainWindowBase.cpp @@ -51,6 +51,7 @@ public: } QString name; + QString affinityName; const MainWindowOptions m_options; }; @@ -73,6 +74,12 @@ void MainWindowBase::addDockWidgetAsTab(DockWidgetBase *widget) Q_ASSERT(widget); qCDebug(addwidget) << Q_FUNC_INFO << widget; + if (widget->affinityName() != affinityName()) { + qWarning() << Q_FUNC_INFO << "Refusing to dock widget with incompatible affinity." + << widget->affinityName() << affinityName(); + return; + } + if (d->supportsCentralFrame()) { dropArea()->m_centralFrame->addWidget(widget); } else { @@ -82,7 +89,7 @@ void MainWindowBase::addDockWidgetAsTab(DockWidgetBase *widget) void MainWindowBase::addDockWidget(DockWidgetBase *dw, Location location, DockWidgetBase *relativeTo, AddingOption option) { - dropArea()->addDockWidget(dw, location, relativeTo, option); + dropArea()->addDockWidget(dw, location, relativeTo, option); } QString MainWindowBase::uniqueName() const @@ -100,6 +107,26 @@ MultiSplitterLayout *MainWindowBase::multiSplitterLayout() const return dropArea()->multiSplitterLayout(); } +void MainWindowBase::setAffinityName(const QString &name) +{ + if (d->affinityName == name) + return; + + if (!d->affinityName.isEmpty()) { + qWarning() << Q_FUNC_INFO + << "Affinity is already set, refusing to change." + << "Submit a feature request with a good justification."; + return; + } + + d->affinityName = name; +} + +QString MainWindowBase::affinityName() const +{ + return d->affinityName; +} + void MainWindowBase::setUniqueName(const QString &uniqueName) { if (uniqueName.isEmpty()) diff --git a/src/MainWindowBase.h b/src/MainWindowBase.h index 111992a0..aa9a60a7 100644 --- a/src/MainWindowBase.h +++ b/src/MainWindowBase.h @@ -100,6 +100,29 @@ public: ///@brief returns the MultiSplitterLayout. MultiSplitterLayout* multiSplitterLayout() const; + /** + * @brief Sets the affinity name. Dock widgets can only dock into main windows of the same affinity. + * + * By default the affinity is empty and a dock widget can dock into any main window. Usually you + * won't ever need to call this function, unless you have requirements where certain dock widgets + * can only dock into certain main windows. @sa DockWidgetBase::setAffinityName(). + * + * Note: Call this function right after creating your main window, before docking any dock widgets + * into a main window and before restoring any layout. + * + * Note: Currently you can only call this function once, to keep the code simple and avoid + * edge cases. This will only be changed if a good use case comes up that requires changing + * affinities multiple times. + * + * @p name The affinity name. + */ + void setAffinityName(const QString &name); + + /** + * @brief Returns the affinity name. Empty by default. + */ + QString affinityName() const; + protected: void setUniqueName(const QString &uniqueName); diff --git a/src/private/DropArea.cpp b/src/private/DropArea.cpp index bac59bea..0fb850b3 100644 --- a/src/private/DropArea.cpp +++ b/src/private/DropArea.cpp @@ -26,6 +26,7 @@ #include "Config.h" #include "DropIndicatorOverlayInterface_p.h" #include "FrameworkWidgetFactory.h" +#include "MainWindowBase.h" // #include "indicators/AnimatedIndicators_p.h" #include "WindowBeingDragged_p.h" @@ -109,6 +110,9 @@ void DropArea::addDockWidget(DockWidgetBase *dw, Location location, DockWidgetBa return; } + if (!validateAffinity(dw)) + return; + Frame *frame = nullptr; Frame *relativeToFrame = relativeTo ? relativeTo->frame() : nullptr; @@ -158,8 +162,22 @@ bool DropArea::contains(DockWidgetBase *dw) const return dw->frame() && m_layout->contains(dw->frame()); } +QString DropArea::affinityName() const +{ + if (auto mw = mainWindow()) { + return mw->affinityName(); + } else if (auto fw = floatingWindow()) { + return fw->affinityName(); + } + + return QString(); +} + void DropArea::hover(FloatingWindow *floatingWindow, QPoint globalPos) { + if (!validateAffinity(floatingWindow)) + return; + Frame *frame = frameContainingPos(globalPos); // Frame is nullptr if MainWindowOption_HasCentralFrame isn't set m_dropIndicatorOverlay->setWindowBeingDragged(floatingWindow); m_dropIndicatorOverlay->setHoveredFrame(frame); @@ -220,6 +238,8 @@ bool DropArea::drop(FloatingWindow *droppedWindow, QPoint globalPos) break; case DropIndicatorOverlayInterface::DropLocation_Center: qCDebug(hovering) << "Tabbing" << droppedWindow << "into" << acceptingFrame; + if (!validateAffinity(droppedWindow)) + return false; acceptingFrame->addWidget(droppedWindow); break; @@ -241,10 +261,16 @@ bool DropArea::drop(QWidgetOrQuick *droppedWindow, KDDockWidgets::Location locat qCDebug(docking) << "DropArea::addFrame"; if (auto dock = qobject_cast(droppedWindow)) { + if (!validateAffinity(dock)) + return false; + auto frame = Config::self().frameworkWidgetFactory()->createFrame(); frame->addWidget(dock); m_layout->addWidget(frame, location, relativeTo); } else if (auto floatingWindow = qobject_cast(droppedWindow)) { + if (!validateAffinity(floatingWindow)) + return false; + m_layout->addMultiSplitter(floatingWindow->dropArea(), location, relativeTo); floatingWindow->scheduleDeleteLater(); return true; @@ -261,3 +287,16 @@ void DropArea::removeHover() m_dropIndicatorOverlay->setWindowBeingDragged(nullptr); m_dropIndicatorOverlay->setCurrentDropLocation(DropIndicatorOverlayInterface::DropLocation_None); } + +template +bool DropArea::validateAffinity(T *window) const +{ + if (window->affinityName() != affinityName()) { + // Commented the warning, so we don't warn when hovering over + //qWarning() << Q_FUNC_INFO << "Refusing to dock widget with incompatible affinity." + //<< window->affinityName() << affinityName(); + return false; + } + + return true; +} diff --git a/src/private/DropArea_p.h b/src/private/DropArea_p.h index a2850622..5ff53d04 100644 --- a/src/private/DropArea_p.h +++ b/src/private/DropArea_p.h @@ -68,13 +68,18 @@ public: bool checkSanity(MultiSplitterLayout::AnchorSanityOption o = MultiSplitterLayout::AnchorSanity_All); bool contains(DockWidgetBase *) const; + + QString affinityName() const; private: Q_DISABLE_COPY(DropArea) friend class Frame; friend class TestDocks; friend class DropIndicatorOverlayInterface; friend class AnimatedIndicators; + template + bool validateAffinity(T *) const; bool m_inDestructor = false; + QString m_affinityName; DropIndicatorOverlayInterface *m_dropIndicatorOverlay = nullptr; }; } diff --git a/src/private/FloatingWindow.cpp b/src/private/FloatingWindow.cpp index 70829d2c..24078b69 100644 --- a/src/private/FloatingWindow.cpp +++ b/src/private/FloatingWindow.cpp @@ -122,7 +122,7 @@ FloatingWindow::FloatingWindow(MainWindowBase *parent) m_layoutDestroyedConnection = connect(ms, &MultiSplitterLayout::destroyed, this, &FloatingWindow::scheduleDeleteLater); } -static MainWindowBase* hackFindParentHarder(MainWindowBase *candidateParent) +static MainWindowBase* hackFindParentHarder(Frame *frame, MainWindowBase *candidateParent) { // TODO: Using a parent helps the floating windows stay in front of the main window always. // We're not receiving the parent via ctor argument as the app can have multiple-main windows, @@ -137,10 +137,30 @@ static MainWindowBase* hackFindParentHarder(MainWindowBase *candidateParent) if (windows.isEmpty()) return nullptr; - if (windows.size() == 1) + + if (windows.size() == 1) { return windows.first(); - else { - qWarning() << Q_FUNC_INFO << "There's multiple MainWindows, not sure what to do about parenting"; + } else { + const QString affinityName = frame ? frame->affinityName() : QString(); + + if (affinityName.isEmpty()) { + + for (MainWindowBase *window : windows) { + if (window->affinityName().isEmpty()) + return window; + } + + qWarning() << Q_FUNC_INFO << "No window with empty affinity found"; + + } else { + for (MainWindowBase *window : windows) { + if (window->affinityName() == affinityName) + return window; + } + + qWarning() << Q_FUNC_INFO << "No window with affinity" << affinityName << "found"; + } + return windows.first(); } #else @@ -150,7 +170,7 @@ static MainWindowBase* hackFindParentHarder(MainWindowBase *candidateParent) } FloatingWindow::FloatingWindow(Frame *frame, MainWindowBase *parent) - : FloatingWindow(hackFindParentHarder(parent)) + : FloatingWindow(hackFindParentHarder(frame, parent)) { m_disableSetVisible = true; // Adding a widget will trigger onFrameCountChanged, which triggers a setVisible(true). @@ -310,6 +330,12 @@ void FloatingWindow::updateTitleBarVisibility() m_titleBar->setVisible(visible); } +QString FloatingWindow::affinityName() const +{ + auto frames = this->frames(); + return frames.isEmpty() ? QString() : frames.constFirst()->affinityName(); +} + void FloatingWindow::updateTitleAndIcon() { QString title; diff --git a/src/private/FloatingWindow_p.h b/src/private/FloatingWindow_p.h index a803f093..6f555758 100644 --- a/src/private/FloatingWindow_p.h +++ b/src/private/FloatingWindow_p.h @@ -109,6 +109,8 @@ public: void updateTitleAndIcon(); void updateTitleBarVisibility(); + QString affinityName() const; + Q_SIGNALS: void numFramesChanged(); protected: diff --git a/src/private/Frame.cpp b/src/private/Frame.cpp index 00e5b890..e744f31e 100644 --- a/src/private/Frame.cpp +++ b/src/private/Frame.cpp @@ -405,6 +405,15 @@ bool Frame::hasTabsVisible() const return alwaysShowsTabs() || dockWidgetCount() > 1; } +QString Frame::affinityName() const +{ + if (isEmpty()) { + return {}; + } else { + return dockWidgetAt(0)->affinityName(); + } +} + DockWidgetBase *Frame::dockWidgetAt(int index) const { return qobject_cast(m_tabWidget->dockwidgetAt(index)); diff --git a/src/private/Frame_p.h b/src/private/Frame_p.h index cd3d3c6d..d42ba3d3 100644 --- a/src/private/Frame_p.h +++ b/src/private/Frame_p.h @@ -202,6 +202,8 @@ public: **/ bool hasTabsVisible() const; + QString affinityName() const; + Q_SIGNALS: void currentDockWidgetChanged(KDDockWidgets::DockWidgetBase *); void numDockWidgetsChanged();