1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 Uwe Kindler
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 or (at your option) any later version.
19 ** The licenses are as published by the Free Software Foundation
20 ** and appearing in the file LICENSE.LGPLv21 included in the packaging
21 ** of this file. Please review the following information to ensure
22 ** the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 3 or (at your option) any later version
28 ** approved by the KDE Free Qt Foundation. The licenses are as published by
29 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
30 ** included in the packaging of this file. Please review the following
31 ** information to ensure the GNU General Public License requirements will
32 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
33 **
34 ****************************************************************************/
35 
36 #include "dockcontainerwidget.h"
37 
38 #include "ads_globals.h"
39 #include "dockareawidget.h"
40 #include "dockingstatereader.h"
41 #include "dockmanager.h"
42 #include "dockoverlay.h"
43 #include "docksplitter.h"
44 #include "dockwidget.h"
45 #include "floatingdockcontainer.h"
46 
47 #include <QAbstractButton>
48 #include <QDebug>
49 #include <QEvent>
50 #include <QGridLayout>
51 #include <QList>
52 #include <QLoggingCategory>
53 #include <QPointer>
54 #include <QVariant>
55 #include <QXmlStreamWriter>
56 
57 #include <functional>
58 #include <iostream>
59 
60 static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
61 
62 namespace ADS
63 {
64     static unsigned int zOrderCounter = 0;
65 
66     enum eDropMode {
67         DropModeIntoArea,      ///< drop widget into a dock area
68         DropModeIntoContainer, ///< drop into container
69         DropModeInvalid        ///< invalid mode - do not drop
70     };
71 
72     /**
73      * Converts dock area ID to an index for array access
74      */
areaIdToIndex(DockWidgetArea area)75     static int areaIdToIndex(DockWidgetArea area)
76     {
77         switch (area) {
78         case LeftDockWidgetArea:
79             return 0;
80         case RightDockWidgetArea:
81             return 1;
82         case TopDockWidgetArea:
83             return 2;
84         case BottomDockWidgetArea:
85             return 3;
86         case CenterDockWidgetArea:
87             return 4;
88         default:
89             return 4;
90         }
91     }
92 
93     /**
94      * Helper function to ease insertion of dock area into splitter
95      */
insertWidgetIntoSplitter(QSplitter * splitter,QWidget * widget,bool append)96     static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append)
97     {
98         if (append)
99             splitter->addWidget(widget);
100         else
101             splitter->insertWidget(0, widget);
102     }
103 
104     /**
105      * Private data class of DockContainerWidget class (pimpl)
106      */
107     class DockContainerWidgetPrivate
108     {
109     public:
110         DockContainerWidget *q;
111         QPointer<DockManager> m_dockManager;
112         unsigned int m_zOrderIndex = 0;
113         QList<DockAreaWidget *> m_dockAreas;
114         QGridLayout *m_layout = nullptr;
115         QSplitter *m_rootSplitter = nullptr;
116         bool m_isFloating = false;
117         DockAreaWidget *m_lastAddedAreaCache[5];
118         int m_visibleDockAreaCount = -1;
119         DockAreaWidget *m_topLevelDockArea = nullptr;
120 
121         /**
122          * Private data constructor
123          */
124         DockContainerWidgetPrivate(DockContainerWidget *parent);
125 
126         /**
127          * Adds dock widget to container and returns the dock area that contains
128          * the inserted dock widget
129          */
130         DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget);
131 
132         /**
133          * Adds dock widget to a existing DockWidgetArea
134          */
135         DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area,
136                                                 DockWidget *dockWidget,
137                                                 DockAreaWidget *targetDockArea);
138 
139         /**
140          * Add dock area to this container
141          */
142         void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea);
143 
144         /**
145          * Drop floating widget into container
146          */
147         void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area);
148 
149         /**
150          * Drop floating widget into dock area
151          */
152         void dropIntoSection(FloatingDockContainer *floatingWidget,
153                              DockAreaWidget *targetArea,
154                              DockWidgetArea area);
155 
156         /**
157          * Moves the dock widget or dock area given in Widget parameter to a
158          * new dock widget area
159          */
160         void moveToNewSection(QWidget *widget, DockAreaWidget *targetArea, DockWidgetArea area);
161 
162         /**
163          * Moves the dock widget or dock area given in Widget parameter to a
164          * a dock area in container
165          */
166         void moveToContainer(QWidget *widget, DockWidgetArea area);
167 
168         /**
169          * Creates a new tab for a widget dropped into the center of a section
170          */
171         void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget,
172                                      DockAreaWidget *targetArea);
173 
174         /**
175          * Creates a new tab for a widget dropped into the center of a section
176          */
177         void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea);
178 
179         /**
180          * Adds new dock areas to the internal dock area list
181          */
182         void addDockAreasToList(const QList<DockAreaWidget *> newDockAreas);
183 
184         /**
185          * Wrapper function for DockAreas append, that ensures that dock area signals
186          * are properly connected to dock container slots
187          */
188         void appendDockAreas(const QList<DockAreaWidget *> newDockAreas);
189 
190         /**
191          * Save state of child nodes
192          */
193         void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget);
194 
195         /**
196          * Restore state of child nodes.
197          * \param[in] Stream The data stream that contains the serialized state
198          * \param[out] CreatedWidget The widget created from parsed data or 0 if
199          * the parsed widget was an empty splitter
200          * \param[in] Testing If Testing is true, only the stream data is
201          * parsed without modifiying anything.
202          */
203         bool restoreChildNodes(DockingStateReader &stateReader,
204                                QWidget *&createdWidget,
205                                bool testing);
206 
207         /**
208          * Restores a splitter.
209          * \see restoreChildNodes() for details
210          */
211         bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing);
212 
213         /**
214          * Restores a dock area.
215          * \see restoreChildNodes() for details
216          */
217         bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing);
218 
219         /**
220          * Helper function for recursive dumping of layout
221          */
222         void dumpRecursive(int level, QWidget *widget) const;
223 
224         /**
225          * Calculate the drop mode from the given target position
226          */
227         eDropMode getDropMode(const QPoint &targetPosition);
228 
229         /**
230          * Initializes the visible dock area count variable if it is not initialized
231          * yet
232          */
initVisibleDockAreaCount()233         void initVisibleDockAreaCount()
234         {
235             if (m_visibleDockAreaCount > -1)
236                 return;
237 
238             m_visibleDockAreaCount = 0;
239             for (auto dockArea : qAsConst(m_dockAreas))
240                 m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1;
241         }
242 
243         /**
244          * Access function for the visible dock area counter
245          */
visibleDockAreaCount()246         int visibleDockAreaCount()
247         {
248             // Lazy initialization - we initialize the m_visibleDockAreaCount variable
249             // on first use
250             initVisibleDockAreaCount();
251             return m_visibleDockAreaCount;
252         }
253 
254         /**
255          * The visible dock area count changes, if dock areas are remove, added or
256          * when its view is toggled
257          */
258         void onVisibleDockAreaCountChanged();
259 
emitDockAreasRemoved()260         void emitDockAreasRemoved()
261         {
262             onVisibleDockAreaCountChanged();
263             emit q->dockAreasRemoved();
264         }
265 
emitDockAreasAdded()266         void emitDockAreasAdded()
267         {
268             onVisibleDockAreaCountChanged();
269             emit q->dockAreasAdded();
270         }
271 
272         /**
273          * Helper function for creation of new splitter
274          */
createSplitter(Qt::Orientation orientation,QWidget * parent=nullptr)275         DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr)
276         {
277             auto *splitter = new DockSplitter(orientation, parent);
278             splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize));
279             splitter->setChildrenCollapsible(false);
280             return splitter;
281         }
282 
283         /**
284          * Ensures equal distribution of the sizes of a splitter if an dock widget
285          * is inserted from code
286          */
adjustSplitterSizesOnInsertion(QSplitter * splitter,qreal lastRatio=1.0)287         void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0)
288         {
289             const int areaSize = (splitter->orientation() == Qt::Horizontal) ? splitter->width()
290                                                                              : splitter->height();
291             auto splitterSizes = splitter->sizes();
292 
293             const qreal totalRatio = splitterSizes.size() - 1.0 + lastRatio;
294             for (int i = 0; i < splitterSizes.size() - 1; ++i)
295                 splitterSizes[i] = areaSize / totalRatio;
296 
297             splitterSizes.back() = areaSize * lastRatio / totalRatio;
298             splitter->setSizes(splitterSizes);
299         }
300 
onDockAreaViewToggled(bool visible)301         void onDockAreaViewToggled(bool visible)
302         {
303             DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(q->sender());
304             m_visibleDockAreaCount += visible ? 1 : -1;
305             onVisibleDockAreaCountChanged();
306             emit q->dockAreaViewToggled(dockArea, visible);
307         }
308     }; // struct DockContainerWidgetPrivate
309 
DockContainerWidgetPrivate(DockContainerWidget * parent)310     DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent)
311         : q(parent)
312     {
313         std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr);
314     }
315 
getDropMode(const QPoint & targetPosition)316     eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition)
317     {
318         DockAreaWidget *dockArea = q->dockAreaAt(targetPosition);
319         auto dropArea = InvalidDockWidgetArea;
320         auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor();
321 
322         if (dockArea) {
323             auto dropOverlay = m_dockManager->dockAreaOverlay();
324             dropOverlay->setAllowedAreas(dockArea->allowedAreas());
325             dropArea = dropOverlay->showOverlay(dockArea);
326             if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea)
327                 dropArea = InvalidDockWidgetArea;
328 
329             if (dropArea != InvalidDockWidgetArea) {
330                 qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea;
331                 return DropModeIntoArea;
332             }
333         }
334 
335         // mouse is over container
336         if (InvalidDockWidgetArea == dropArea) {
337             dropArea = containerDropArea;
338             qCInfo(adsLog) << "Container Drop Content: " << dropArea;
339             if (dropArea != InvalidDockWidgetArea)
340                 return DropModeIntoContainer;
341         }
342 
343         return DropModeInvalid;
344     }
345 
onVisibleDockAreaCountChanged()346     void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged()
347     {
348         auto topLevelDockArea = q->topLevelDockArea();
349 
350         if (topLevelDockArea) {
351             this->m_topLevelDockArea = topLevelDockArea;
352             topLevelDockArea->titleBarButton(TitleBarButtonUndock)
353                 ->setVisible(false || !q->isFloating());
354             topLevelDockArea->titleBarButton(TitleBarButtonClose)
355                 ->setVisible(false || !q->isFloating());
356         } else if (this->m_topLevelDockArea) {
357             this->m_topLevelDockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true);
358             this->m_topLevelDockArea->titleBarButton(TitleBarButtonClose)->setVisible(true);
359             this->m_topLevelDockArea = nullptr;
360         }
361     }
362 
dropIntoContainer(FloatingDockContainer * floatingWidget,DockWidgetArea area)363     void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget,
364                                                        DockWidgetArea area)
365     {
366         auto insertParam = internal::dockAreaInsertParameters(area);
367         DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer();
368         auto newDockAreas = floatingDockContainer
369                                 ->findChildren<DockAreaWidget *>(QString(),
370                                                                  Qt::FindChildrenRecursively);
371         QSplitter *splitter = m_rootSplitter;
372 
373         if (m_dockAreas.count() <= 1) {
374             splitter->setOrientation(insertParam.orientation());
375         } else if (splitter->orientation() != insertParam.orientation()) {
376             QSplitter *newSplitter = createSplitter(insertParam.orientation());
377             QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter);
378             newSplitter->addWidget(splitter);
379             splitter = newSplitter;
380             delete layoutItem;
381         }
382 
383         // Now we can insert the floating widget content into this container
384         auto floatingSplitter = floatingDockContainer->rootSplitter();
385         if (floatingSplitter->count() == 1) {
386             insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append());
387         } else if (floatingSplitter->orientation() == insertParam.orientation()) {
388             int insertIndex = insertParam.append() ? splitter->count() : 0;
389             while (floatingSplitter->count())
390                 splitter->insertWidget(insertIndex++, floatingSplitter->widget(0));
391         } else {
392             insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append());
393         }
394 
395         m_rootSplitter = splitter;
396         addDockAreasToList(newDockAreas);
397 
398         // If we dropped the floating widget into the main dock container that does
399         // not contain any dock widgets, then splitter is invisible and we need to
400         // show it to display the docked widgets
401         if (!splitter->isVisible())
402             splitter->show();
403 
404         q->dumpLayout();
405     }
406 
dropIntoCenterOfSection(FloatingDockContainer * floatingWidget,DockAreaWidget * targetArea)407     void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget,
408                                                              DockAreaWidget *targetArea)
409     {
410         DockContainerWidget *floatingContainer = floatingWidget->dockContainer();
411         auto newDockWidgets = floatingContainer->dockWidgets();
412         auto topLevelDockArea = floatingContainer->topLevelDockArea();
413         int newCurrentIndex = -1;
414 
415         // If the floating widget contains only one single dock are, then the
416         // current dock widget of the dock area will also be the future current
417         // dock widget in the drop area.
418         if (topLevelDockArea)
419             newCurrentIndex = topLevelDockArea->currentIndex();
420 
421         for (int i = 0; i < newDockWidgets.count(); ++i) {
422             DockWidget *dockWidget = newDockWidgets[i];
423             targetArea->insertDockWidget(i, dockWidget, false);
424             // If the floating widget contains multiple visible dock areas, then we
425             // simply pick the first visible open dock widget and make it
426             // the current one.
427             if (newCurrentIndex < 0 && !dockWidget->isClosed())
428                 newCurrentIndex = i;
429         }
430         targetArea->setCurrentIndex(newCurrentIndex);
431         targetArea->updateTitleBarVisibility();
432         return;
433     }
434 
dropIntoSection(FloatingDockContainer * floatingWidget,DockAreaWidget * targetArea,DockWidgetArea area)435     void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget,
436                                                      DockAreaWidget *targetArea,
437                                                      DockWidgetArea area)
438     {
439         // Dropping into center means all dock widgets in the dropped floating
440         // widget will become tabs of the drop area
441         if (CenterDockWidgetArea == area) {
442             dropIntoCenterOfSection(floatingWidget, targetArea);
443             return;
444         }
445 
446         auto insertParam = internal::dockAreaInsertParameters(area);
447         auto newDockAreas = floatingWidget->dockContainer()
448                                 ->findChildren<DockAreaWidget *>(QString(),
449                                                                  Qt::FindChildrenRecursively);
450         QSplitter *targetAreaSplitter = internal::findParent<QSplitter *>(targetArea);
451 
452         if (!targetAreaSplitter) {
453             QSplitter *splitter = createSplitter(insertParam.orientation());
454             m_layout->replaceWidget(targetArea, splitter);
455             splitter->addWidget(targetArea);
456             targetAreaSplitter = splitter;
457         }
458         int areaIndex = targetAreaSplitter->indexOf(targetArea);
459         auto widget = floatingWidget->dockContainer()
460                           ->findChild<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
461         auto floatingSplitter = qobject_cast<QSplitter *>(widget);
462 
463         if (targetAreaSplitter->orientation() == insertParam.orientation()) {
464             auto sizes = targetAreaSplitter->sizes();
465             int targetAreaSize = (insertParam.orientation() == Qt::Horizontal)
466                                      ? targetArea->width()
467                                      : targetArea->height();
468             bool adjustSplitterSizes = true;
469             if ((floatingSplitter->orientation() != insertParam.orientation())
470                 && floatingSplitter->count() > 1) {
471                 targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), widget);
472             } else {
473                 adjustSplitterSizes = (floatingSplitter->count() == 1);
474                 int insertIndex = areaIndex + insertParam.insertOffset();
475                 while (floatingSplitter->count())
476                     targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0));
477             }
478 
479             if (adjustSplitterSizes) {
480                 int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2;
481                 sizes[areaIndex] = size;
482                 sizes.insert(areaIndex, size);
483                 targetAreaSplitter->setSizes(sizes);
484             }
485         } else {
486             QSplitter *newSplitter = createSplitter(insertParam.orientation());
487             int targetAreaSize = (insertParam.orientation() == Qt::Horizontal)
488                                      ? targetArea->width()
489                                      : targetArea->height();
490             bool adjustSplitterSizes = true;
491             if ((floatingSplitter->orientation() != insertParam.orientation())
492                 && floatingSplitter->count() > 1) {
493                 newSplitter->addWidget(widget);
494             } else {
495                 adjustSplitterSizes = (floatingSplitter->count() == 1);
496                 while (floatingSplitter->count()) {
497                     newSplitter->addWidget(floatingSplitter->widget(0));
498                 }
499             }
500 
501             // Save the sizes before insertion and restore it later to prevent
502             // shrinking of existing area
503             auto sizes = targetAreaSplitter->sizes();
504             insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append());
505             if (adjustSplitterSizes) {
506                 int size = targetAreaSize / 2;
507                 newSplitter->setSizes({size, size});
508             }
509             targetAreaSplitter->insertWidget(areaIndex, newSplitter);
510             targetAreaSplitter->setSizes(sizes);
511         }
512 
513         addDockAreasToList(newDockAreas);
514         q->dumpLayout();
515     }
516 
moveIntoCenterOfSection(QWidget * widget,DockAreaWidget * targetArea)517     void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget,
518                                                              DockAreaWidget *targetArea)
519     {
520         auto droppedDockWidget = qobject_cast<DockWidget *>(widget);
521         auto droppedArea = qobject_cast<DockAreaWidget *>(widget);
522 
523         if (droppedDockWidget) {
524             DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget();
525             if (oldDockArea == targetArea)
526                 return;
527 
528             if (oldDockArea)
529                 oldDockArea->removeDockWidget(droppedDockWidget);
530 
531             targetArea->insertDockWidget(0, droppedDockWidget, true);
532         } else {
533             QList<DockWidget *> newDockWidgets = droppedArea->dockWidgets();
534             int newCurrentIndex = droppedArea->currentIndex();
535             for (int i = 0; i < newDockWidgets.count(); ++i) {
536                 DockWidget *dockWidget = newDockWidgets[i];
537                 targetArea->insertDockWidget(i, dockWidget, false);
538             }
539             targetArea->setCurrentIndex(newCurrentIndex);
540             droppedArea->dockContainer()->removeDockArea(droppedArea);
541             droppedArea->deleteLater();
542         }
543 
544         targetArea->updateTitleBarVisibility();
545         return;
546     }
547 
moveToNewSection(QWidget * widget,DockAreaWidget * targetArea,DockWidgetArea area)548     void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget,
549                                                       DockAreaWidget *targetArea,
550                                                       DockWidgetArea area)
551     {
552         // Dropping into center means all dock widgets in the dropped floating
553         // widget will become tabs of the drop area
554         if (CenterDockWidgetArea == area) {
555             moveIntoCenterOfSection(widget, targetArea);
556             return;
557         }
558 
559         DockWidget *droppedDockWidget = qobject_cast<DockWidget *>(widget);
560         DockAreaWidget *droppedDockArea = qobject_cast<DockAreaWidget *>(widget);
561         DockAreaWidget *newDockArea;
562         if (droppedDockWidget) {
563             newDockArea = new DockAreaWidget(m_dockManager, q);
564             DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget();
565             if (oldDockArea)
566                 oldDockArea->removeDockWidget(droppedDockWidget);
567 
568             newDockArea->addDockWidget(droppedDockWidget);
569         } else {
570             droppedDockArea->dockContainer()->removeDockArea(droppedDockArea);
571             newDockArea = droppedDockArea;
572         }
573 
574         auto insertParam = internal::dockAreaInsertParameters(area);
575         QSplitter *targetAreaSplitter = internal::findParent<QSplitter *>(targetArea);
576         const int areaIndex = targetAreaSplitter->indexOf(targetArea);
577         auto sizes = targetAreaSplitter->sizes();
578         if (targetAreaSplitter->orientation() == insertParam.orientation()) {
579             const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal)
580                                            ? targetArea->width()
581                                            : targetArea->height();
582             targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea);
583             const int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2;
584             sizes[areaIndex] = size;
585             sizes.insert(areaIndex, size);
586         } else {
587             const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal)
588                                            ? targetArea->width()
589                                            : targetArea->height();
590             QSplitter *newSplitter = createSplitter(insertParam.orientation());
591             newSplitter->addWidget(targetArea);
592             insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append());
593             const int size = targetAreaSize / 2;
594             newSplitter->setSizes({size, size});
595             targetAreaSplitter->insertWidget(areaIndex, newSplitter);
596         }
597         targetAreaSplitter->setSizes(sizes);
598 
599         addDockAreasToList({newDockArea});
600     }
601 
moveToContainer(QWidget * widget,DockWidgetArea area)602     void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area)
603     {
604         DockWidget *droppedDockWidget = qobject_cast<DockWidget *>(widget);
605         DockAreaWidget *droppedDockArea = qobject_cast<DockAreaWidget *>(widget);
606         DockAreaWidget *newDockArea = nullptr;
607 
608         if (droppedDockWidget) {
609             newDockArea = new DockAreaWidget(m_dockManager, q);
610             DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget();
611             if (oldDockArea)
612                 oldDockArea->removeDockWidget(droppedDockWidget);
613 
614             newDockArea->addDockWidget(droppedDockWidget);
615         } else {
616             // We check, if we insert the dropped widget into the same place that
617             // it already has and do nothing, if it is the same place. It would
618             // also work without this check, but it looks nicer with the check
619             // because there will be no layout updates
620             auto splitter = internal::findParent<DockSplitter*>(droppedDockArea);
621             auto insertParam = internal::dockAreaInsertParameters(area);
622             if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) {
623                 if (insertParam.append() && splitter->lastWidget() == droppedDockArea)
624                     return;
625                 else if (!insertParam.append() && splitter->firstWidget() == droppedDockArea)
626                     return;
627             }
628             droppedDockArea->dockContainer()->removeDockArea(droppedDockArea);
629             newDockArea = droppedDockArea;
630         }
631 
632         addDockArea(newDockArea, area);
633         m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea;
634     }
635 
addDockAreasToList(const QList<DockAreaWidget * > newDockAreas)636     void DockContainerWidgetPrivate::addDockAreasToList(const QList<DockAreaWidget *> newDockAreas)
637     {
638         const int countBefore = m_dockAreas.count();
639         const int newAreaCount = newDockAreas.count();
640         appendDockAreas(newDockAreas);
641         // If the user dropped a floating widget that contains only one single
642         // visible dock area, then its title bar button TitleBarButtonUndock is
643         // likely hidden. We need to ensure, that it is visible
644         for (auto dockArea : newDockAreas) {
645             dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true);
646             dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true);
647         }
648 
649         // We need to ensure, that the dock area title bar is visible. The title bar
650         // is invisible, if the dock are is a single dock area in a floating widget.
651         if (1 == countBefore)
652             m_dockAreas.at(0)->updateTitleBarVisibility();
653 
654         if (1 == newAreaCount)
655             m_dockAreas.last()->updateTitleBarVisibility();
656 
657         emitDockAreasAdded();
658     }
659 
appendDockAreas(const QList<DockAreaWidget * > newDockAreas)660     void DockContainerWidgetPrivate::appendDockAreas(const QList<DockAreaWidget *> newDockAreas)
661     {
662         m_dockAreas.append(newDockAreas);
663         for (auto dockArea : newDockAreas) {
664             QObject::connect(dockArea,
665                              &DockAreaWidget::viewToggled,
666                              q,
667                              std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled,
668                                        this,
669                                        std::placeholders::_1));
670         }
671     }
672 
saveChildNodesState(QXmlStreamWriter & stream,QWidget * widget)673     void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget)
674     {
675         QSplitter *splitter = qobject_cast<QSplitter *>(widget);
676         if (splitter) {
677             stream.writeStartElement("splitter");
678             stream.writeAttribute("orientation",
679                                   QVariant::fromValue(splitter->orientation()).toString());
680             stream.writeAttribute("count", QString::number(splitter->count()));
681             qCInfo(adsLog) << "NodeSplitter orient: " << splitter->orientation()
682                            << " WidgetCont: " << splitter->count();
683             for (int i = 0; i < splitter->count(); ++i)
684                 saveChildNodesState(stream, splitter->widget(i));
685 
686             stream.writeStartElement("sizes");
687             QStringList sizes;
688             for (auto size : splitter->sizes())
689                 sizes.append(QString::number(size));
690 
691             stream.writeCharacters(sizes.join(" "));
692             stream.writeEndElement(); // sizes
693             stream.writeEndElement(); // splitter
694         } else {
695             DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(widget);
696             if (dockArea)
697                 dockArea->saveState(stream);
698         }
699     }
700 
restoreSplitter(DockingStateReader & stateReader,QWidget * & createdWidget,bool testing)701     bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader,
702                                                      QWidget *&createdWidget,
703                                                      bool testing)
704     {
705         QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString());
706 
707         // Check if the orientation string is convertable
708         if (!orientationVar.canConvert<Qt::Orientation>())
709             return false;
710 
711         Qt::Orientation orientation = orientationVar.value<Qt::Orientation>();
712 
713         bool ok;
714         int widgetCount = stateReader.attributes().value("count").toInt(&ok);
715         if (!ok)
716             return false;
717 
718         qCInfo(adsLog) << "Restore NodeSplitter Orientation: " << orientation
719                        << " WidgetCount: " << widgetCount;
720         QSplitter *splitter = nullptr;
721         if (!testing)
722             splitter = createSplitter(orientation);
723 
724         bool visible = false;
725         QList<int> sizes;
726         while (stateReader.readNextStartElement()) {
727             QWidget *childNode = nullptr;
728             bool result = true;
729             if (stateReader.name() == QLatin1String("splitter")) {
730                 result = restoreSplitter(stateReader, childNode, testing);
731             } else if (stateReader.name() == QLatin1String("area")) {
732                 result = restoreDockArea(stateReader, childNode, testing);
733             } else if (stateReader.name() == QLatin1String("sizes")) {
734                 QString size = stateReader.readElementText().trimmed();
735                 qCInfo(adsLog) << "Size: " << size;
736                 QTextStream textStream(&size);
737                 while (!textStream.atEnd()) {
738                     int value;
739                     textStream >> value;
740                     sizes.append(value);
741                 }
742             } else {
743                 stateReader.skipCurrentElement();
744             }
745 
746             if (!result)
747                 return false;
748 
749             if (testing || !childNode)
750                 continue;
751 
752             qCInfo(adsLog) << "ChildNode isVisible " << childNode->isVisible() << " isVisibleTo "
753                            << childNode->isVisibleTo(splitter);
754             splitter->addWidget(childNode);
755             visible |= childNode->isVisibleTo(splitter);
756         }
757 
758         if (sizes.count() != widgetCount)
759             return false;
760 
761         if (!testing) {
762             if (!splitter->count()) {
763                 delete splitter;
764                 splitter = nullptr;
765             } else {
766                 splitter->setSizes(sizes);
767                 splitter->setVisible(visible);
768             }
769             createdWidget = splitter;
770         } else {
771             createdWidget = nullptr;
772         }
773 
774         return true;
775     }
776 
restoreDockArea(DockingStateReader & stateReader,QWidget * & createdWidget,bool testing)777     bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader,
778                                                      QWidget *&createdWidget,
779                                                      bool testing)
780     {
781         QString currentDockWidget = stateReader.attributes().value("current").toString();
782 
783 #ifdef ADS_DEBUG_PRINT
784         bool ok;
785         int tabs = stateReader.attributes().value("tabs").toInt(&ok);
786         if (!ok)
787             return false;
788 
789         qCInfo(adsLog) << "Restore NodeDockArea Tabs: " << tabs
790                        << " Current: " << currentDockWidget;
791 #endif
792 
793         DockAreaWidget *dockArea = nullptr;
794         if (!testing)
795             dockArea = new DockAreaWidget(m_dockManager, q);
796 
797         while (stateReader.readNextStartElement()) {
798             if (stateReader.name() != QLatin1String("widget"))
799                 continue;
800 
801             auto objectName = stateReader.attributes().value("name");
802             if (objectName.isEmpty()) {
803                 qCInfo(adsLog) << "Error: Empty name!";
804                 return false;
805             }
806 
807             QVariant closedVar = QVariant(stateReader.attributes().value("closed").toString());
808             if (!closedVar.canConvert<bool>())
809                 return false;
810 
811             bool closed = closedVar.value<bool>();
812 
813             stateReader.skipCurrentElement();
814             DockWidget *dockWidget = m_dockManager->findDockWidget(objectName.toString());
815             if (!dockWidget || testing)
816                 continue;
817 
818             qCInfo(adsLog) << "Dock Widget found - parent " << dockWidget->parent();
819             // We hide the DockArea here to prevent the short display (the flashing)
820             // of the dock areas during application startup
821             dockArea->hide();
822             dockArea->addDockWidget(dockWidget);
823             dockWidget->setToggleViewActionChecked(!closed);
824             dockWidget->setClosedState(closed);
825             dockWidget->setProperty(internal::closedProperty, closed);
826             dockWidget->setProperty(internal::dirtyProperty, false);
827         }
828 
829         if (testing)
830             return true;
831 
832         if (!dockArea->dockWidgetsCount()) {
833             delete dockArea;
834             dockArea = nullptr;
835         } else {
836             dockArea->setProperty("currentDockWidget", currentDockWidget);
837             appendDockAreas({dockArea});
838         }
839 
840         createdWidget = dockArea;
841         return true;
842     }
843 
restoreChildNodes(DockingStateReader & stateReader,QWidget * & createdWidget,bool testing)844     bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader,
845                                                        QWidget *&createdWidget,
846                                                        bool testing)
847     {
848         bool result = true;
849         while (stateReader.readNextStartElement()) {
850             if (stateReader.name() == QLatin1String("splitter")) {
851                 result = restoreSplitter(stateReader, createdWidget, testing);
852                 qCInfo(adsLog) << "Splitter";
853             } else if (stateReader.name() == QLatin1String("area")) {
854                 result = restoreDockArea(stateReader, createdWidget, testing);
855                 qCInfo(adsLog) << "DockAreaWidget";
856             } else {
857                 stateReader.skipCurrentElement();
858                 qCInfo(adsLog) << "Unknown element" << stateReader.name();
859             }
860         }
861 
862         return result;
863     }
864 
addDockWidgetToContainer(DockWidgetArea area,DockWidget * dockWidget)865     DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area,
866                                                                          DockWidget *dockWidget)
867     {
868         DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q);
869         newDockArea->addDockWidget(dockWidget);
870         addDockArea(newDockArea, area);
871         newDockArea->updateTitleBarVisibility();
872         m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea;
873         return newDockArea;
874     }
875 
addDockArea(DockAreaWidget * newDockArea,DockWidgetArea area)876     void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area)
877     {
878         auto insertParam = internal::dockAreaInsertParameters(area);
879         // As long as we have only one dock area in the splitter we can adjust its orientation
880         if (m_dockAreas.count() <= 1)
881             m_rootSplitter->setOrientation(insertParam.orientation());
882 
883         QSplitter *splitter = m_rootSplitter;
884         if (splitter->orientation() == insertParam.orientation()) {
885             insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append());
886             if (splitter->isHidden())
887                 splitter->show();
888 
889         } else {
890             QSplitter *newSplitter = createSplitter(insertParam.orientation());
891             if (insertParam.append()) {
892                 QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter);
893                 newSplitter->addWidget(splitter);
894                 newSplitter->addWidget(newDockArea);
895                 delete layoutItem;
896             } else {
897                 newSplitter->addWidget(newDockArea);
898                 QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter);
899                 newSplitter->addWidget(splitter);
900                 delete layoutItem;
901             }
902             m_rootSplitter = newSplitter;
903         }
904 
905         addDockAreasToList({newDockArea});
906     }
907 
dumpRecursive(int level,QWidget * widget) const908     void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const
909     {
910 #if defined(QT_DEBUG)
911         QSplitter *splitter = qobject_cast<QSplitter *>(widget);
912         QByteArray buf;
913         buf.fill(' ', level * 4);
914         if (splitter) {
915 #ifdef ADS_DEBUG_PRINT
916             qDebug("%sSplitter %s v: %s c: %s",
917                    buf.data(),
918                    (splitter->orientation() == Qt::Vertical) ? "--" : "|",
919                    splitter->isHidden() ? " " : "v",
920                    QString::number(splitter->count()).toStdString().c_str());
921             std::cout << buf.data() << "Splitter "
922                       << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " "
923                       << (splitter->isHidden() ? " " : "v") << " "
924                       << QString::number(splitter->count()).toStdString() << std::endl;
925 #endif
926             for (int i = 0; i < splitter->count(); ++i)
927                 dumpRecursive(level + 1, splitter->widget(i));
928         } else {
929             DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(widget);
930             if (!dockArea)
931                 return;
932 
933 #ifdef ADS_DEBUG_PRINT
934             qDebug("%sDockArea", buf.data());
935             std::cout << buf.data() << (dockArea->isHidden() ? " " : "v")
936                       << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea"
937                       << std::endl;
938             buf.fill(' ', (level + 1) * 4);
939             for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) {
940                 std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " ");
941                 DockWidget *dockWidget = dockArea->dockWidget(i);
942                 std::cout << (dockWidget->isHidden() ? " " : "v");
943                 std::cout << (dockWidget->isClosed() ? "c" : " ") << " ";
944                 std::cout << dockWidget->windowTitle().toStdString() << std::endl;
945             }
946 #endif
947         }
948 #else
949         Q_UNUSED(level)
950         Q_UNUSED(widget)
951 #endif
952     }
953 
addDockWidgetToDockArea(DockWidgetArea area,DockWidget * dockWidget,DockAreaWidget * targetDockArea)954     DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area,
955                                                                         DockWidget *dockWidget,
956                                                                         DockAreaWidget *targetDockArea)
957     {
958         if (CenterDockWidgetArea == area) {
959             targetDockArea->addDockWidget(dockWidget);
960             targetDockArea->updateTitleBarVisibility();
961             return targetDockArea;
962         }
963 
964         DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q);
965         newDockArea->addDockWidget(dockWidget);
966         auto insertParam = internal::dockAreaInsertParameters(area);
967 
968         QSplitter *targetAreaSplitter = internal::findParent<QSplitter *>(targetDockArea);
969         int index = targetAreaSplitter->indexOf(targetDockArea);
970         if (targetAreaSplitter->orientation() == insertParam.orientation()) {
971             qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()";
972             targetAreaSplitter->insertWidget(index + insertParam.insertOffset(), newDockArea);
973             // do nothing, if flag is not enabled
974             if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion))
975                 adjustSplitterSizesOnInsertion(targetAreaSplitter);
976         } else {
977             qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()";
978             auto targetAreaSizes = targetAreaSplitter->sizes();
979             QSplitter *newSplitter = createSplitter(insertParam.orientation());
980             newSplitter->addWidget(targetDockArea);
981             insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append());
982             targetAreaSplitter->insertWidget(index, newSplitter);
983             if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) {
984                 targetAreaSplitter->setSizes(targetAreaSizes);
985                 adjustSplitterSizesOnInsertion(newSplitter);
986             }
987         }
988 
989         appendDockAreas({newDockArea});
990         emitDockAreasAdded();
991         return newDockArea;
992     }
993 
DockContainerWidget(DockManager * dockManager,QWidget * parent)994     DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent)
995         : QFrame(parent)
996         , d(new DockContainerWidgetPrivate(this))
997     {
998         d->m_dockManager = dockManager;
999         d->m_isFloating = floatingWidget() != nullptr;
1000 
1001         d->m_layout = new QGridLayout();
1002         d->m_layout->setContentsMargins(0, 1, 0, 1);
1003         d->m_layout->setSpacing(0);
1004         setLayout(d->m_layout);
1005 
1006         // The function d->createSplitter() accesses the config flags from dock
1007         // manager which in turn requires a properly constructed dock manager.
1008         // If this dock container is the dock manager, then it is not properly
1009         // constructed yet because this base class constructor is called before
1010         // the constructor of the DockManager private class
1011         if (dockManager != this) {
1012             d->m_dockManager->registerDockContainer(this);
1013             createRootSplitter();
1014         }
1015     }
1016 
~DockContainerWidget()1017     DockContainerWidget::~DockContainerWidget()
1018     {
1019         if (d->m_dockManager)
1020             d->m_dockManager->removeDockContainer(this);
1021 
1022         delete d;
1023     }
1024 
addDockWidget(DockWidgetArea area,DockWidget * dockWidget,DockAreaWidget * dockAreaWidget)1025     DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area,
1026                                                        DockWidget *dockWidget,
1027                                                        DockAreaWidget *dockAreaWidget)
1028     {
1029         DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget();
1030         if (oldDockArea)
1031             oldDockArea->removeDockWidget(dockWidget);
1032 
1033         dockWidget->setDockManager(d->m_dockManager);
1034         if (dockAreaWidget)
1035             return d->addDockWidgetToDockArea(area, dockWidget, dockAreaWidget);
1036         else
1037             return d->addDockWidgetToContainer(area, dockWidget);
1038     }
1039 
removeDockWidget(DockWidget * dockWidget)1040     void DockContainerWidget::removeDockWidget(DockWidget * dockWidget)
1041     {
1042         DockAreaWidget *area = dockWidget->dockAreaWidget();
1043         if (area)
1044             area->removeDockWidget(dockWidget);
1045     }
1046 
zOrderIndex() const1047     unsigned int DockContainerWidget::zOrderIndex() const { return d->m_zOrderIndex; }
1048 
isInFrontOf(DockContainerWidget * other) const1049     bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const
1050     {
1051         return this->zOrderIndex() > other->zOrderIndex();
1052     }
1053 
event(QEvent * event)1054     bool DockContainerWidget::event(QEvent *event)
1055     {
1056         bool result = QWidget::event(event);
1057         if (event->type() == QEvent::WindowActivate)
1058             d->m_zOrderIndex = ++zOrderCounter;
1059         else if (event->type() == QEvent::Show && !d->m_zOrderIndex)
1060             d->m_zOrderIndex = ++zOrderCounter;
1061 
1062         return result;
1063     }
1064 
addDockArea(DockAreaWidget * dockAreaWidget,DockWidgetArea area)1065     void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area)
1066     {
1067         DockContainerWidget *container = dockAreaWidget->dockContainer();
1068         if (container && container != this)
1069             container->removeDockArea(dockAreaWidget);
1070 
1071         d->addDockArea(dockAreaWidget, area);
1072     }
1073 
removeDockArea(DockAreaWidget * area)1074     void DockContainerWidget::removeDockArea(DockAreaWidget *area)
1075     {
1076         qCInfo(adsLog) << Q_FUNC_INFO;
1077         area->disconnect(this);
1078         d->m_dockAreas.removeAll(area);
1079         DockSplitter *splitter = internal::findParent<DockSplitter *>(area);
1080 
1081         // Remove area from parent splitter and recursively hide tree of parent
1082         // splitters if it has no visible content
1083         area->setParent(nullptr);
1084         internal::hideEmptyParentSplitters(splitter);
1085 
1086         // Remove this area from cached areas
1087         const auto &cache = d->m_lastAddedAreaCache;
1088         if (auto p = std::find(cache, cache + sizeof(cache) / sizeof(cache[0]), area))
1089             d->m_lastAddedAreaCache[std::distance(cache, p)] = nullptr;
1090 
1091         // If splitter has more than 1 widgets, we are finished and can leave
1092         if (splitter->count() > 1) {
1093             emitAndExit();
1094             return;
1095         }
1096 
1097         // If this is the RootSplitter we need to remove empty splitters to
1098         // avoid too many empty splitters
1099         if (splitter == d->m_rootSplitter) {
1100             qCInfo(adsLog) << "Removed from RootSplitter";
1101             // If splitter is empty, we are finished
1102             if (!splitter->count()) {
1103                 splitter->hide();
1104                 emitAndExit();
1105                 return;
1106             }
1107 
1108             QWidget *widget = splitter->widget(0);
1109             QSplitter *childSplitter = qobject_cast<QSplitter *>(widget);
1110             // If the one and only content widget of the splitter is not a splitter
1111             // then we are finished
1112             if (!childSplitter) {
1113                 emitAndExit();
1114                 return;
1115             }
1116 
1117             // We replace the superfluous RootSplitter with the ChildSplitter
1118             childSplitter->setParent(nullptr);
1119             QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter);
1120             d->m_rootSplitter = childSplitter;
1121             delete layoutItem;
1122             qCInfo(adsLog) << "RootSplitter replaced by child splitter";
1123         } else if (splitter->count() == 1) {
1124             qCInfo(adsLog) << "Replacing splitter with content";
1125             QSplitter *parentSplitter = internal::findParent<QSplitter *>(splitter);
1126             auto sizes = parentSplitter->sizes();
1127             QWidget *widget = splitter->widget(0);
1128             widget->setParent(this);
1129             internal::replaceSplitterWidget(parentSplitter, splitter, widget);
1130             parentSplitter->setSizes(sizes);
1131         }
1132 
1133         delete splitter;
1134     }
1135 
emitAndExit() const1136     void DockContainerWidget::emitAndExit() const
1137     {
1138         DockWidget *topLevelWidget = topLevelDockWidget();
1139 
1140         // Updated the title bar visibility of the dock widget if there is only
1141         // one single visible dock widget
1142         DockWidget::emitTopLevelEventForWidget(topLevelWidget, true);
1143         dumpLayout();
1144         d->emitDockAreasRemoved();
1145     }
1146 
dockAreaAt(const QPoint & globalPosition) const1147     DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const
1148     {
1149         for (auto dockArea : qAsConst(d->m_dockAreas)) {
1150             if (dockArea->isVisible()
1151                 && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition)))
1152                 return dockArea;
1153         }
1154 
1155         return nullptr;
1156     }
1157 
dockArea(int index) const1158     DockAreaWidget *DockContainerWidget::dockArea(int index) const
1159     {
1160         return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr;
1161     }
1162 
isFloating() const1163     bool DockContainerWidget::isFloating() const { return d->m_isFloating; }
1164 
dockAreaCount() const1165     int DockContainerWidget::dockAreaCount() const { return d->m_dockAreas.count(); }
1166 
visibleDockAreaCount() const1167     int DockContainerWidget::visibleDockAreaCount() const
1168     {
1169         int result = 0;
1170         for (auto dockArea : qAsConst(d->m_dockAreas))
1171             result += dockArea->isHidden() ? 0 : 1;
1172 
1173         return result;
1174 
1175         // TODO Cache or precalculate this to speed it up because it is used during
1176         // movement of floating widget
1177         //return d->visibleDockAreaCount();
1178     }
1179 
dropFloatingWidget(FloatingDockContainer * floatingWidget,const QPoint & targetPosition)1180     void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget,
1181                                                  const QPoint &targetPosition)
1182     {
1183         qCInfo(adsLog) << Q_FUNC_INFO;
1184         DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget();
1185         DockWidget *singleDockWidget = topLevelDockWidget();
1186         DockAreaWidget *dockArea = dockAreaAt(targetPosition);
1187         auto dropArea = InvalidDockWidgetArea;
1188         auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor();
1189         bool dropped = false;
1190 
1191         if (dockArea) {
1192             auto dropOverlay = d->m_dockManager->dockAreaOverlay();
1193             dropOverlay->setAllowedAreas(dockArea->allowedAreas());
1194             dropArea = dropOverlay->showOverlay(dockArea);
1195             if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea)
1196                 dropArea = InvalidDockWidgetArea;
1197 
1198             if (dropArea != InvalidDockWidgetArea) {
1199                 qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea;
1200                 d->dropIntoSection(floatingWidget, dockArea, dropArea);
1201                 dropped = true;
1202             }
1203         }
1204 
1205         // mouse is over container
1206         if (InvalidDockWidgetArea == dropArea) {
1207             dropArea = containerDropArea;
1208             qCInfo(adsLog) << "Container Drop Content: " << dropArea;
1209             if (dropArea != InvalidDockWidgetArea) {
1210                 d->dropIntoContainer(floatingWidget, dropArea);
1211                 dropped = true;
1212             }
1213         }
1214 
1215         if (dropped) {
1216             floatingWidget->deleteLater();
1217 
1218             // If we dropped a floating widget with only one single dock widget, then we
1219             // drop a top level widget that changes from floating to docked now
1220             DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false);
1221 
1222             // If there was a top level widget before the drop, then it is not top
1223             // level widget anymore
1224             DockWidget::emitTopLevelEventForWidget(singleDockWidget, false);
1225         }
1226         window()->activateWindow();
1227         if (singleDroppedDockWidget)
1228             d->m_dockManager->notifyWidgetOrAreaRelocation(singleDroppedDockWidget);
1229 
1230         d->m_dockManager->notifyFloatingWidgetDrop(floatingWidget);
1231     }
1232 
dropWidget(QWidget * widget,DockWidgetArea dropArea,DockAreaWidget * targetAreaWidget)1233     void DockContainerWidget::dropWidget(QWidget *widget, DockWidgetArea dropArea, DockAreaWidget *targetAreaWidget)
1234     {
1235         DockWidget *singleDockWidget = topLevelDockWidget();
1236         if (targetAreaWidget)
1237             d->moveToNewSection(widget, targetAreaWidget, dropArea);
1238         else
1239             d->moveToContainer(widget, dropArea);
1240 
1241         // If there was a top level widget before the drop, then it is not top
1242         // level widget anymore
1243         DockWidget::emitTopLevelEventForWidget(singleDockWidget, false);
1244         DockWidget *dockWidget = qobject_cast<DockWidget *>(widget);
1245         if (!dockWidget)
1246         {
1247             DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(widget);
1248             auto openDockWidgets = dockArea->openedDockWidgets();
1249             if (openDockWidgets.count() == 1)
1250                 dockWidget = openDockWidgets[0];
1251         }
1252 
1253         window()->activateWindow();
1254         d->m_dockManager->notifyWidgetOrAreaRelocation(widget);
1255     }
1256 
openedDockAreas() const1257     QList<DockAreaWidget *> DockContainerWidget::openedDockAreas() const
1258     {
1259         QList<DockAreaWidget *> result;
1260         for (auto dockArea : qAsConst(d->m_dockAreas)) {
1261             if (!dockArea->isHidden())
1262                 result.append(dockArea);
1263         }
1264 
1265         return result;
1266     }
1267 
saveState(QXmlStreamWriter & stream) const1268     void DockContainerWidget::saveState(QXmlStreamWriter &stream) const
1269     {
1270         qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating();
1271 
1272         stream.writeStartElement("container");
1273         stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString());
1274         if (isFloating()) {
1275             FloatingDockContainer *floatingDockContainer = floatingWidget();
1276             QByteArray geometry = floatingDockContainer->saveGeometry();
1277             stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64()));
1278         }
1279         d->saveChildNodesState(stream, d->m_rootSplitter);
1280         stream.writeEndElement();
1281     }
1282 
restoreState(DockingStateReader & stateReader,bool testing)1283     bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing)
1284     {
1285         QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString());
1286         if (!floatingVar.canConvert<bool>())
1287             return false;
1288 
1289         bool isFloating = floatingVar.value<bool>();
1290         qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating;
1291 
1292         QWidget *newRootSplitter{};
1293         if (!testing) {
1294             d->m_visibleDockAreaCount = -1; // invalidate the dock area count
1295             d->m_dockAreas.clear();
1296             std::fill(std::begin(d->m_lastAddedAreaCache),
1297                       std::end(d->m_lastAddedAreaCache),
1298                       nullptr);
1299         }
1300 
1301         if (isFloating) {
1302             qCInfo(adsLog) << "Restore floating widget";
1303             if (!stateReader.readNextStartElement() || stateReader.name() != QLatin1String("geometry"))
1304                 return false;
1305 
1306             QByteArray geometryString = stateReader
1307                                             .readElementText(
1308                                                 DockingStateReader::ErrorOnUnexpectedElement)
1309                                             .toLocal8Bit();
1310             QByteArray geometry = QByteArray::fromBase64(geometryString);
1311             if (geometry.isEmpty())
1312                 return false;
1313 
1314             if (!testing) {
1315                 FloatingDockContainer *floatingDockContainer = floatingWidget();
1316                 floatingDockContainer->restoreGeometry(geometry);
1317             }
1318         }
1319 
1320         if (!d->restoreChildNodes(stateReader, newRootSplitter, testing))
1321             return false;
1322 
1323         if (testing)
1324             return true;
1325 
1326         // If the root splitter is empty, rostoreChildNodes returns a 0 pointer
1327         // and we need to create a new empty root splitter
1328         if (!newRootSplitter)
1329             newRootSplitter = d->createSplitter(Qt::Horizontal);
1330 
1331         d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter);
1332         QSplitter *oldRoot = d->m_rootSplitter;
1333         d->m_rootSplitter = qobject_cast<QSplitter *>(newRootSplitter);
1334         oldRoot->deleteLater();
1335 
1336         return true;
1337     }
1338 
rootSplitter() const1339     QSplitter *DockContainerWidget::rootSplitter() const { return d->m_rootSplitter; }
1340 
createRootSplitter()1341     void DockContainerWidget::createRootSplitter()
1342     {
1343         if (d->m_rootSplitter)
1344             return;
1345 
1346         d->m_rootSplitter = d->createSplitter(Qt::Horizontal);
1347         d->m_layout->addWidget(d->m_rootSplitter);
1348     }
1349 
dumpLayout() const1350     void DockContainerWidget::dumpLayout() const
1351     {
1352 #if (ADS_DEBUG_LEVEL > 0)
1353         qDebug("\n\nDumping layout --------------------------");
1354         std::cout << "\n\nDumping layout --------------------------" << std::endl;
1355         d->dumpRecursive(0, d->m_rootSplitter);
1356         qDebug("--------------------------\n\n");
1357         std::cout << "--------------------------\n\n" << std::endl;
1358 #endif
1359     }
1360 
lastAddedDockAreaWidget(DockWidgetArea area) const1361     DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const
1362     {
1363         return d->m_lastAddedAreaCache[areaIdToIndex(area)];
1364     }
1365 
hasTopLevelDockWidget() const1366     bool DockContainerWidget::hasTopLevelDockWidget() const
1367     {
1368         auto dockAreas = openedDockAreas();
1369         if (dockAreas.count() != 1)
1370             return false;
1371 
1372         return dockAreas[0]->openDockWidgetsCount() == 1;
1373     }
1374 
topLevelDockWidget() const1375     DockWidget *DockContainerWidget::topLevelDockWidget() const
1376     {
1377         auto dockArea = topLevelDockArea();
1378         if (!dockArea)
1379             return nullptr;
1380 
1381         auto dockWidgets = dockArea->openedDockWidgets();
1382         if (dockWidgets.count() != 1)
1383             return nullptr;
1384 
1385         return dockWidgets[0];
1386     }
1387 
topLevelDockArea() const1388     DockAreaWidget *DockContainerWidget::topLevelDockArea() const
1389     {
1390         auto dockAreas = openedDockAreas();
1391         if (dockAreas.count() != 1)
1392             return nullptr;
1393 
1394         return dockAreas[0];
1395     }
1396 
dockWidgets() const1397     QList<DockWidget *> DockContainerWidget::dockWidgets() const
1398     {
1399         QList<DockWidget *> result;
1400         for (const auto dockArea : qAsConst(d->m_dockAreas))
1401             result.append(dockArea->dockWidgets());
1402 
1403         return result;
1404     }
1405 
features() const1406     DockWidget::DockWidgetFeatures DockContainerWidget::features() const
1407     {
1408         DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures);
1409         for (const auto dockArea : qAsConst(d->m_dockAreas))
1410             features &= dockArea->features();
1411 
1412         return features;
1413     }
1414 
floatingWidget() const1415     FloatingDockContainer *DockContainerWidget::floatingWidget() const
1416     {
1417         return internal::findParent<FloatingDockContainer *>(this);
1418     }
1419 
closeOtherAreas(DockAreaWidget * keepOpenArea)1420     void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea)
1421     {
1422         for (const auto dockArea : qAsConst(d->m_dockAreas)) {
1423             if (dockArea == keepOpenArea)
1424                 continue;
1425 
1426             if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable))
1427                 continue;
1428 
1429             // We do not close areas with widgets with custom close handling
1430             if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling))
1431                 continue;
1432 
1433             dockArea->closeArea();
1434         }
1435     }
1436 
1437 } // namespace ADS
1438