/******************************************************************************* ** Qt Advanced Docking System ** Copyright (C) 2017 Uwe Kindler ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library 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 ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; If not, see . ******************************************************************************/ //============================================================================ /// \file DockContainerWidget.cpp /// \author Uwe Kindler /// \date 24.02.2017 /// \brief Implementation of CDockContainerWidget class //============================================================================ //============================================================================ // INCLUDES //============================================================================ #include "DockContainerWidget.h" #include #include #include #include #include #include #include #include #include #include "DockManager.h" #include "DockAreaWidget.h" #include "DockWidget.h" #include "DockingStateReader.h" #include "FloatingDockContainer.h" #include "DockOverlay.h" #include "ads_globals.h" #include "DockSplitter.h" #include "SideTabBar.h" #include "OverlayDockContainer.h" #include "DockWidgetTab.h" #include "DockWidgetSideTab.h" #include "DockAreaTitleBar.h" #include "DockFocusController.h" #include #include #if QT_VERSION < 0x050900 inline char toHexLower(uint value) { return "0123456789abcdef"[value & 0xF]; } QByteArray qByteArrayToHex(const QByteArray& src, char separator) { if(src.size() == 0) return QByteArray(); const int length = separator ? (src.size() * 3 - 1) : (src.size() * 2); QByteArray hex(length, Qt::Uninitialized); char *hexData = hex.data(); const uchar *data = reinterpret_cast(src.data()); for (int i = 0, o = 0; i < src.size(); ++i) { hexData[o++] = toHexLower(data[i] >> 4); hexData[o++] = toHexLower(data[i] & 0xf); if ((separator) && (o < length)) hexData[o++] = separator; } return hex; } #endif namespace ads { static unsigned int zOrderCounter = 0; enum eDropMode { DropModeIntoArea,///< drop widget into a dock area DropModeIntoContainer,///< drop into container DropModeInvalid///< invalid mode - do not drop }; /** * Converts dock area ID to an index for array access */ static int areaIdToIndex(DockWidgetArea area) { switch (area) { case LeftDockWidgetArea: return 0; case RightDockWidgetArea: return 1; case TopDockWidgetArea: return 2; case BottomDockWidgetArea: return 3; case CenterDockWidgetArea: return 4; default: return 4; } } /** * Helper function to ease insertion of dock area into splitter */ static void insertWidgetIntoSplitter(QSplitter* Splitter, QWidget* widget, bool Append) { if (Append) { Splitter->addWidget(widget); } else { Splitter->insertWidget(0, widget); } } /** * Private data class of CDockContainerWidget class (pimpl) */ class DockContainerWidgetPrivate { public: CDockContainerWidget* _this; QPointer DockManager; unsigned int zOrderIndex = 0; QList DockAreas; QList OverlayWidgets; QMap SideTabBarWidgets; QGridLayout* Layout = nullptr; QSplitter* RootSplitter = nullptr; bool isFloating = false; CDockAreaWidget* LastAddedAreaCache[5]; int VisibleDockAreaCount = -1; CDockAreaWidget* TopLevelDockArea = nullptr; /** * Private data constructor */ DockContainerWidgetPrivate(CDockContainerWidget* _public); /** * Adds dock widget to container and returns the dock area that contains * the inserted dock widget */ CDockAreaWidget* addDockWidgetToContainer(DockWidgetArea area, CDockWidget* Dockwidget); /** * Adds dock widget to a existing DockWidgetArea */ CDockAreaWidget* addDockWidgetToDockArea(DockWidgetArea area, CDockWidget* Dockwidget, CDockAreaWidget* TargetDockArea); /** * Add dock area to this container */ void addDockArea(CDockAreaWidget* NewDockWidget, DockWidgetArea area = CenterDockWidgetArea); /** * Drop floating widget into container */ void dropIntoContainer(CFloatingDockContainer* FloatingWidget, DockWidgetArea area); /** * Drop floating widget into dock area */ void dropIntoSection(CFloatingDockContainer* FloatingWidget, CDockAreaWidget* TargetArea, DockWidgetArea area); /** * Moves the dock widget or dock area given in Widget parameter to a * new dock widget area */ void moveToNewSection(QWidget* Widget, CDockAreaWidget* TargetArea, DockWidgetArea area); /** * Moves the dock widget or dock area given in Widget parameter to a * a dock area in container */ void moveToContainer(QWidget* Widgett, DockWidgetArea area); /** * Creates a new tab for a widget dropped into the center of a section */ void dropIntoCenterOfSection(CFloatingDockContainer* FloatingWidget, CDockAreaWidget* TargetArea); /** * Creates a new tab for a widget dropped into the center of a section */ void moveIntoCenterOfSection(QWidget* Widget, CDockAreaWidget* TargetArea); /** * Adds new dock areas to the internal dock area list */ void addDockAreasToList(const QList NewDockAreas); /** * Wrapper function for DockAreas append, that ensures that dock area signals * are properly connected to dock container slots */ void appendDockAreas(const QList NewDockAreas); /** * Save state of child nodes */ void saveChildNodesState(QXmlStreamWriter& Stream, QWidget* Widget); /** * Save state of overlay widgets */ void saveOverlayWidgetsState(QXmlStreamWriter& Stream); /** * Restore state of child nodes. * \param[in] Stream The data stream that contains the serialized state * \param[out] CreatedWidget The widget created from parsed data or 0 if * the parsed widget was an empty splitter * \param[in] Testing If Testing is true, only the stream data is * parsed without modifiying anything. */ bool restoreChildNodes(CDockingStateReader& Stream, QWidget*& CreatedWidget, bool Testing); /** * Restores a splitter. * \see restoreChildNodes() for details */ bool restoreSplitter(CDockingStateReader& Stream, QWidget*& CreatedWidget, bool Testing); /** * Restores a dock area. * \see restoreChildNodes() for details */ bool restoreDockArea(CDockingStateReader& Stream, QWidget*& CreatedWidget, bool Testing); /** * Restores the overlay dock area. * Assumes that there are no overlay dock areas, and then restores all the dock areas that * exist in the XML */ bool restoreOverlayDockArea(CDockingStateReader& s, CDockWidgetSideTab::SideTabBarArea area, bool Testing); /** * Restores either a dock area or an overlay dock area depending on the value in the XML */ bool restoreDockOrOverlayDockArea(CDockingStateReader& Stream, QWidget*& CreatedWidget, bool Testing); /** * Helper function for recursive dumping of layout */ void dumpRecursive(int level, QWidget* widget); /** * Calculate the drop mode from the given target position */ eDropMode getDropMode(const QPoint& TargetPos); /** * Initializes the visible dock area count variable if it is not initialized * yet */ void initVisibleDockAreaCount() { if (VisibleDockAreaCount > -1) { return; } VisibleDockAreaCount = 0; for (auto DockArea : DockAreas) { VisibleDockAreaCount += DockArea->isHidden() ? 0 : 1; } } /** * Access function for the visible dock area counter */ int& visibleDockAreaCount() { // Lazy initialisation - we initialize the VisibleDockAreaCount variable // on first use initVisibleDockAreaCount(); return VisibleDockAreaCount; } /** * The visible dock area count changes, if dock areas are remove, added or * when its view is toggled */ void onVisibleDockAreaCountChanged(); void emitDockAreasRemoved() { onVisibleDockAreaCountChanged(); Q_EMIT _this->dockAreasRemoved(); } void emitDockAreasAdded() { onVisibleDockAreaCountChanged(); Q_EMIT _this->dockAreasAdded(); } /** * Helper function for creation of new splitter */ CDockSplitter* newSplitter(Qt::Orientation orientation, QWidget* parent = nullptr) { CDockSplitter* s = new CDockSplitter(orientation, parent); s->setOpaqueResize(CDockManager::testConfigFlag(CDockManager::OpaqueSplitterResize)); s->setChildrenCollapsible(false); return s; } /** * Ensures equal distribution of the sizes of a splitter if an dock widget * is inserted from code */ void adjustSplitterSizesOnInsertion(QSplitter* Splitter, qreal LastRatio = 1.0) { int AreaSize = (Splitter->orientation() == Qt::Horizontal) ? Splitter->width() : Splitter->height(); auto SplitterSizes = Splitter->sizes(); qreal TotRatio = SplitterSizes.size() - 1.0 + LastRatio; for(int i = 0; i < SplitterSizes.size() -1; i++) { SplitterSizes[i] = AreaSize / TotRatio; } SplitterSizes.back() = AreaSize * LastRatio / TotRatio; Splitter->setSizes(SplitterSizes); } /** * This function forces the dock container widget to update handles of splitters * based if a central widget exists. */ void updateSplitterHandles(QSplitter* splitter); /** * If no central widget exists, the widgets resize with the container. * If a central widget exists, the widgets surrounding the central widget * do not resize its height or width. */ bool widgetResizesWithContainer(QWidget* widget); // private slots: ------------------------------------------------------------ void onDockAreaViewToggled(bool Visible) { CDockAreaWidget* DockArea = qobject_cast(_this->sender()); VisibleDockAreaCount += Visible ? 1 : -1; onVisibleDockAreaCountChanged(); Q_EMIT _this->dockAreaViewToggled(DockArea, Visible); } }; // struct DockContainerWidgetPrivate //============================================================================ DockContainerWidgetPrivate::DockContainerWidgetPrivate(CDockContainerWidget* _public) : _this(_public) { std::fill(std::begin(LastAddedAreaCache),std::end(LastAddedAreaCache), nullptr); } //============================================================================ eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint& TargetPos) { CDockAreaWidget* DockArea = _this->dockAreaAt(TargetPos); auto dropArea = InvalidDockWidgetArea; auto ContainerDropArea = DockManager->containerOverlay()->dropAreaUnderCursor(); if (DockArea) { auto dropOverlay = DockManager->dockAreaOverlay(); dropOverlay->setAllowedAreas(DockArea->allowedAreas()); dropArea = dropOverlay->showOverlay(DockArea); if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) { dropArea = InvalidDockWidgetArea; } if (dropArea != InvalidDockWidgetArea) { ADS_PRINT("Dock Area Drop Content: " << dropArea); return DropModeIntoArea; } } // mouse is over container if (InvalidDockWidgetArea == dropArea) { dropArea = ContainerDropArea; ADS_PRINT("Container Drop Content: " << dropArea); if (dropArea != InvalidDockWidgetArea) { return DropModeIntoContainer; } } return DropModeInvalid; } //============================================================================ void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() { auto TopLevelDockArea = _this->topLevelDockArea(); if (TopLevelDockArea) { this->TopLevelDockArea = TopLevelDockArea; TopLevelDockArea->updateTitleBarButtonVisibility(true); } else if (this->TopLevelDockArea) { this->TopLevelDockArea->updateTitleBarButtonVisibility(false); this->TopLevelDockArea = nullptr; } } //============================================================================ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* FloatingWidget, DockWidgetArea area) { auto InsertParam = internal::dockAreaInsertParameters(area); CDockContainerWidget* FloatingDockContainer = FloatingWidget->dockContainer(); auto NewDockAreas = FloatingDockContainer->findChildren( QString(), Qt::FindChildrenRecursively); QSplitter* Splitter = RootSplitter; if (DockAreas.count() <= 1) { Splitter->setOrientation(InsertParam.orientation()); } else if (Splitter->orientation() != InsertParam.orientation()) { QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); updateSplitterHandles(NewSplitter); Splitter = NewSplitter; delete li; } // Now we can insert the floating widget content into this container auto FloatingSplitter = FloatingDockContainer->rootSplitter(); if (FloatingSplitter->count() == 1) { insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append()); updateSplitterHandles(Splitter); } else if (FloatingSplitter->orientation() == InsertParam.orientation()) { int InsertIndex = InsertParam.append() ? Splitter->count() : 0; while (FloatingSplitter->count()) { Splitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); updateSplitterHandles(Splitter); } } else { insertWidgetIntoSplitter(Splitter, FloatingSplitter, InsertParam.append()); } RootSplitter = Splitter; addDockAreasToList(NewDockAreas); // If we dropped the floating widget into the main dock container that does // not contain any dock widgets, then splitter is invisible and we need to // show it to display the docked widgets if (!Splitter->isVisible()) { Splitter->show(); } _this->dumpLayout(); } //============================================================================ void DockContainerWidgetPrivate::dropIntoCenterOfSection( CFloatingDockContainer* FloatingWidget, CDockAreaWidget* TargetArea) { CDockContainerWidget* FloatingContainer = FloatingWidget->dockContainer(); auto NewDockWidgets = FloatingContainer->dockWidgets(); auto TopLevelDockArea = FloatingContainer->topLevelDockArea(); int NewCurrentIndex = -1; // If the floating widget contains only one single dock are, then the // current dock widget of the dock area will also be the future current // dock widget in the drop area. if (TopLevelDockArea) { NewCurrentIndex = TopLevelDockArea->currentIndex(); } for (int i = 0; i < NewDockWidgets.count(); ++i) { CDockWidget* DockWidget = NewDockWidgets[i]; TargetArea->insertDockWidget(i, DockWidget, false); // If the floating widget contains multiple visible dock areas, then we // simply pick the first visible open dock widget and make it // the current one. if (NewCurrentIndex < 0 && !DockWidget->isClosed()) { NewCurrentIndex = i; } } TargetArea->setCurrentIndex(NewCurrentIndex); TargetArea->updateTitleBarVisibility(); return; } //============================================================================ void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* FloatingWidget, CDockAreaWidget* TargetArea, DockWidgetArea area) { // Dropping into center means all dock widgets in the dropped floating // widget will become tabs of the drop area if (CenterDockWidgetArea == area) { dropIntoCenterOfSection(FloatingWidget, TargetArea); return; } auto InsertParam = internal::dockAreaInsertParameters(area); auto NewDockAreas = FloatingWidget->dockContainer()->findChildren( QString(), Qt::FindChildrenRecursively); QSplitter* TargetAreaSplitter = internal::findParent(TargetArea); if (!TargetAreaSplitter) { QSplitter* Splitter = newSplitter(InsertParam.orientation()); Layout->replaceWidget(TargetArea, Splitter); Splitter->addWidget(TargetArea); updateSplitterHandles(Splitter); TargetAreaSplitter = Splitter; } int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); auto Widget = FloatingWidget->dockContainer()->findChild(QString(), Qt::FindDirectChildrenOnly); auto FloatingSplitter = qobject_cast(Widget); if (TargetAreaSplitter->orientation() == InsertParam.orientation()) { auto Sizes = TargetAreaSplitter->sizes(); int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); bool AdjustSplitterSizes = true; if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), Widget); updateSplitterHandles(TargetAreaSplitter); } else { AdjustSplitterSizes = (FloatingSplitter->count() == 1); int InsertIndex = AreaIndex + InsertParam.insertOffset(); while (FloatingSplitter->count()) { TargetAreaSplitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); updateSplitterHandles(TargetAreaSplitter); } } if (AdjustSplitterSizes) { int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; Sizes[AreaIndex] = Size; Sizes.insert(AreaIndex, Size); TargetAreaSplitter->setSizes(Sizes); } } else { QList NewSplitterSizes; QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); bool AdjustSplitterSizes = true; if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { NewSplitter->addWidget(Widget); updateSplitterHandles(NewSplitter); } else { AdjustSplitterSizes = (FloatingSplitter->count() == 1); while (FloatingSplitter->count()) { NewSplitter->addWidget(FloatingSplitter->widget(0)); updateSplitterHandles(NewSplitter); } } // Save the sizes before insertion and restore it later to prevent // shrinking of existing area auto Sizes = TargetAreaSplitter->sizes(); insertWidgetIntoSplitter(NewSplitter, TargetArea, !InsertParam.append()); updateSplitterHandles(NewSplitter); if (AdjustSplitterSizes) { int Size = TargetAreaSize / 2; NewSplitter->setSizes({Size, Size}); } TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); TargetAreaSplitter->setSizes(Sizes); updateSplitterHandles(TargetAreaSplitter); } addDockAreasToList(NewDockAreas); _this->dumpLayout(); } //============================================================================ void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget* Widget, CDockAreaWidget* TargetArea) { auto DroppedDockWidget = qobject_cast(Widget); auto DroppedArea = qobject_cast(Widget); if (DroppedDockWidget) { CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); if (OldDockArea == TargetArea) { return; } if (OldDockArea) { OldDockArea->removeDockWidget(DroppedDockWidget); } TargetArea->insertDockWidget(0, DroppedDockWidget, true); } else { QList NewDockWidgets = DroppedArea->dockWidgets(); int NewCurrentIndex = DroppedArea->currentIndex(); for (int i = 0; i < NewDockWidgets.count(); ++i) { CDockWidget* DockWidget = NewDockWidgets[i]; TargetArea->insertDockWidget(i, DockWidget, false); } TargetArea->setCurrentIndex(NewCurrentIndex); DroppedArea->dockContainer()->removeDockArea(DroppedArea); DroppedArea->deleteLater(); } TargetArea->updateTitleBarVisibility(); return; } //============================================================================ void DockContainerWidgetPrivate::moveToNewSection(QWidget* Widget, CDockAreaWidget* TargetArea, DockWidgetArea area) { // Dropping into center means all dock widgets in the dropped floating // widget will become tabs of the drop area if (CenterDockWidgetArea == area) { moveIntoCenterOfSection(Widget, TargetArea); return; } CDockWidget* DroppedDockWidget = qobject_cast(Widget); CDockAreaWidget* DroppedDockArea = qobject_cast(Widget); CDockAreaWidget* NewDockArea; if (DroppedDockWidget) { NewDockArea = new CDockAreaWidget(DockManager, _this); CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); if (OldDockArea) { OldDockArea->removeDockWidget(DroppedDockWidget); } NewDockArea->addDockWidget(DroppedDockWidget); } else { DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea); NewDockArea = DroppedDockArea; } auto InsertParam = internal::dockAreaInsertParameters(area); QSplitter* TargetAreaSplitter = internal::findParent(TargetArea); int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); auto Sizes = TargetAreaSplitter->sizes(); if (TargetAreaSplitter->orientation() == InsertParam.orientation()) { int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), NewDockArea); updateSplitterHandles(TargetAreaSplitter); int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; Sizes[AreaIndex] = Size; Sizes.insert(AreaIndex, Size); } else { auto Sizes = TargetAreaSplitter->sizes(); int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); NewSplitter->addWidget(TargetArea); insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); updateSplitterHandles(NewSplitter); int Size = TargetAreaSize / 2; NewSplitter->setSizes({Size, Size}); TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); updateSplitterHandles(TargetAreaSplitter); } TargetAreaSplitter->setSizes(Sizes); addDockAreasToList({NewDockArea}); } //============================================================================ void DockContainerWidgetPrivate::updateSplitterHandles( QSplitter* splitter ) { if (!DockManager->centralWidget() || !splitter) { return; } for (int i = 0; i < splitter->count(); ++i) { splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0); } } //============================================================================ bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget* widget) { if (!DockManager->centralWidget()) { return true; } auto Area = qobject_cast(widget); if(Area) { return Area->isCentralWidgetArea(); } auto innerSplitter = qobject_cast(widget); if (innerSplitter) { return innerSplitter->isResizingWithContainer(); } return false; } //============================================================================ void DockContainerWidgetPrivate::moveToContainer(QWidget* Widget, DockWidgetArea area) { CDockWidget* DroppedDockWidget = qobject_cast(Widget); CDockAreaWidget* DroppedDockArea = qobject_cast(Widget); CDockAreaWidget* NewDockArea; if (DroppedDockWidget) { NewDockArea = new CDockAreaWidget(DockManager, _this); CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); if (OldDockArea) { OldDockArea->removeDockWidget(DroppedDockWidget); } NewDockArea->addDockWidget(DroppedDockWidget); } else { // We check, if we insert the dropped widget into the same place that // it already has and do nothing, if it is the same place. It would // also work without this check, but it looks nicer with the check // because there will be no layout updates auto Splitter = internal::findParent(DroppedDockArea); auto InsertParam = internal::dockAreaInsertParameters(area); if (Splitter == RootSplitter && InsertParam.orientation() == Splitter->orientation()) { if (InsertParam.append() && Splitter->lastWidget() == DroppedDockArea) { return; } else if (!InsertParam.append() && Splitter->firstWidget() == DroppedDockArea) { return; } } DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea); NewDockArea = DroppedDockArea; } addDockArea(NewDockArea, area); LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea; } //============================================================================ void DockContainerWidgetPrivate::addDockAreasToList(const QList NewDockAreas) { int CountBefore = DockAreas.count(); int NewAreaCount = NewDockAreas.count(); appendDockAreas(NewDockAreas); // If the user dropped a floating widget that contains only one single // visible dock area, then its title bar button TitleBarButtonUndock is // likely hidden. We need to ensure, that it is visible for (auto DockArea : NewDockAreas) { DockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); DockArea->titleBarButton(TitleBarButtonAutoHide)->setVisible(true); } // We need to ensure, that the dock area title bar is visible. The title bar // is invisible, if the dock are is a single dock area in a floating widget. if (1 == CountBefore) { DockAreas.at(0)->updateTitleBarVisibility(); } if (1 == NewAreaCount) { DockAreas.last()->updateTitleBarVisibility(); } emitDockAreasAdded(); } //============================================================================ void DockContainerWidgetPrivate::appendDockAreas(const QList NewDockAreas) { DockAreas.append(NewDockAreas); for (auto DockArea : NewDockAreas) { QObject::connect(DockArea, &CDockAreaWidget::viewToggled, _this, std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, this, std::placeholders::_1)); } } //============================================================================ void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter& s, QWidget* Widget) { QSplitter* Splitter = qobject_cast(Widget); if (Splitter) { s.writeStartElement("Splitter"); s.writeAttribute("Orientation", (Splitter->orientation() == Qt::Horizontal) ? "|" : "-"); s.writeAttribute("Count", QString::number(Splitter->count())); ADS_PRINT("NodeSplitter orient: " << Splitter->orientation() << " WidgetCont: " << Splitter->count()); for (int i = 0; i < Splitter->count(); ++i) { saveChildNodesState(s, Splitter->widget(i)); } s.writeStartElement("Sizes"); for (auto Size : Splitter->sizes()) { s.writeCharacters(QString::number(Size) + " "); } s.writeEndElement(); s.writeEndElement(); } else { CDockAreaWidget* DockArea = qobject_cast(Widget); if (DockArea) { DockArea->saveState(s); } } } void DockContainerWidgetPrivate::saveOverlayWidgetsState(QXmlStreamWriter& Stream) { for (const auto sideTabBar : SideTabBarWidgets.values()) { for (auto itemIndex = 0; itemIndex < sideTabBar->tabCount(); itemIndex++) { const auto sideTab = sideTabBar->tabAt(itemIndex); if (sideTab == nullptr) { continue; } const auto dockArea = sideTab->dockWidget()->dockAreaWidget(); dockArea->saveState(Stream); } } } //============================================================================ bool DockContainerWidgetPrivate::restoreSplitter(CDockingStateReader& s, QWidget*& CreatedWidget, bool Testing) { bool Ok; QString OrientationStr = s.attributes().value("Orientation").toString(); // Check if the orientation string is right if (!OrientationStr.startsWith("|") && !OrientationStr.startsWith("-")) { return false; } // The "|" shall indicate a vertical splitter handle which in turn means // a Horizontal orientation of the splitter layout. bool HorizontalSplitter = OrientationStr.startsWith("|"); // In version 0 we had a small bug. The "|" indicated a vertical orientation, // but this is wrong, because only the splitter handle is vertical, the // layout of the splitter is a horizontal layout. We fix this here if (s.fileVersion() == 0) { HorizontalSplitter = !HorizontalSplitter; } int Orientation = HorizontalSplitter ? Qt::Horizontal : Qt::Vertical; int WidgetCount = s.attributes().value("Count").toInt(&Ok); if (!Ok) { return false; } ADS_PRINT("Restore NodeSplitter Orientation: " << Orientation << " WidgetCount: " << WidgetCount); QSplitter* Splitter = nullptr; if (!Testing) { Splitter = newSplitter(static_cast(Orientation)); } bool Visible = false; QList Sizes; while (s.readNextStartElement()) { QWidget* ChildNode = nullptr; bool Result = true; if (s.name() == QLatin1String("Splitter")) { Result = restoreSplitter(s, ChildNode, Testing); } else if (s.name() == QLatin1String("Area")) { Result = restoreDockArea(s, ChildNode, Testing); } else if (s.name() == QLatin1String("Sizes")) { QString sSizes = s.readElementText().trimmed(); ADS_PRINT("Sizes: " << sSizes); QTextStream TextStream(&sSizes); while (!TextStream.atEnd()) { int value; TextStream >> value; Sizes.append(value); } } else { s.skipCurrentElement(); } if (!Result) { return false; } if (Testing || !ChildNode) { continue; } ADS_PRINT("ChildNode isVisible " << ChildNode->isVisible() << " isVisibleTo " << ChildNode->isVisibleTo(Splitter)); Splitter->addWidget(ChildNode); Visible |= ChildNode->isVisibleTo(Splitter); } if(!Testing) { updateSplitterHandles(Splitter); } if (Sizes.count() != WidgetCount) { return false; } if (!Testing) { if (!Splitter->count()) { delete Splitter; Splitter = nullptr; } else { Splitter->setSizes(Sizes); Splitter->setVisible(Visible); } CreatedWidget = Splitter; } else { CreatedWidget = nullptr; } return true; } //============================================================================ bool DockContainerWidgetPrivate::restoreOverlayDockArea(CDockingStateReader& s, CDockWidgetSideTab::SideTabBarArea area, bool Testing) { bool Ok; #ifdef ADS_DEBUG_PRINT int Tabs = s.attributes().value("Tabs").toInt(&Ok); if (!Ok) { return false; } #endif QString CurrentDockWidget = s.attributes().value("Current").toString(); ADS_PRINT("Restore NodeDockArea Tabs: " << Tabs << " Current: " << CurrentDockWidget); if (!COverlayDockContainer::areaExistsInConfig(area)) { return false; } CDockAreaWidget* DockArea = nullptr; if (!Testing) { const auto dockContainer = new COverlayDockContainer(DockManager, area, _this); if (!dockContainer->restoreState(s, Testing)) { return false; } dockContainer->hide(); DockArea = dockContainer->dockAreaWidget(); DockArea->updateAutoHideButtonCheckState(); DockArea->updateTitleBarButtonToolTip(); } while (s.readNextStartElement()) { if (s.name() != QLatin1String("Widget")) { continue; } auto ObjectName = s.attributes().value("Name"); if (ObjectName.isEmpty()) { return false; } bool Closed = s.attributes().value("Closed").toInt(&Ok); if (!Ok) { return false; } s.skipCurrentElement(); CDockWidget* DockWidget = DockManager->findDockWidget(ObjectName.toString()); if (!DockWidget || Testing) { continue; } ADS_PRINT("Dock Widget found - parent " << DockWidget->parent()); // We hide the DockArea here to prevent the short display (the flashing) // of the dock areas during application startup DockArea->hide(); DockWidget->setToggleViewActionChecked(!Closed); DockWidget->setClosedState(Closed); DockWidget->setProperty(internal::ClosedProperty, Closed); DockWidget->setProperty(internal::DirtyProperty, false); _this->sideTabBar(area)->insertSideTab(-1, DockWidget->sideTabWidget()); DockArea->overlayDockContainer()->addDockWidget(DockWidget); DockWidget->sideTabWidget()->updateStyle(); // Needed as the side tab widget get it's left/right property from the overlay dock container which was just added DockArea->overlayDockContainer()->toggleView(!Closed); } return true; } //============================================================================ bool DockContainerWidgetPrivate::restoreDockOrOverlayDockArea(CDockingStateReader& Stream, QWidget*& CreatedWidget, bool Testing) { bool Ok; const auto sideTabAreaValue = Stream.attributes().value("SideTabBarArea"); if (!sideTabAreaValue.isNull()) { auto sideTabBarArea = static_cast(sideTabAreaValue.toInt(&Ok)); if (!Ok) { return false; } return restoreOverlayDockArea(Stream, sideTabBarArea, Testing); } // If there's no SideTabBarArea value in the XML, or the value of SideTabBarArea is none, restore the dock area return restoreDockArea(Stream, CreatedWidget, Testing); } //============================================================================ bool DockContainerWidgetPrivate::restoreDockArea(CDockingStateReader& s, QWidget*& CreatedWidget, bool Testing) { bool Ok; #ifdef ADS_DEBUG_PRINT int Tabs = s.attributes().value("Tabs").toInt(&Ok); if (!Ok) { return false; } #endif QString CurrentDockWidget = s.attributes().value("Current").toString(); ADS_PRINT("Restore NodeDockArea Tabs: " << Tabs << " Current: " << CurrentDockWidget); CDockAreaWidget* DockArea = nullptr; if (!Testing) { DockArea = new CDockAreaWidget(DockManager, _this); const auto AllowedAreasAttribute = s.attributes().value("AllowedAreas"); if (!AllowedAreasAttribute.isEmpty()) { DockArea->setAllowedAreas((DockWidgetArea)AllowedAreasAttribute.toInt(nullptr, 16)); } const auto FlagsAttribute = s.attributes().value("Flags"); if (!FlagsAttribute.isEmpty()) { DockArea->setDockAreaFlags((CDockAreaWidget::DockAreaFlags)FlagsAttribute.toInt(nullptr, 16)); } } while (s.readNextStartElement()) { if (s.name() != QLatin1String("Widget")) { continue; } auto ObjectName = s.attributes().value("Name"); if (ObjectName.isEmpty()) { return false; } bool Closed = s.attributes().value("Closed").toInt(&Ok); if (!Ok) { return false; } s.skipCurrentElement(); CDockWidget* DockWidget = DockManager->findDockWidget(ObjectName.toString()); if (!DockWidget || Testing) { continue; } ADS_PRINT("Dock Widget found - parent " << DockWidget->parent()); // We hide the DockArea here to prevent the short display (the flashing) // of the dock areas during application startup DockArea->hide(); DockArea->addDockWidget(DockWidget); DockWidget->setToggleViewActionChecked(!Closed); DockWidget->setClosedState(Closed); DockWidget->setProperty(internal::ClosedProperty, Closed); DockWidget->setProperty(internal::DirtyProperty, false); } if (Testing) { return true; } if (!DockArea->dockWidgetsCount()) { delete DockArea; DockArea = nullptr; } else { DockArea->setProperty("currentDockWidget", CurrentDockWidget); appendDockAreas({DockArea}); } CreatedWidget = DockArea; return true; } //============================================================================ bool DockContainerWidgetPrivate::restoreChildNodes(CDockingStateReader& s, QWidget*& CreatedWidget, bool Testing) { bool Result = true; while (s.readNextStartElement()) { if (s.name() == QLatin1String("Splitter")) { Result = restoreSplitter(s, CreatedWidget, Testing); ADS_PRINT("Splitter"); } else if (s.name() == QLatin1String("Area")) { Result = restoreDockOrOverlayDockArea(s, CreatedWidget, Testing); ADS_PRINT("DockAreaWidget"); } else { s.skipCurrentElement(); ADS_PRINT("Unknown element"); } } return Result; } //============================================================================ CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, CDockWidget* Dockwidget) { CDockAreaWidget* NewDockArea = new CDockAreaWidget(DockManager, _this); NewDockArea->addDockWidget(Dockwidget); addDockArea(NewDockArea, area); NewDockArea->updateTitleBarVisibility(); LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea; return NewDockArea; } //============================================================================ void DockContainerWidgetPrivate::addDockArea(CDockAreaWidget* NewDockArea, DockWidgetArea area) { auto InsertParam = internal::dockAreaInsertParameters(area); // As long as we have only one dock area in the splitter we can adjust // its orientation if (DockAreas.count() <= 1) { RootSplitter->setOrientation(InsertParam.orientation()); } QSplitter* Splitter = RootSplitter; if (Splitter->orientation() == InsertParam.orientation()) { insertWidgetIntoSplitter(Splitter, NewDockArea, InsertParam.append()); updateSplitterHandles(Splitter); if (Splitter->isHidden()) { Splitter->show(); } } else { QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); if (InsertParam.append()) { QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); NewSplitter->addWidget(NewDockArea); updateSplitterHandles(NewSplitter); delete li; } else { NewSplitter->addWidget(NewDockArea); QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); updateSplitterHandles(NewSplitter); delete li; } RootSplitter = NewSplitter; } addDockAreasToList({NewDockArea}); } //============================================================================ void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget* widget) { #if defined(QT_DEBUG) QSplitter* Splitter = qobject_cast(widget); QByteArray buf; buf.fill(' ', level * 4); if (Splitter) { #ifdef ADS_DEBUG_PRINT qDebug("%sSplitter %s v: %s c: %s", (const char*)buf, (Splitter->orientation() == Qt::Vertical) ? "--" : "|", Splitter->isHidden() ? " " : "v", QString::number(Splitter->count()).toStdString().c_str()); std::cout << (const char*)buf << "Splitter " << ((Splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " << (Splitter->isHidden() ? " " : "v") << " " << QString::number(Splitter->count()).toStdString() << std::endl; #endif for (int i = 0; i < Splitter->count(); ++i) { dumpRecursive(level + 1, Splitter->widget(i)); } } else { CDockAreaWidget* DockArea = qobject_cast(widget); if (!DockArea) { return; } #ifdef ADS_DEBUG_PRINT qDebug("%sDockArea", (const char*)buf); std::cout << (const char*)buf << (DockArea->isHidden() ? " " : "v") << (DockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea " << "[hs: " << DockArea->sizePolicy().horizontalStretch() << ", vs: " << DockArea->sizePolicy().verticalStretch() << "]" << std::endl; buf.fill(' ', (level + 1) * 4); for (int i = 0; i < DockArea->dockWidgetsCount(); ++i) { std::cout << (const char*)buf << (i == DockArea->currentIndex() ? "*" : " "); CDockWidget* DockWidget = DockArea->dockWidget(i); std::cout << (DockWidget->isHidden() ? " " : "v"); std::cout << (DockWidget->isClosed() ? "c" : " ") << " "; std::cout << DockWidget->windowTitle().toStdString() << std::endl; } #endif } #else Q_UNUSED(level); Q_UNUSED(widget); #endif } //============================================================================ CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, CDockWidget* Dockwidget, CDockAreaWidget* TargetDockArea) { if (CenterDockWidgetArea == area) { TargetDockArea->addDockWidget(Dockwidget); TargetDockArea->updateTitleBarVisibility(); return TargetDockArea; } CDockAreaWidget* NewDockArea = new CDockAreaWidget(DockManager, _this); NewDockArea->addDockWidget(Dockwidget); auto InsertParam = internal::dockAreaInsertParameters(area); QSplitter* TargetAreaSplitter = internal::findParent(TargetDockArea); int index = TargetAreaSplitter ->indexOf(TargetDockArea); if (TargetAreaSplitter->orientation() == InsertParam.orientation()) { ADS_PRINT("TargetAreaSplitter->orientation() == InsertParam.orientation()"); TargetAreaSplitter->insertWidget(index + InsertParam.insertOffset(), NewDockArea); updateSplitterHandles(TargetAreaSplitter); // do nothing, if flag is not enabled if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) { adjustSplitterSizesOnInsertion(TargetAreaSplitter); } } else { ADS_PRINT("TargetAreaSplitter->orientation() != InsertParam.orientation()"); auto TargetAreaSizes = TargetAreaSplitter->sizes(); QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); NewSplitter->addWidget(TargetDockArea); insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); updateSplitterHandles(NewSplitter); TargetAreaSplitter->insertWidget(index, NewSplitter); updateSplitterHandles(TargetAreaSplitter); if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) { TargetAreaSplitter->setSizes(TargetAreaSizes); adjustSplitterSizesOnInsertion(NewSplitter); } } addDockAreasToList({NewDockArea}); return NewDockArea; } //============================================================================ CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *parent) : QFrame(parent), d(new DockContainerWidgetPrivate(this)) { d->DockManager = DockManager; d->isFloating = floatingWidget() != nullptr; d->Layout = new QGridLayout(); d->Layout->setContentsMargins(0, 0, 0, 0); d->Layout->setSpacing(0); d->Layout->setColumnStretch(1, 1); d->Layout->setRowStretch(0, 1); setLayout(d->Layout); // The function d->newSplitter() accesses the config flags from dock // manager which in turn requires a properly constructed dock manager. // If this dock container is the dock manager, then it is not properly // constructed yet because this base class constructor is called before // the constructor of the DockManager private class if (DockManager != this) { d->DockManager->registerDockContainer(this); createRootSplitter(); createSideTabBarWidgets(); } } //============================================================================ CDockContainerWidget::~CDockContainerWidget() { if (d->DockManager) { d->DockManager->removeDockContainer(this); } auto OverlayWidgets = d->OverlayWidgets; for (auto OverlayWidget : OverlayWidgets) { delete OverlayWidget; } delete d; } //============================================================================ CDockAreaWidget* CDockContainerWidget::addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, CDockAreaWidget* DockAreaWidget) { CDockAreaWidget* OldDockArea = Dockwidget->dockAreaWidget(); if (OldDockArea) { OldDockArea->removeDockWidget(Dockwidget); } Dockwidget->setDockManager(d->DockManager); if (DockAreaWidget) { return d->addDockWidgetToDockArea(area, Dockwidget, DockAreaWidget); } else { return d->addDockWidgetToContainer(area, Dockwidget); } } //============================================================================ COverlayDockContainer* CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer(CDockWidgetSideTab::SideTabBarArea area, CDockWidget* DockWidget, CDockWidget::eOverlayInsertOrder insertOrder) { if (d->DockManager != DockWidget->dockManager()) { DockWidget->setDockManager(d->DockManager); // Overlay Dock Container needs a valid dock manager } if (!COverlayDockContainer::areaExistsInConfig(area)) { Q_ASSERT_X(false, "CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer", "Requested area does not exist in config"); return nullptr; } DockWidget->sideTabWidget()->updateOrientationAndSpacing(area); sideTabBar(area)->insertSideTab(insertOrder == CDockWidget::First ? 0 : -1, DockWidget->sideTabWidget()); DockWidget->sideTabWidget()->show(); const auto dockContainer = new COverlayDockContainer(DockWidget, area, this); dockContainer->hide(); d->DockManager->dockFocusController()->clearDockWidgetFocus(DockWidget); return dockContainer; } //============================================================================ CDockWidgetSideTab::SideTabBarArea CDockContainerWidget::getDockAreaPosition(CDockAreaWidget* DockAreaWidget) { // Handle bottom case // It's bottom if the width is wider than the height, and if it's below 50% of the window const auto dockWidgetFrameGeometry = DockAreaWidget->frameGeometry(); const auto splitterCenter = rootSplitter()->mapToGlobal(rootSplitter()->frameGeometry().center()); const auto a = dockWidgetFrameGeometry.width(); const auto b = dockWidgetFrameGeometry.height(); const auto c = DockAreaWidget->mapToGlobal(dockWidgetFrameGeometry.topLeft()).y(); const auto d = splitterCenter.y(); const auto e = CDockManager::testConfigFlag(CDockManager::DockContainerHasBottomSideBar); if (dockWidgetFrameGeometry.width() > dockWidgetFrameGeometry.height() && DockAreaWidget->mapToGlobal(dockWidgetFrameGeometry.topLeft()).y() > splitterCenter.y() && CDockManager::testConfigFlag(CDockManager::DockContainerHasBottomSideBar)) { return CDockWidgetSideTab::Bottom; } // Then handle left and right const auto dockWidgetCenter = DockAreaWidget->mapToGlobal(dockWidgetFrameGeometry.center()); const auto calculatedPosition = dockWidgetCenter.x() <= splitterCenter.x() ? CDockWidgetSideTab::LeftTop : CDockWidgetSideTab::RightTop; if (calculatedPosition == CDockWidgetSideTab::RightTop) { if (CDockManager::testConfigFlag(CDockManager::DockContainerHasRightSideBar)) { return CDockWidgetSideTab::RightTop; } if (CDockManager::testConfigFlag(CDockManager::DockContainerHasLeftSideBar)) { return CDockWidgetSideTab::LeftTop; } return CDockWidgetSideTab::Bottom; } if (calculatedPosition == CDockWidgetSideTab::LeftTop) { if (CDockManager::testConfigFlag(CDockManager::DockContainerHasLeftSideBar)) { // todo: cleanup - revert return CDockWidgetSideTab::LeftBottom; } if (CDockManager::testConfigFlag(CDockManager::DockContainerHasRightSideBar)) { return CDockWidgetSideTab::RightTop; } return CDockWidgetSideTab::Bottom; } Q_ASSERT_X(false, "CDockContainerWidget::getDockAreaPosition", "Unhandled branch. All positions should be accounted for."); return CDockWidgetSideTab::LeftTop; } //============================================================================ void CDockContainerWidget::removeDockWidget(CDockWidget* Dockwidget) { CDockAreaWidget* Area = Dockwidget->dockAreaWidget(); if (Area) { Area->removeDockWidget(Dockwidget); } } //============================================================================ unsigned int CDockContainerWidget::zOrderIndex() const { return d->zOrderIndex; } //============================================================================ bool CDockContainerWidget::isInFrontOf(CDockContainerWidget* Other) const { return this->zOrderIndex() > Other->zOrderIndex(); } //============================================================================ bool CDockContainerWidget::event(QEvent *e) { bool Result = QWidget::event(e); if (e->type() == QEvent::WindowActivate) { d->zOrderIndex = ++zOrderCounter; } else if (e->type() == QEvent::Show && !d->zOrderIndex) { d->zOrderIndex = ++zOrderCounter; } return Result; } //============================================================================ void CDockContainerWidget::deleteOverlayWidgets() { for (auto OverlayWidget : d->OverlayWidgets) { OverlayWidget->cleanupAndDelete(); } d->OverlayWidgets.clear(); } //============================================================================ QList CDockContainerWidget::overlayWidgets() const { return d->OverlayWidgets; } //============================================================================ void CDockContainerWidget::addDockArea(CDockAreaWidget* DockAreaWidget, DockWidgetArea area) { CDockContainerWidget* Container = DockAreaWidget->dockContainer(); if (Container && Container != this) { Container->removeDockArea(DockAreaWidget); } d->addDockArea(DockAreaWidget, area); } //============================================================================ void CDockContainerWidget::removeDockArea(CDockAreaWidget* area) { ADS_PRINT("CDockContainerWidget::removeDockArea"); area->disconnect(this); d->DockAreas.removeAll(area); CDockSplitter* Splitter = internal::findParent(area); // Remove are from parent splitter and recursively hide tree of parent // splitters if it has no visible content area->setParent(nullptr); internal::hideEmptyParentSplitters(Splitter); // Remove this area from cached areas auto p = std::find(std::begin(d->LastAddedAreaCache), std::end(d->LastAddedAreaCache), area); if (p != std::end(d->LastAddedAreaCache)) { *p = nullptr; } if (area->isOverlayed()) { // Removing an area from an overlay widget implies deleting the whole overlay widget // So cleanup will be done when the overlay widget is deleted // Note: there is no parent splitter CDockWidget* TopLevelWidget = topLevelDockWidget(); // Updated the title bar visibility of the dock widget if there is only // one single visible dock widget CDockWidget::emitTopLevelEventForWidget(TopLevelWidget, true); dumpLayout(); d->emitDockAreasRemoved(); area->setOverlayDockContainer(nullptr); area->updateAutoHideButtonCheckState(); area->updateTitleBarButtonToolTip(); return; } // If splitter has more than 1 widgets, we are finished and can leave if (Splitter->count() > 1) { goto emitAndExit; } // If this is the RootSplitter we need to remove empty splitters to // avoid too many empty splitters if (Splitter == d->RootSplitter) { ADS_PRINT("Removed from RootSplitter"); // If splitter is empty, we are finished if (!Splitter->count()) { Splitter->hide(); goto emitAndExit; } QWidget* widget = Splitter->widget(0); QSplitter* ChildSplitter = qobject_cast(widget); // If the one and only content widget of the splitter is not a splitter // then we are finished if (!ChildSplitter) { goto emitAndExit; } // We replace the superfluous RootSplitter with the ChildSplitter ChildSplitter->setParent(nullptr); QLayoutItem* li = d->Layout->replaceWidget(Splitter, ChildSplitter); d->RootSplitter = ChildSplitter; delete li; ADS_PRINT("RootSplitter replaced by child splitter"); } else if (Splitter->count() == 1) { ADS_PRINT("Replacing splitter with content"); QSplitter* ParentSplitter = internal::findParent(Splitter); auto Sizes = ParentSplitter->sizes(); QWidget* widget = Splitter->widget(0); widget->setParent(this); internal::replaceSplitterWidget(ParentSplitter, Splitter, widget); ParentSplitter->setSizes(Sizes); } delete Splitter; Splitter = nullptr; emitAndExit: updateSplitterHandles(Splitter); CDockWidget* TopLevelWidget = topLevelDockWidget(); // Updated the title bar visibility of the dock widget if there is only // one single visible dock widget CDockWidget::emitTopLevelEventForWidget(TopLevelWidget, true); dumpLayout(); d->emitDockAreasRemoved(); } //============================================================================ CDockAreaWidget* CDockContainerWidget::dockAreaAt(const QPoint& GlobalPos) const { for (const auto& DockArea : d->DockAreas) { if (DockArea->isVisible() && DockArea->rect().contains(DockArea->mapFromGlobal(GlobalPos))) { return DockArea; } } return nullptr; } //============================================================================ CDockAreaWidget* CDockContainerWidget::dockArea(int Index) const { return (Index < dockAreaCount()) ? d->DockAreas[Index] : nullptr; } //============================================================================ bool CDockContainerWidget::isFloating() const { return d->isFloating; } //============================================================================ int CDockContainerWidget::dockAreaCount() const { return d->DockAreas.count(); } //============================================================================ int CDockContainerWidget::visibleDockAreaCount() const { int Result = 0; for (auto DockArea : d->DockAreas) { Result += DockArea->isHidden() ? 0 : 1; } return Result; // TODO Cache or precalculate this to speed it up because it is used during // movement of floating widget //return d->visibleDockAreaCount(); } //============================================================================ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWidget, const QPoint& TargetPos) { ADS_PRINT("CDockContainerWidget::dropFloatingWidget"); CDockWidget* SingleDroppedDockWidget = FloatingWidget->topLevelDockWidget(); CDockWidget* SingleDockWidget = topLevelDockWidget(); CDockAreaWidget* DockArea = dockAreaAt(TargetPos); auto dropArea = InvalidDockWidgetArea; auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(); bool Dropped = false; auto overlayWidgets = FloatingWidget->dockContainer()->overlayWidgets(); for (const auto overlayWidget : overlayWidgets) { createAndInitializeDockWidgetOverlayContainer(overlayWidget->sideTabBarArea(), overlayWidget->dockWidget(), overlayWidget->dockWidget()->overlayInsertOrder()); } if (DockArea) { auto dropOverlay = d->DockManager->dockAreaOverlay(); dropOverlay->setAllowedAreas(DockArea->allowedAreas()); dropArea = dropOverlay->showOverlay(DockArea); if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) { dropArea = InvalidDockWidgetArea; } if (dropArea != InvalidDockWidgetArea) { ADS_PRINT("Dock Area Drop Content: " << dropArea); d->dropIntoSection(FloatingWidget, DockArea, dropArea); Dropped = true; } } // mouse is over container if (InvalidDockWidgetArea == dropArea) { dropArea = ContainerDropArea; ADS_PRINT("Container Drop Content: " << dropArea); if (dropArea != InvalidDockWidgetArea) { d->dropIntoContainer(FloatingWidget, dropArea); Dropped = true; } } if (Dropped) { // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 FloatingWidget->hideAndDeleteLater(); // If we dropped a floating widget with only one single dock widget, then we // drop a top level widget that changes from floating to docked now CDockWidget::emitTopLevelEventForWidget(SingleDroppedDockWidget, false); // If there was a top level widget before the drop, then it is not top // level widget anymore CDockWidget::emitTopLevelEventForWidget(SingleDockWidget, false); } window()->activateWindow(); if (SingleDroppedDockWidget) { d->DockManager->notifyWidgetOrAreaRelocation(SingleDroppedDockWidget); } d->DockManager->notifyFloatingWidgetDrop(FloatingWidget); } //============================================================================ void CDockContainerWidget::dropWidget(QWidget* Widget, DockWidgetArea DropArea, CDockAreaWidget* TargetAreaWidget) { CDockWidget* SingleDockWidget = topLevelDockWidget(); if (TargetAreaWidget) { d->moveToNewSection(Widget, TargetAreaWidget, DropArea); } else { d->moveToContainer(Widget, DropArea); } // If there was a top level widget before the drop, then it is not top // level widget anymore CDockWidget::emitTopLevelEventForWidget(SingleDockWidget, false); window()->activateWindow(); d->DockManager->notifyWidgetOrAreaRelocation(Widget); } //============================================================================ QList CDockContainerWidget::openedDockAreas() const { QList Result; for (auto DockArea : d->DockAreas) { if (!DockArea->isHidden()) { Result.append(DockArea); } } return Result; } //============================================================================ QList CDockContainerWidget::openedDockWidgets() const { QList DockWidgetList; for (auto DockArea : d->DockAreas) { if (!DockArea->isHidden()) { DockWidgetList.append(DockArea->openedDockWidgets()); } } return DockWidgetList; } //============================================================================ bool CDockContainerWidget::hasOpenDockAreas() const { for (auto DockArea : d->DockAreas) { if (!DockArea->isHidden()) { return true; } } return false; } //============================================================================ void CDockContainerWidget::saveState(QXmlStreamWriter& s) const { ADS_PRINT("CDockContainerWidget::saveState isFloating " << isFloating()); s.writeStartElement("Container"); s.writeAttribute("Floating", QString::number(isFloating() ? 1 : 0)); if (isFloating()) { CFloatingDockContainer* FloatingWidget = floatingWidget(); QByteArray Geometry = FloatingWidget->saveGeometry(); #if QT_VERSION < 0x050900 s.writeTextElement("Geometry", qByteArrayToHex(Geometry, ' ')); #else s.writeTextElement("Geometry", Geometry.toHex(' ')); #endif } d->saveChildNodesState(s, d->RootSplitter); d->saveOverlayWidgetsState(s); s.writeEndElement(); } //============================================================================ bool CDockContainerWidget::restoreState(CDockingStateReader& s, bool Testing) { bool IsFloating = s.attributes().value("Floating").toInt(); ADS_PRINT("Restore CDockContainerWidget Floating" << IsFloating); QWidget*NewRootSplitter {}; if (!Testing) { d->VisibleDockAreaCount = -1;// invalidate the dock area count d->DockAreas.clear(); std::fill(std::begin(d->LastAddedAreaCache),std::end(d->LastAddedAreaCache), nullptr); } if (IsFloating) { ADS_PRINT("Restore floating widget"); if (!s.readNextStartElement() || s.name() != QLatin1String("Geometry")) { return false; } QByteArray GeometryString = s.readElementText(CDockingStateReader::ErrorOnUnexpectedElement).toLocal8Bit(); QByteArray Geometry = QByteArray::fromHex(GeometryString); if (Geometry.isEmpty()) { return false; } if (!Testing) { CFloatingDockContainer* FloatingWidget = floatingWidget(); if (FloatingWidget) { FloatingWidget->restoreGeometry(Geometry); } } } if (!d->restoreChildNodes(s, NewRootSplitter, Testing)) { return false; } if (Testing) { return true; } // If the root splitter is empty, rostoreChildNodes returns a 0 pointer // and we need to create a new empty root splitter if (!NewRootSplitter) { NewRootSplitter = d->newSplitter(Qt::Horizontal); } d->Layout->replaceWidget(d->RootSplitter, NewRootSplitter); QSplitter* OldRoot = d->RootSplitter; d->RootSplitter = qobject_cast(NewRootSplitter); OldRoot->deleteLater(); return true; } //============================================================================ QSplitter* CDockContainerWidget::rootSplitter() const { return d->RootSplitter; } //============================================================================ void CDockContainerWidget::createRootSplitter() { if (d->RootSplitter) { return; } d->RootSplitter = d->newSplitter(Qt::Horizontal); d->Layout->addWidget(d->RootSplitter, 0, 1); // Add it to the center - the 0 and 2 indexes are used for the SideTabBar widgets } //============================================================================ void CDockContainerWidget::createSideTabBarWidgets() { if (CDockManager::testConfigFlag(CDockManager::DockContainerHasLeftSideBar)) { auto leftLayout = new QVBoxLayout(); d->SideTabBarWidgets[CDockWidgetSideTab::LeftTop] = new CSideTabBar(this, Qt::Vertical); leftLayout->addWidget(d->SideTabBarWidgets[CDockWidgetSideTab::LeftTop]); leftLayout->addStretch(1); d->SideTabBarWidgets[CDockWidgetSideTab::LeftBottom] = new CSideTabBar(this, Qt::Vertical); leftLayout->addWidget(d->SideTabBarWidgets[CDockWidgetSideTab::LeftBottom]); d->Layout->addLayout(leftLayout, 0, 0); } if (CDockManager::testConfigFlag(CDockManager::DockContainerHasRightSideBar)) { auto rightLayout = new QVBoxLayout(); d->SideTabBarWidgets[CDockWidgetSideTab::RightTop] = new CSideTabBar(this, Qt::Vertical); rightLayout->addWidget(d->SideTabBarWidgets[CDockWidgetSideTab::RightTop]); rightLayout->addStretch(1); d->SideTabBarWidgets[CDockWidgetSideTab::RightBottom] = new CSideTabBar(this, Qt::Vertical); rightLayout->addWidget(d->SideTabBarWidgets[CDockWidgetSideTab::RightBottom]); d->Layout->addLayout(rightLayout, 0, 2); } if (CDockManager::testConfigFlag(CDockManager::DockContainerHasBottomSideBar)) { d->SideTabBarWidgets[CDockWidgetSideTab::Bottom] = new CSideTabBar(this, Qt::Horizontal); d->Layout->addWidget(d->SideTabBarWidgets[CDockWidgetSideTab::Bottom], 1, 1); } } //============================================================================ void CDockContainerWidget::dumpLayout() { #if (ADS_DEBUG_LEVEL > 0) qDebug("\n\nDumping layout --------------------------"); std::cout << "\n\nDumping layout --------------------------" << std::endl; d->dumpRecursive(0, d->RootSplitter); qDebug("--------------------------\n\n"); std::cout << "--------------------------\n\n" << std::endl; #endif } //============================================================================ CDockAreaWidget* CDockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const { return d->LastAddedAreaCache[areaIdToIndex(area)]; } //============================================================================ bool CDockContainerWidget::hasTopLevelDockWidget() const { auto DockAreas = openedDockAreas(); if (DockAreas.count() != 1) { return false; } return DockAreas[0]->openDockWidgetsCount() == 1; } //============================================================================ CDockWidget* CDockContainerWidget::topLevelDockWidget() const { auto TopLevelDockArea = topLevelDockArea(); if (!TopLevelDockArea) { return nullptr; } auto DockWidgets = TopLevelDockArea->openedDockWidgets(); if (DockWidgets.count() != 1) { return nullptr; } return DockWidgets[0]; } //============================================================================ CDockAreaWidget* CDockContainerWidget::topLevelDockArea() const { auto DockAreas = openedDockAreas(); if (DockAreas.count() != 1) { return nullptr; } return DockAreas[0]; } //============================================================================ QList CDockContainerWidget::dockWidgets() const { QList Result; for (const auto DockArea : d->DockAreas) { Result.append(DockArea->dockWidgets()); } return Result; } //============================================================================ void CDockContainerWidget::updateSplitterHandles(QSplitter* splitter) { d->updateSplitterHandles(splitter); } //============================================================================ void CDockContainerWidget::registerOverlayWidget(COverlayDockContainer* OverlayWidget) { d->OverlayWidgets.append(OverlayWidget); Q_EMIT overlayWidgetCreated(OverlayWidget); ADS_PRINT("d->OverlayWidgets.count() " << d->OverlayWidgets.count()); } //============================================================================ void CDockContainerWidget::removeOverlayWidget(COverlayDockContainer* OverlayWidget) { d->OverlayWidgets.removeAll(OverlayWidget); } //============================================================================ CDockWidget::DockWidgetFeatures CDockContainerWidget::features() const { CDockWidget::DockWidgetFeatures Features(CDockWidget::AllDockWidgetFeatures); for (const auto DockArea : d->DockAreas) { Features &= DockArea->features(); } return Features; } //============================================================================ CFloatingDockContainer* CDockContainerWidget::floatingWidget() const { return internal::findParent(this); } //============================================================================ void CDockContainerWidget::closeOtherAreas(CDockAreaWidget* KeepOpenArea) { for (const auto DockArea : d->DockAreas) { if (DockArea == KeepOpenArea) { continue; } if (!DockArea->features(BitwiseAnd).testFlag(CDockWidget::DockWidgetClosable)) { continue; } // We do not close areas with widgets with custom close handling if (DockArea->features(BitwiseOr).testFlag(CDockWidget::CustomCloseHandling)) { continue; } DockArea->closeArea(); } } //============================================================================ CSideTabBar* CDockContainerWidget::sideTabBar(CDockWidgetSideTab::SideTabBarArea area) const { return d->SideTabBarWidgets[area]; } } // namespace ads //--------------------------------------------------------------------------- // EOF DockContainerWidget.cpp