diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17304c52..80eea54f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,8 +41,6 @@ set(DOCKSLIBS_SRCS private/LayoutWidget_p.h private/MDILayoutWidget.cpp private/MDILayoutWidget_p.h - private/MultiSplitter.cpp - private/MultiSplitter_p.h private/Position.cpp private/Position_p.h private/DropIndicatorOverlayInterface.cpp @@ -179,7 +177,6 @@ set(DOCKS_INSTALLABLE_PRIVATE_INCLUDES # private/FloatingWindow_p.h # private/Frame_p.h private/LayoutSaver_p.h - private/MultiSplitter_p.h private/LayoutWidget_p.h # private/SideBar_p.h private/TitleBar_p.h diff --git a/src/KDDockWidgets.h b/src/KDDockWidgets.h index 28077305..f1536a94 100644 --- a/src/KDDockWidgets.h +++ b/src/KDDockWidgets.h @@ -38,7 +38,6 @@ class ItemBoxContainer; namespace KDDockWidgets { DOCKS_EXPORT Q_NAMESPACE -class MultiSplitter; namespace Controllers { class DropArea; @@ -175,7 +174,6 @@ struct InitialOption private: friend class Layouting::Item; friend class Layouting::ItemBoxContainer; - friend class KDDockWidgets::MultiSplitter; friend class KDDockWidgets::Controllers::DropArea; InitialOption(DefaultSizeMode mode) diff --git a/src/View.cpp b/src/View.cpp index 4f59c922..b0153a62 100644 --- a/src/View.cpp +++ b/src/View.cpp @@ -18,8 +18,7 @@ #include "controllers/TitleBar.h" #include "controllers/TabBar.h" #include "controllers/MainWindow.h" - -#include "private/MultiSplitter_p.h" +#include "controllers/DropArea.h" #include // TODOv2 remove @@ -242,10 +241,10 @@ Controllers::MainWindow *View::asMainWindowController() const return nullptr; } -MultiSplitter *View::asMultiSplitterView() +Controllers::DropArea *View::asMultiSplitterView() { if (is(Type::MultiSplitter)) - return static_cast(this); + return static_cast(this); return nullptr; } diff --git a/src/View.h b/src/View.h index c13bceca..a620e0df 100644 --- a/src/View.h +++ b/src/View.h @@ -36,10 +36,10 @@ namespace KDDockWidgets { class ViewWrapper; class Controller; -class MultiSplitter; class Window; namespace Controllers { +class DropArea; class DockWidget; class FloatingWindow; class Frame; @@ -271,7 +271,7 @@ public: Controllers::Stack *asStackController() const; Controllers::DockWidget *asDockWidgetController() const; Controllers::MainWindow *asMainWindowController() const; - MultiSplitter *asMultiSplitterView(); + Controllers::DropArea *asMultiSplitterView(); /// @brief returns whether this view is inside the specified window bool isInWindow(std::shared_ptr window) const; diff --git a/src/controllers/DockWidget.h b/src/controllers/DockWidget.h index b15adaf2..705f23f5 100644 --- a/src/controllers/DockWidget.h +++ b/src/controllers/DockWidget.h @@ -528,7 +528,6 @@ public: private: #endif Q_DISABLE_COPY(DockWidget) - friend class MultiSplitter; friend class LayoutWidget; friend class MDILayoutWidget; friend class Controllers::FloatingWindow; diff --git a/src/controllers/DropArea.cpp b/src/controllers/DropArea.cpp index f2db53c9..6ab1dc9b 100644 --- a/src/controllers/DropArea.cpp +++ b/src/controllers/DropArea.cpp @@ -28,6 +28,7 @@ #include "qtwidgets/views/Frame_qtwidgets.h" +#include #include using namespace KDDockWidgets; @@ -40,11 +41,22 @@ using namespace KDDockWidgets::Controllers; * @author Sérgio Martins \ */ DropArea::DropArea(View *parent, MainWindowOptions options, bool isMDIWrapper) - : MultiSplitter(parent) + : LayoutWidget(Type::MultiSplitter, parent) , m_isMDIWrapper(isMDIWrapper) , m_dropIndicatorOverlay(Config::self().frameworkWidgetFactory()->createDropIndicatorOverlay(this)) , m_centralFrame(createCentralFrame(options)) { + Q_ASSERT(parent); + setRootItem(new Layouting::ItemBoxContainer(this)); + DockRegistry::self()->registerLayout(this); + + setLayoutSize(parent->size()); + + // Initialize min size + updateSizeConstraints(); + + setMinimumSize(minimumSize()); + qCDebug(creation) << "DropArea"; if (isWayland()) { #ifdef KDDOCKWIDGETS_QTWIDGETS @@ -55,7 +67,7 @@ DropArea::DropArea(View *parent, MainWindowOptions options, bool isMDIWrapper) } if (m_isMDIWrapper) { - connect(this, &MultiSplitter::visibleWidgetCountChanged, this, [this] { + connect(this, &DropArea::visibleWidgetCountChanged, this, [this] { auto dw = mdiDockWidgetWrapper(); if (!dw) { qWarning() << Q_FUNC_INFO << "Unexpected null wrapper dock widget"; @@ -436,3 +448,191 @@ Controllers::Frame *DropArea::createCentralFrame(MainWindowOptions options) return frame; } + + +bool DropArea::validateInputs(View *widget, Location location, + const Controllers::Frame *relativeToFrame, InitialOption option) const +{ + if (!widget) { + qWarning() << Q_FUNC_INFO << "Widget is null"; + return false; + } + + const bool isDockWidget = widget->is(Type::DockWidget); + const bool isStartHidden = option.startsHidden(); + + if (!widget->is(Type::Frame) && !widget->is(Type::Layout) && !isDockWidget) { + qWarning() << "Unknown widget type" << widget; + return false; + } + + if (isDockWidget != isStartHidden) { + qWarning() << "Wrong parameters" << isDockWidget << isStartHidden; + return false; + } + + if (relativeToFrame && relativeToFrame->view()->equals(widget)) { + qWarning() << "widget can't be relative to itself"; + return false; + } + + Layouting::Item *item = itemForFrame(widget->asFrameController()); + + if (containsItem(item)) { + qWarning() << "DropArea::addWidget: Already contains" << widget; + return false; + } + + if (location == Location_None) { + qWarning() << "DropArea::addWidget: not adding to location None"; + return false; + } + + const bool relativeToThis = relativeToFrame == nullptr; + + Layouting::Item *relativeToItem = itemForFrame(relativeToFrame); + if (!relativeToThis && !containsItem(relativeToItem)) { + qWarning() << "DropArea::addWidget: Doesn't contain relativeTo:" + << "; relativeToFrame=" << relativeToFrame + << "; relativeToItem=" << relativeToItem + << "; options=" << option; + return false; + } + + return true; +} + +void DropArea::addWidget(View *w, Location location, + Controllers::Frame *relativeToWidget, + InitialOption option) +{ + + auto frame = w->asFrameController(); + if (itemForFrame(frame) != nullptr) { + // Item already exists, remove it. + // Changing the frame parent will make the item clean itself up. It turns into a placeholder and is removed by unrefOldPlaceholders + frame->view()->setParent(nullptr); // so ~Item doesn't delete it + frame->setLayoutItem(nullptr); // so Item is destroyed, as there's no refs to it + } + + // Make some sanity checks: + if (!validateInputs(w, location, relativeToWidget, option)) + return; + + Layouting::Item *relativeTo = itemForFrame(relativeToWidget); + if (!relativeTo) + relativeTo = m_rootItem; + + Layouting::Item *newItem = nullptr; + + Controllers::Frame::List frames = framesFrom(w); + unrefOldPlaceholders(frames); + auto dw = w->asDockWidgetController(); + + if (frame) { + newItem = new Layouting::Item(this); + newItem->setGuestView(frame->view()); + } else if (dw) { + newItem = new Layouting::Item(this); + frame = new Controllers::Frame(); + newItem->setGuestView(frame->view()); + frame->addWidget(dw, option); + } else if (auto ms = w->asMultiSplitterView()) { + newItem = ms->m_rootItem; + newItem->setHostWidget(this); + + if (auto fw = ms->floatingWindow()) { + newItem->setSize_recursive(fw->size()); + } + + delete ms; + } else { + // This doesn't happen but let's make coverity happy. + // Tests will fail if this is ever printed. + qWarning() << Q_FUNC_INFO << "Unknown widget added" << w; + return; + } + + Q_ASSERT(!newItem->geometry().isEmpty()); + Layouting::ItemBoxContainer::insertItemRelativeTo(newItem, relativeTo, location, option); + + if (dw && option.startsHidden()) + delete frame; +} + +void DropArea::addMultiSplitter(Controllers::DropArea *sourceMultiSplitter, Location location, + Controllers::Frame *relativeTo, + InitialOption option) +{ + qCDebug(addwidget) << Q_FUNC_INFO << sourceMultiSplitter << location << relativeTo; + addWidget(sourceMultiSplitter, location, relativeTo, option); +} + +QVector DropArea::separators() const +{ + return m_rootItem->separators_recursive(); +} + +int DropArea::availableLengthForOrientation(Qt::Orientation orientation) const +{ + if (orientation == Qt::Vertical) + return availableSize().height(); + else + return availableSize().width(); +} + +QSize DropArea::availableSize() const +{ + return m_rootItem->availableSize(); +} + +void DropArea::layoutEqually() +{ + if (!checkSanity()) + return; + + layoutEqually(m_rootItem); +} + +void DropArea::layoutEqually(Layouting::ItemBoxContainer *container) +{ + if (container) { + container->layoutEqually_recursive(); + } else { + qWarning() << Q_FUNC_INFO << "null container"; + } +} + +void DropArea::setRootItem(Layouting::ItemBoxContainer *root) +{ + LayoutWidget::setRootItem(root); + m_rootItem = root; +} + +Layouting::ItemBoxContainer *DropArea::rootItem() const +{ + return m_rootItem; +} + +QRect DropArea::rectForDrop(const WindowBeingDragged *wbd, Location location, + const Layouting::Item *relativeTo) const +{ + Layouting::Item item(nullptr); + if (!wbd) + return {}; + + item.setSize(wbd->size().boundedTo(wbd->maxSize())); + item.setMinSize(wbd->minSize()); + item.setMaxSizeHint(wbd->maxSize()); + + Layouting::ItemBoxContainer *container = relativeTo ? relativeTo->parentBoxContainer() + : m_rootItem; + + return container->suggestedDropRect(&item, relativeTo, location); +} + +bool DropArea::deserialize(const LayoutSaver::MultiSplitter &l) +{ + setRootItem(new Layouting::ItemBoxContainer(this)); + return LayoutWidget::deserialize(l); +} diff --git a/src/controllers/DropArea.h b/src/controllers/DropArea.h index 691477fd..a9ead883 100644 --- a/src/controllers/DropArea.h +++ b/src/controllers/DropArea.h @@ -24,9 +24,10 @@ #include "kddockwidgets/KDDockWidgets.h" #include "controllers/Frame.h" -#include "private/MultiSplitter_p.h" +#include "private/LayoutWidget_p.h" #include "private/DropIndicatorOverlayInterface_p.h" +class TestQtWidgets; class TestDocks; namespace KDDockWidgets { @@ -37,11 +38,21 @@ struct WindowBeingDragged; namespace Controllers { class Frame; class DockWidget; +class Separator; /** - * @brief A MultiSplitter with support for drop indicators when hovering over. + * MultiSplitter is simply a wrapper around Layouting::Item in which the hosted widgets are + * of class KDDockWidgets::Frame. The stuff in Layouting:: being agnostic and generic, not specific + * to KDDW. + * + * A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in + * any combination. + * + * It supports adding a widget to the left/top/bottom/right of the whole MultiSplitter or adding + * relative to a single widget. */ -class DOCKS_EXPORT DropArea : public MultiSplitter + +class DOCKS_EXPORT DropArea : public LayoutWidget { Q_OBJECT public: @@ -84,6 +95,43 @@ public: static Controllers::Frame *createCentralFrame(MainWindowOptions options); + /** + * @brief Adds a widget to this MultiSplitter. + */ + void addWidget(View *widget, KDDockWidgets::Location location, + Controllers::Frame *relativeTo = nullptr, + InitialOption option = DefaultSizeMode::Fair); + + /** + * Adds an entire MultiSplitter into this layout. The donor MultiSplitter will be deleted + * after all its Frames are stolen. All added Frames will preserve their original layout, so, + * if widgetFoo was at the left of widgetBar when in the donor splitter, then it will still be at left + * of widgetBar when the whole splitter is dropped into this one. + */ + void addMultiSplitter(Controllers::DropArea *splitter, KDDockWidgets::Location location, + Controllers::Frame *relativeTo = nullptr, + InitialOption option = DefaultSizeMode::Fair); + + /** + * Called by the indicators, so they draw the drop rubber band at the correct place. + * The rect for the rubberband when dropping a widget at the specified location. + * Excludes the Separator thickness, result is actually smaller than what needed. In other words, + * the result will be exactly the same as the geometry the widget will get. + */ + QRect rectForDrop(const WindowBeingDragged *wbd, KDDockWidgets::Location location, + const Layouting::Item *relativeTo) const; + + bool deserialize(const LayoutSaver::MultiSplitter &) override; + + ///@brief returns the list of separators + QVector separators() const; + + /// @brief See docs for MainWindowBase::layoutEqually() + void layoutEqually(); + + /// @brief overload that just resizes widgets within a sub-tree + void layoutEqually(Layouting::ItemBoxContainer *); + private: Q_DISABLE_COPY(DropArea) friend class Controllers::MainWindow; @@ -94,6 +142,28 @@ private: friend class DropIndicatorOverlayInterface; friend class AnimatedIndicators; + Layouting::ItemBoxContainer *rootItem() const; + + // For debug/hardening + bool validateInputs(View *widget, KDDockWidgets::Location location, + const Controllers::Frame *relativeToFrame, InitialOption option) const; + + + void setRootItem(Layouting::ItemBoxContainer *); + + /** + * @brief Like @ref availableLengthForDrop but just returns the total available width or height (depending on @p orientation) + * So no need to receive any location. + * @param orientation If Qt::Vertical then returns the available height. Width otherwise. + */ + int availableLengthForOrientation(Qt::Orientation orientation) const; + + /** + * @brief Equivalent to @ref availableLengthForOrientation but returns for both orientations. + * width is for Qt::Vertical. + */ + QSize availableSize() const; + template bool validateAffinity(T *, Controllers::Frame *acceptingFrame = nullptr) const; bool drop(WindowBeingDragged *draggedWindow, Controllers::Frame *acceptingFrame, DropLocation); @@ -106,6 +176,7 @@ private: QString m_affinityName; DropIndicatorOverlayInterface *m_dropIndicatorOverlay = nullptr; Controllers::Frame *const m_centralFrame = nullptr; + Layouting::ItemBoxContainer *m_rootItem = nullptr; }; } } diff --git a/src/controllers/FloatingWindow.cpp b/src/controllers/FloatingWindow.cpp index efc2a7b5..9964b316 100644 --- a/src/controllers/FloatingWindow.cpp +++ b/src/controllers/FloatingWindow.cpp @@ -316,7 +316,7 @@ void FloatingWindow::scheduleDeleteLater() deleteLater(); } -MultiSplitter *FloatingWindow::multiSplitter() const +Controllers::DropArea *FloatingWindow::multiSplitter() const { return m_dropArea; } diff --git a/src/controllers/FloatingWindow.h b/src/controllers/FloatingWindow.h index 714428a3..72ab5c6f 100644 --- a/src/controllers/FloatingWindow.h +++ b/src/controllers/FloatingWindow.h @@ -22,7 +22,6 @@ #include "kddockwidgets/docks_export.h" namespace KDDockWidgets { -class MultiSplitter; class LayoutWidget; } @@ -122,7 +121,7 @@ public: /** * @brief Returns the MultiSplitter */ - MultiSplitter *multiSplitter() const; + Controllers::DropArea *multiSplitter() const; /** * @brief Returns the LayoutWidget diff --git a/src/controllers/MainWindow.cpp b/src/controllers/MainWindow.cpp index e9e5481c..8d651988 100644 --- a/src/controllers/MainWindow.cpp +++ b/src/controllers/MainWindow.cpp @@ -208,7 +208,7 @@ DropArea *MainWindow::dropArea() const return qobject_cast(d->m_layoutWidget); } -MultiSplitter *MainWindow::multiSplitter() const +DropArea *MainWindow::multiSplitter() const { return dropArea(); } diff --git a/src/controllers/MainWindow.h b/src/controllers/MainWindow.h index 3b9793f4..a44dbfd7 100644 --- a/src/controllers/MainWindow.h +++ b/src/controllers/MainWindow.h @@ -33,7 +33,6 @@ class TestDocks; namespace KDDockWidgets { class MDILayoutWidget; -class MultiSplitter; class LayoutWidget; namespace Controllers { @@ -121,7 +120,7 @@ public: ///@internal ///@brief returns the MultiSplitter. - MultiSplitter *multiSplitter() const; + DropArea *multiSplitter() const; ///@internal ///@brief returns the MultiSplitter. diff --git a/src/private/MultiSplitter.cpp b/src/private/MultiSplitter.cpp deleted file mode 100644 index c93762a1..00000000 --- a/src/private/MultiSplitter.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* - This file is part of KDDockWidgets. - - SPDX-FileCopyrightText: 2020-2022 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Sérgio Martins - - SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only - - Contact KDAB at for commercial licensing options. -*/ - -/** - * @file - * @brief A widget that supports an arbitrary number of splitters (called Separators) in any - * combination of vertical/horizontal. - * - * @author Sérgio Martins \ - */ - -#include "MultiSplitter_p.h" -#include "LayoutSaver_p.h" -#include "Config.h" -#include "DockRegistry_p.h" -#include "FrameworkWidgetFactory.h" -#include "LayoutSaver.h" -#include "Logging_p.h" -#include "Position_p.h" -#include "WindowBeingDragged_p.h" -#include "multisplitter/Item_p.h" - -#include "controllers/Frame.h" -#include "controllers/FloatingWindow.h" -#include "controllers/DockWidget_p.h" -#include "controllers/DockWidget.h" -#include "controllers/MainWindow.h" - -#include - -using namespace KDDockWidgets; -using namespace KDDockWidgets::Controllers; - -MultiSplitter::MultiSplitter(View *parent) - : LayoutWidget(Type::MultiSplitter, parent) -{ - Q_ASSERT(parent); - setRootItem(new Layouting::ItemBoxContainer(this)); - DockRegistry::self()->registerLayout(this); - - setLayoutSize(parent->size()); - - // Initialize min size - updateSizeConstraints(); - - setMinimumSize(minimumSize()); -} - -MultiSplitter::~MultiSplitter() -{ -} - -bool MultiSplitter::validateInputs(View *widget, Location location, - const Controllers::Frame *relativeToFrame, InitialOption option) const -{ - if (!widget) { - qWarning() << Q_FUNC_INFO << "Widget is null"; - return false; - } - - const bool isDockWidget = widget->is(Type::DockWidget); - const bool isStartHidden = option.startsHidden(); - - if (!widget->is(Type::Frame) && !widget->is(Type::Layout) && !isDockWidget) { - qWarning() << "Unknown widget type" << widget; - return false; - } - - if (isDockWidget != isStartHidden) { - qWarning() << "Wrong parameters" << isDockWidget << isStartHidden; - return false; - } - - if (relativeToFrame && relativeToFrame->view()->equals(widget)) { - qWarning() << "widget can't be relative to itself"; - return false; - } - - Layouting::Item *item = itemForFrame(widget->asFrameController()); - - if (containsItem(item)) { - qWarning() << "MultiSplitter::addWidget: Already contains" << widget; - return false; - } - - if (location == Location_None) { - qWarning() << "MultiSplitter::addWidget: not adding to location None"; - return false; - } - - const bool relativeToThis = relativeToFrame == nullptr; - - Layouting::Item *relativeToItem = itemForFrame(relativeToFrame); - if (!relativeToThis && !containsItem(relativeToItem)) { - qWarning() << "MultiSplitter::addWidget: Doesn't contain relativeTo:" - << "; relativeToFrame=" << relativeToFrame - << "; relativeToItem=" << relativeToItem - << "; options=" << option; - return false; - } - - return true; -} - -void MultiSplitter::addWidget(View *w, Location location, - Controllers::Frame *relativeToWidget, - InitialOption option) -{ - - auto frame = w->asFrameController(); - if (itemForFrame(frame) != nullptr) { - // Item already exists, remove it. - // Changing the frame parent will make the item clean itself up. It turns into a placeholder and is removed by unrefOldPlaceholders - frame->view()->setParent(nullptr); // so ~Item doesn't delete it - frame->setLayoutItem(nullptr); // so Item is destroyed, as there's no refs to it - } - - // Make some sanity checks: - if (!validateInputs(w, location, relativeToWidget, option)) - return; - - Layouting::Item *relativeTo = itemForFrame(relativeToWidget); - if (!relativeTo) - relativeTo = m_rootItem; - - Layouting::Item *newItem = nullptr; - - Controllers::Frame::List frames = framesFrom(w); - unrefOldPlaceholders(frames); - auto dw = w->asDockWidgetController(); - - if (frame) { - newItem = new Layouting::Item(this); - newItem->setGuestView(frame->view()); - } else if (dw) { - newItem = new Layouting::Item(this); - frame = new Controllers::Frame(); - newItem->setGuestView(frame->view()); - frame->addWidget(dw, option); - } else if (auto ms = w->asMultiSplitterView()) { - newItem = ms->m_rootItem; - newItem->setHostWidget(this); - - if (auto fw = ms->floatingWindow()) { - newItem->setSize_recursive(fw->size()); - } - - delete ms; - } else { - // This doesn't happen but let's make coverity happy. - // Tests will fail if this is ever printed. - qWarning() << Q_FUNC_INFO << "Unknown widget added" << w; - return; - } - - Q_ASSERT(!newItem->geometry().isEmpty()); - Layouting::ItemBoxContainer::insertItemRelativeTo(newItem, relativeTo, location, option); - - if (dw && option.startsHidden()) - delete frame; -} - -void MultiSplitter::addMultiSplitter(MultiSplitter *sourceMultiSplitter, Location location, - Controllers::Frame *relativeTo, - InitialOption option) -{ - qCDebug(addwidget) << Q_FUNC_INFO << sourceMultiSplitter << location << relativeTo; - addWidget(sourceMultiSplitter, location, relativeTo, option); -} - -QVector MultiSplitter::separators() const -{ - return m_rootItem->separators_recursive(); -} - -int MultiSplitter::availableLengthForOrientation(Qt::Orientation orientation) const -{ - if (orientation == Qt::Vertical) - return availableSize().height(); - else - return availableSize().width(); -} - -QSize MultiSplitter::availableSize() const -{ - return m_rootItem->availableSize(); -} - -void MultiSplitter::layoutEqually() -{ - if (!checkSanity()) - return; - - layoutEqually(m_rootItem); -} - -void MultiSplitter::layoutEqually(Layouting::ItemBoxContainer *container) -{ - if (container) { - container->layoutEqually_recursive(); - } else { - qWarning() << Q_FUNC_INFO << "null container"; - } -} - -void MultiSplitter::setRootItem(Layouting::ItemBoxContainer *root) -{ - LayoutWidget::setRootItem(root); - m_rootItem = root; -} - -Layouting::ItemBoxContainer *MultiSplitter::rootItem() const -{ - return m_rootItem; -} - -QRect MultiSplitter::rectForDrop(const WindowBeingDragged *wbd, Location location, - const Layouting::Item *relativeTo) const -{ - Layouting::Item item(nullptr); - if (!wbd) - return {}; - - item.setSize(wbd->size().boundedTo(wbd->maxSize())); - item.setMinSize(wbd->minSize()); - item.setMaxSizeHint(wbd->maxSize()); - - Layouting::ItemBoxContainer *container = relativeTo ? relativeTo->parentBoxContainer() - : m_rootItem; - - return container->suggestedDropRect(&item, relativeTo, location); -} - -bool MultiSplitter::deserialize(const LayoutSaver::MultiSplitter &l) -{ - setRootItem(new Layouting::ItemBoxContainer(this)); - return LayoutWidget::deserialize(l); -} diff --git a/src/private/MultiSplitter_p.h b/src/private/MultiSplitter_p.h deleted file mode 100644 index 9d87bff7..00000000 --- a/src/private/MultiSplitter_p.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - This file is part of KDDockWidgets. - - SPDX-FileCopyrightText: 2020-2022 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Sérgio Martins - - SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only - - Contact KDAB at for commercial licensing options. -*/ - -/** - * @file - * @brief A widget that supports an arbitrary number of splitters (called Separators) in any - * combination of vertical/horizontal. - * - * This is a widget wrapper around the multisplitter layout (Layouting::Item) - * - * @author Sérgio Martins \ - */ - -#ifndef KDDOCKWIDGETS_MULTISPLITTER_P_H -#define KDDOCKWIDGETS_MULTISPLITTER_P_H - -#include "LayoutWidget_p.h" -#include "kddockwidgets/KDDockWidgets.h" -#include "kddockwidgets/docks_export.h" - -class TestDocks; -class TestQtWidgets; - -namespace KDDockWidgets { - -namespace Controllers { -class Separator; -class Frame; -} - -struct WindowBeingDragged; - -/** - * MultiSplitter is simply a wrapper around Layouting::Item in which the hosted widgets are - * of class KDDockWidgets::Frame. The stuff in Layouting:: being agnostic and generic, not specific - * to KDDW. - * - * A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in - * any combination. - * - * It supports adding a widget to the left/top/bottom/right of the whole MultiSplitter or adding - * relative to a single widget. - */ -class DOCKS_EXPORT MultiSplitter : public LayoutWidget -{ - Q_OBJECT -public: - explicit MultiSplitter(View *parent = nullptr); - ~MultiSplitter() override; - - /** - * @brief Adds a widget to this MultiSplitter. - */ - void addWidget(View *widget, KDDockWidgets::Location location, - Controllers::Frame *relativeTo = nullptr, - InitialOption option = DefaultSizeMode::Fair); - - /** - * Adds an entire MultiSplitter into this layout. The donor MultiSplitter will be deleted - * after all its Frames are stolen. All added Frames will preserve their original layout, so, - * if widgetFoo was at the left of widgetBar when in the donor splitter, then it will still be at left - * of widgetBar when the whole splitter is dropped into this one. - */ - void addMultiSplitter(MultiSplitter *splitter, KDDockWidgets::Location location, - Controllers::Frame *relativeTo = nullptr, - InitialOption option = DefaultSizeMode::Fair); - - /** - * Called by the indicators, so they draw the drop rubber band at the correct place. - * The rect for the rubberband when dropping a widget at the specified location. - * Excludes the Separator thickness, result is actually smaller than what needed. In other words, - * the result will be exactly the same as the geometry the widget will get. - */ - QRect rectForDrop(const WindowBeingDragged *wbd, KDDockWidgets::Location location, - const Layouting::Item *relativeTo) const; - - bool deserialize(const LayoutSaver::MultiSplitter &) override; - - ///@brief returns the list of separators - QVector separators() const; - - /// @brief See docs for MainWindowBase::layoutEqually() - void layoutEqually(); - - /// @brief overload that just resizes widgets within a sub-tree - void layoutEqually(Layouting::ItemBoxContainer *); - -private: - friend class ::TestDocks; - friend class ::TestQtWidgets; - - Layouting::ItemBoxContainer *rootItem() const; - - // For debug/hardening - bool validateInputs(View *widget, KDDockWidgets::Location location, - const Controllers::Frame *relativeToFrame, InitialOption option) const; - - - void setRootItem(Layouting::ItemBoxContainer *); - - /** - * @brief Like @ref availableLengthForDrop but just returns the total available width or height (depending on @p orientation) - * So no need to receive any location. - * @param orientation If Qt::Vertical then returns the available height. Width otherwise. - */ - int availableLengthForOrientation(Qt::Orientation orientation) const; - - /** - * @brief Equivalent to @ref availableLengthForOrientation but returns for both orientations. - * width is for Qt::Vertical. - */ - QSize availableSize() const; - - Layouting::ItemBoxContainer *m_rootItem = nullptr; -}; - -} - -#endif diff --git a/src/qtquick/views/ViewWrapper_qtquick.cpp b/src/qtquick/views/ViewWrapper_qtquick.cpp index f70a82c5..7b95e701 100644 --- a/src/qtquick/views/ViewWrapper_qtquick.cpp +++ b/src/qtquick/views/ViewWrapper_qtquick.cpp @@ -20,7 +20,7 @@ // #include "qtwidgets/views/TabBar_qtquick.h" // #include "qtwidgets/views/TitleBar_qtquick.h" -#include "private/MultiSplitter_p.h" +#include "controllers/DropArea.h" #include "private/MDILayoutWidget_p.h" #include "MDIArea.h" @@ -147,7 +147,7 @@ bool ViewWrapper_qtquick::is(Type t) const // case Type::Layout: // return qobject_cast(m_item); // case Type::MultiSplitter: - // return qobject_cast(m_item); + // return qobject_cast(m_item); // case Type::MDILayout: // return qobject_cast(m_item); // case Type::MDIArea: diff --git a/src/qtwidgets/views/DropArea_qtwidgets.cpp b/src/qtwidgets/views/DropArea_qtwidgets.cpp index 87d33a09..cac3a45e 100644 --- a/src/qtwidgets/views/DropArea_qtwidgets.cpp +++ b/src/qtwidgets/views/DropArea_qtwidgets.cpp @@ -16,7 +16,7 @@ using namespace KDDockWidgets; using namespace KDDockWidgets::Views; DropArea_qtwidgets::DropArea_qtwidgets(Controllers::DropArea *dropArea, View *parent) - : MultiSplitter(parent) + : LayoutWidget(Type::MultiSplitter, parent) , m_dropArea(dropArea) { if (isWayland()) { diff --git a/src/qtwidgets/views/DropArea_qtwidgets.h b/src/qtwidgets/views/DropArea_qtwidgets.h index 0467e2fd..4ab9d8f9 100644 --- a/src/qtwidgets/views/DropArea_qtwidgets.h +++ b/src/qtwidgets/views/DropArea_qtwidgets.h @@ -21,7 +21,7 @@ #include "kddockwidgets/docks_export.h" #include "kddockwidgets/KDDockWidgets.h" -#include "private/MultiSplitter_p.h" +#include "private/LayoutWidget_p.h" namespace KDDockWidgets { @@ -31,7 +31,7 @@ class DropArea; namespace Views { -class DOCKS_EXPORT DropArea_qtwidgets : public MultiSplitter +class DOCKS_EXPORT DropArea_qtwidgets : public LayoutWidget { Q_OBJECT public: diff --git a/src/qtwidgets/views/ViewWrapper_qtwidgets.cpp b/src/qtwidgets/views/ViewWrapper_qtwidgets.cpp index 8e6a2255..7f02c3f1 100644 --- a/src/qtwidgets/views/ViewWrapper_qtwidgets.cpp +++ b/src/qtwidgets/views/ViewWrapper_qtwidgets.cpp @@ -21,10 +21,10 @@ #include "qtwidgets/views/TitleBar_qtwidgets.h" #include "qtwidgets/Window_qtwidgets.h" -#include "private/MultiSplitter_p.h" #include "private/MDILayoutWidget_p.h" #include "MDIArea.h" #include "controllers/DropArea.h" +#include "controllers/DropArea.h" #include #include @@ -219,7 +219,7 @@ bool ViewWrapper_qtwidgets::is(Type t) const case Type::Layout: return qobject_cast(m_widget); case Type::MultiSplitter: - return qobject_cast(m_widget); + return qobject_cast(m_widget); // TODOv2 case Type::MDILayout: return qobject_cast(m_widget); case Type::MDIArea: diff --git a/tests/tst_docks.cpp b/tests/tst_docks.cpp index a7c3764d..f2a3905a 100644 --- a/tests/tst_docks.cpp +++ b/tests/tst_docks.cpp @@ -22,7 +22,6 @@ #include "MDIArea.h" #include "Platform.h" #include "multisplitter/Item_p.h" -#include "private/MultiSplitter_p.h" #include "controllers/DropArea.h" #include "controllers/DockWidget.h" @@ -405,7 +404,7 @@ void TestDocks::tst_sizeAfterRedock() Controllers::FloatingWindow *fw1 = dw1->floatingWindow(); DropArea *dropArea = fw1->dropArea(); - MultiSplitter *ms1 = fw1->multiSplitter(); + Controllers::DropArea *ms1 = fw1->multiSplitter(); { WindowBeingDragged wbd2(oldFw2); const QRect suggestedDropRect = ms1->rectForDrop(&wbd2, Location_OnBottom, nullptr); @@ -1104,7 +1103,7 @@ void TestDocks::tst_28NestedWidgets() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; int i = 0; for (DockDescriptor &desc : docksToCreate) { @@ -1193,7 +1192,7 @@ void TestDocks::tst_startHidden2() auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, {}, false); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; m->addDockWidget(dock1, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); QVERIFY(layout->checkSanity()); @@ -1223,7 +1222,7 @@ void TestDocks::tst_startHidden2() auto dock3 = createDockWidget("dock3", new QPushButton("three"), {}, {}, false); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; m->addDockWidget(dock1, Location_OnLeft, nullptr, InitialVisibilityOption::StartHidden); m->addDockWidget(dock2, Location_OnBottom, nullptr, InitialVisibilityOption::StartHidden); @@ -1256,7 +1255,7 @@ void TestDocks::tst_negativeAnchorPosition() auto w3 = new MyWidget2(QSize(133, 343)); w3->resize(392, 362); - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); @@ -1300,7 +1299,7 @@ void TestDocks::tst_negativeAnchorPosition2() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; auto dock1 = createDockWidget("1", new QPushButton("1"), {}, {}, /*show=*/false); auto dock2 = createDockWidget("2", new QPushButton("2"), {}, {}, /*show=*/false); @@ -1330,7 +1329,7 @@ void TestDocks::tst_negativeAnchorPosition3() { Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; auto m = createMainWindow(docks); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; layout->checkSanity(); auto dock1 = docks.at(1).createdDock; @@ -1353,7 +1352,7 @@ void TestDocks::tst_negativeAnchorPosition4() auto m = createMainWindow(docks); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; layout->checkSanity(); auto dock1 = docks.at(1).createdDock; @@ -1383,7 +1382,7 @@ void TestDocks::tst_negativeAnchorPosition5() auto m = createMainWindow(docks); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; layout->checkSanity(); auto dock0 = docks.at(0).createdDock; @@ -1520,7 +1519,7 @@ void TestDocks::tst_addAsPlaceholder() m->addDockWidget(dock2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); @@ -1551,7 +1550,7 @@ void TestDocks::tst_removeItem() Item *item2 = dock2->dptr()->lastPosition()->lastItem(); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); @@ -1698,7 +1697,7 @@ void TestDocks::tst_startClosed() auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; m->addDockWidget(dock1, Location_OnTop); Controllers::Frame *frame1 = dock1->dptr()->frame(); @@ -1895,7 +1894,7 @@ void TestDocks::tst_availableLengthForOrientation() // 1. Test a completely empty window, it's available space is its size minus the static separators thickness auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; int availableWidth = layout->availableLengthForOrientation(Qt::Horizontal); int availableHeight = layout->availableLengthForOrientation(Qt::Vertical); @@ -1976,7 +1975,7 @@ void TestDocks::tst_placeholderDisappearsOnReadd() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); @@ -2008,7 +2007,7 @@ void TestDocks::tst_placeholdersAreRemovedProperly() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); QPointer dock2 = createDockWidget("2", new QPushButton("2")); m->addDockWidget(dock1, Location_OnLeft); @@ -2209,7 +2208,7 @@ void TestDocks::tst_toggleMiddleDockCrash() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); QPointer dock2 = createDockWidget("2", new QPushButton("2")); QPointer dock3 = createDockWidget("3", new QPushButton("3")); @@ -2472,7 +2471,7 @@ void TestDocks::tst_setFloatingWhenSideBySide() auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); m->addDockWidget(dock3, KDDockWidgets::Location_OnRight); @@ -3017,7 +3016,7 @@ void TestDocks::tst_addToSmallMainWindow4() auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(50, 50))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(50, 50))); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom); Platform::instance()->tests_waitForResize(m->view()); @@ -3065,7 +3064,7 @@ void TestDocks::tst_fairResizeAfterRemoveWidget() const int oldWidth1 = dock1->dptr()->frame()->width(); const int oldWidth2 = dock2->dptr()->frame()->width(); const int oldWidth3 = dock3->dptr()->frame()->width(); - MultiSplitter *layout = fw->dropArea(); + Controllers::DropArea *layout = fw->dropArea(); QCOMPARE(layout->count(), 3); QCOMPARE(layout->visibleCount(), 3); QCOMPARE(layout->placeholderCount(), 0); @@ -3149,7 +3148,7 @@ void TestDocks::tst_invalidPlaceholderPosition() auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); // Stack: 1, 2, 3 vertically m->addDockWidget(dock3, Location_OnTop); @@ -3374,7 +3373,7 @@ void TestDocks::tst_resizeViaAnchorsAfterPlaceholderCreation() // Stack 1, 2, 3, close 2, close 2 { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); @@ -3403,7 +3402,7 @@ void TestDocks::tst_resizeViaAnchorsAfterPlaceholderCreation() m->addDockWidget(dock3, Location_OnRight); m->addDockWidget(dock4, Location_OnRight); - MultiSplitter *layout = m->multiSplitter(); + Controllers::DropArea *layout = m->multiSplitter(); Item *item1 = layout->itemForFrame(dock1->dptr()->frame()); Item *item2 = layout->itemForFrame(dock2->dptr()->frame()); @@ -4272,7 +4271,7 @@ void TestDocks::tst_anchorFollowingItselfAssert() auto m = createMainWindow(docks); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; layout->checkSanity(); auto dock1 = docks.at(1).createdDock; @@ -5022,7 +5021,7 @@ void TestDocks::tst_invalidLayoutAfterRestore() auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; // Stack 1, 2, 3 m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); @@ -5802,7 +5801,7 @@ void TestDocks::tst_constraintsAfterPlaceholder() auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(400, minHeight))); auto dock3 = createDockWidget("dock3", new MyWidget2(QSize(400, minHeight))); auto dropArea = m->dropArea(); - MultiSplitter *layout = dropArea; + Controllers::DropArea *layout = dropArea; // Stack 3, 2, 1 m->addDockWidget(dock1, Location_OnTop);