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