// Copyright (C) 2020 Uwe Kindler // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "dockareawidget.h" #include "ads_globals_p.h" #include "autohidedockcontainer.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" #include "dockingstatereader.h" #include "dockmanager.h" #include "docksplitter.h" #include "dockwidget.h" #include "dockwidgettab.h" #include "elidinglabel.h" #include "floatingdockcontainer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ADS { static const char *const g_indexProperty = "index"; static const char *const g_actionProperty = "action"; /** * Check, if auto hide is enabled */ static bool isAutoHideFeatureEnabled() { return DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled); } /** * Internal dock area layout mimics stack layout but only inserts the current widget into the * internal QLayout object. * \warning Only the current widget has a parent. All other widgets do not have a parent. That * means, a widget that is in this layout may return nullptr for its parent() function if it is * not the current widget. */ class DockAreaLayout { private: QBoxLayout *m_parentLayout; QList m_widgets; int m_currentIndex = -1; QWidget *m_currentWidget = nullptr; public: /** * Creates an instance with the given parent layout */ DockAreaLayout(QBoxLayout *parentLayout) : m_parentLayout(parentLayout) {} /** * Returns the number of widgets in this layout */ int count() const { return m_widgets.count(); } /** * Inserts the widget at the given index position into the internal widget list */ void insertWidget(int index, QWidget *widget) { widget->setParent(nullptr); if (index < 0) index = m_widgets.count(); m_widgets.insert(index, widget); if (m_currentIndex < 0) { setCurrentIndex(index); } else { if (index <= m_currentIndex) ++m_currentIndex; } } /** * Removes the given widget from the layout */ void removeWidget(QWidget *widget) { if (currentWidget() == widget) { auto layoutItem = m_parentLayout->takeAt(1); if (layoutItem) layoutItem->widget()->setParent(nullptr); delete layoutItem; m_currentWidget = nullptr; m_currentIndex = -1; } else if (indexOf(widget) < m_currentIndex) { --m_currentIndex; } m_widgets.removeOne(widget); } /** * Returns the current selected widget */ QWidget *currentWidget() const { return m_currentWidget; } /** * Activates the widget with the give index. */ void setCurrentIndex(int index) { QWidget *prev = currentWidget(); QWidget *next = widget(index); if (!next || (next == prev && !m_currentWidget)) return; bool reenableUpdates = false; QWidget *parent = m_parentLayout->parentWidget(); if (parent && parent->updatesEnabled()) { reenableUpdates = true; parent->setUpdatesEnabled(false); } auto layoutItem = m_parentLayout->takeAt(1); if (layoutItem) layoutItem->widget()->setParent(nullptr); delete layoutItem; m_parentLayout->addWidget(next); if (prev) prev->hide(); m_currentIndex = index; m_currentWidget = next; if (reenableUpdates) parent->setUpdatesEnabled(true); } /** * Returns the index of the current active widget */ int currentIndex() const { return m_currentIndex; } /** * Returns true if there are no widgets in the layout */ bool isEmpty() const { return m_widgets.empty(); } /** * Returns the index of the given widget */ int indexOf(QWidget *widget) const { return m_widgets.indexOf(widget); } /** * Returns the widget for the given index */ QWidget *widget(int index) const { return (index < m_widgets.size()) ? m_widgets.at(index) : nullptr; } /** * Returns the geometry of the current active widget */ QRect geometry() const { return m_widgets.empty() ? QRect() : currentWidget()->geometry(); } }; static const DockWidgetAreas DefaultAllowedAreas = AllDockAreas; /** * Private data class of DockAreaWidget class (pimpl) */ struct DockAreaWidgetPrivate { DockAreaWidget *q = nullptr; QBoxLayout *m_layout = nullptr; // DockAreaLayout is not a QObject -> std::unique_ptr manages deletion std::unique_ptr m_contentsLayout; DockAreaTitleBar *m_titleBar = nullptr; DockManager *m_dockManager = nullptr; AutoHideDockContainer *m_autoHideDockContainer = nullptr; bool m_updateTitleBarButtons = false; DockWidgetAreas m_allowedAreas = DefaultAllowedAreas; DockAreaWidget::DockAreaFlags m_flags{DockAreaWidget::DefaultFlags}; /** * Private data constructor */ DockAreaWidgetPrivate(DockAreaWidget *parent); /** * Creates the layout for top area with tabs and close button */ void createTitleBar(); /** * Returns the dock widget with the given index */ DockWidget *dockWidgetAt(int index) { return qobject_cast(m_contentsLayout->widget(index)); } /** * Convenience function to ease title widget access by index */ DockWidgetTab *tabWidgetAt(int index) { return dockWidgetAt(index)->tabWidget(); } /** * Returns the tab action of the given dock widget */ QAction *dockWidgetTabAction(DockWidget *dockWidget) const { return qvariant_cast(dockWidget->property(g_actionProperty)); } /** * Returns the index of the given dock widget */ int dockWidgetIndex(DockWidget *dockWidget) const { return dockWidget->property(g_indexProperty).toInt(); } /** * Convenience function for tabbar access */ DockAreaTabBar *tabBar() const { return m_titleBar->tabBar(); } /** * Updates the enable state of the close and detach button */ void updateTitleBarButtonStates(); /** * Updates the visibility of the close and detach button */ void updateTitleBarButtonVisibility(bool isTopLevel); }; // struct DockAreaWidgetPrivate DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *parent) : q(parent) {} void DockAreaWidgetPrivate::createTitleBar() { m_titleBar = componentsFactory()->createDockAreaTitleBar(q); m_layout->addWidget(m_titleBar); QObject::connect(tabBar(), &DockAreaTabBar::tabCloseRequested, q, &DockAreaWidget::onTabCloseRequested); QObject::connect(m_titleBar, &DockAreaTitleBar::tabBarClicked, q, &DockAreaWidget::setCurrentIndex); QObject::connect(tabBar(), &DockAreaTabBar::tabMoved, q, &DockAreaWidget::reorderDockWidget); } void DockAreaWidgetPrivate::updateTitleBarButtonStates() { if (q->isHidden()) { m_updateTitleBarButtons = true; return; } m_titleBar->button(TitleBarButtonClose) ->setEnabled(q->features().testFlag(DockWidget::DockWidgetClosable)); m_titleBar->button(TitleBarButtonUndock) ->setEnabled(q->features().testFlag(DockWidget::DockWidgetFloatable)); m_titleBar->button(TitleBarButtonAutoHide) ->setEnabled(q->features().testFlag(DockWidget::DockWidgetPinnable)); m_titleBar->updateDockWidgetActionsButtons(); m_updateTitleBarButtons = false; } void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool isTopLevel) { auto *const container = q->dockContainer(); if (!container) return; bool isAutoHide = q->isAutoHide(); if (isAutoHide) { bool showCloseButton = DockManager::autoHideConfigFlags().testFlag( DockManager::AutoHideHasCloseButton); m_titleBar->button(TitleBarButtonClose)->setVisible(showCloseButton); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); m_titleBar->button(TitleBarButtonUndock)->setVisible(false); m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(false); } else if (isTopLevel) { m_titleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating()); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating()); // Undock and tabs should never show when auto hidden m_titleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating()); m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(true); } else { m_titleBar->button(TitleBarButtonClose)->setVisible(true); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); m_titleBar->button(TitleBarButtonUndock)->setVisible(!q->isAutoHide()); m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide()); } } DockAreaWidget::DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent) : QFrame(parent) , d(new DockAreaWidgetPrivate(this)) { d->m_dockManager = dockManager; d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); d->m_layout->setContentsMargins(0, 0, 0, 0); d->m_layout->setSpacing(0); setLayout(d->m_layout); d->createTitleBar(); d->m_contentsLayout = std::make_unique(d->m_layout); if (d->m_dockManager) emit d->m_dockManager->dockAreaCreated(this); } DockAreaWidget::~DockAreaWidget() { qCInfo(adsLog) << Q_FUNC_INFO; } DockManager *DockAreaWidget::dockManager() const { return d->m_dockManager; } DockContainerWidget *DockAreaWidget::dockContainer() const { return internal::findParent(this); } AutoHideDockContainer *DockAreaWidget::autoHideDockContainer() const { return d->m_autoHideDockContainer; } bool DockAreaWidget::isAutoHide() const { return d->m_autoHideDockContainer != nullptr; } void DockAreaWidget::setAutoHideDockContainer(AutoHideDockContainer *autoHideDockContainer) { d->m_autoHideDockContainer = autoHideDockContainer; updateAutoHideButtonCheckState(); updateTitleBarButtonsToolTips(); d->m_titleBar->button(TitleBarButtonAutoHide)->setShowInTitleBar(true); } void DockAreaWidget::addDockWidget(DockWidget *dockWidget) { insertDockWidget(d->m_contentsLayout->count(), dockWidget); } void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool activate) { if (index < 0 || index > d->m_contentsLayout->count()) index = d->m_contentsLayout->count(); d->m_contentsLayout->insertWidget(index, dockWidget); dockWidget->setDockArea(this); dockWidget->tabWidget()->setDockAreaWidget(this); auto tabWidget = dockWidget->tabWidget(); // Inserting the tab will change the current index which in turn will make the tab widget // visible in the slot d->tabBar()->blockSignals(true); d->tabBar()->insertTab(index, tabWidget); d->tabBar()->blockSignals(false); tabWidget->setVisible(!dockWidget->isClosed()); d->m_titleBar->autoHideTitleLabel()->setText(dockWidget->windowTitle()); dockWidget->setProperty(g_indexProperty, index); if (activate) { setCurrentIndex(index); // Set current index can show the widget without changing the close state, added to keep // the close state consistent dockWidget->setClosedState(false); } // If this dock area is hidden, then we need to make it visible again by calling // dockWidget->toggleViewInternal(true); if (!isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) dockWidget->toggleViewInternal(true); d->updateTitleBarButtonStates(); updateTitleBarVisibility(); } void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) { qCInfo(adsLog) << Q_FUNC_INFO; if (!dockWidget) return; // If this dock area is in a auto hide container, then we can delete the auto hide container now if (isAutoHide()) { autoHideDockContainer()->cleanupAndDelete(); return; } auto current = currentDockWidget(); auto nextOpen = (dockWidget == current) ? nextOpenDockWidget(dockWidget) : nullptr; d->m_contentsLayout->removeWidget(dockWidget); auto tabWidget = dockWidget->tabWidget(); tabWidget->hide(); d->tabBar()->removeTab(tabWidget); tabWidget->setParent(dockWidget); dockWidget->setDockArea(nullptr); DockContainerWidget *dockContainerWidget = dockContainer(); if (nextOpen) { setCurrentDockWidget(nextOpen); } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() >= 1) { qCInfo(adsLog) << "Dock Area empty"; dockContainerWidget->removeDockArea(this); deleteLater(); if (dockContainerWidget->dockAreaCount() == 0) { if (FloatingDockContainer *floatingDockContainer = dockContainerWidget->floatingWidget()) { floatingDockContainer->hide(); floatingDockContainer->deleteLater(); } } } else if (dockWidget == current) { // If contents layout is not empty but there are no more open dock widgets, then we need to // hide the dock area because it does not contain any visible content hideAreaWithNoVisibleContent(); } d->updateTitleBarButtonStates(); updateTitleBarVisibility(); auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); if (topLevelDockWidget) topLevelDockWidget->emitTopLevelChanged(true); #if (ADS_DEBUG_LEVEL > 0) dockContainerWidget->dumpLayout(); #endif } void DockAreaWidget::hideAreaWithNoVisibleContent() { toggleView(false); // Hide empty parent splitters auto splitter = internal::findParent(this); internal::hideEmptyParentSplitters(splitter); // Hide empty floating widget DockContainerWidget *container = dockContainer(); if (!container->isFloating() && !DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) return; updateTitleBarVisibility(); auto topLevelWidget = container->topLevelDockWidget(); auto floatingWidget = container->floatingWidget(); if (topLevelWidget) { if (floatingWidget) floatingWidget->updateWindowTitle(); DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); } else if (container->openedDockAreas().isEmpty() && floatingWidget) { floatingWidget->hide(); } if (isAutoHide()) autoHideDockContainer()->hide(); } void DockAreaWidget::onTabCloseRequested(int index) { qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; dockWidget(index)->requestCloseDockWidget(); } DockWidget *DockAreaWidget::currentDockWidget() const { int currentIdx = currentIndex(); if (currentIdx < 0) return nullptr; return dockWidget(currentIdx); } void DockAreaWidget::setCurrentDockWidget(DockWidget *dockWidget) { if (dockManager()->isRestoringState()) return; internalSetCurrentDockWidget(dockWidget); } void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget) { int index = indexOf(dockWidget); if (index < 0) return; setCurrentIndex(index); // Set current index can show the widget without changing the close state, added to keep // the close state consistent dockWidget->setClosedState(false); } void DockAreaWidget::setCurrentIndex(int index) { auto currentTabBar = d->tabBar(); if (index < 0 || index > (currentTabBar->count() - 1)) { qWarning() << Q_FUNC_INFO << "Invalid index" << index; return; } auto cw = d->m_contentsLayout->currentWidget(); auto nw = d->m_contentsLayout->widget(index); if (cw == nw && !nw->isHidden()) return; emit currentChanging(index); currentTabBar->setCurrentIndex(index); d->m_contentsLayout->setCurrentIndex(index); d->m_contentsLayout->currentWidget()->show(); emit currentChanged(index); } int DockAreaWidget::currentIndex() const { return d->m_contentsLayout->currentIndex(); } QRect DockAreaWidget::titleBarGeometry() const { return d->m_titleBar->geometry(); } QRect DockAreaWidget::contentAreaGeometry() const { return d->m_contentsLayout->geometry(); } int DockAreaWidget::indexOf(DockWidget *dockWidget) { return d->m_contentsLayout->indexOf(dockWidget); } QList DockAreaWidget::dockWidgets() const { QList dockWidgetList; for (int i = 0; i < d->m_contentsLayout->count(); ++i) dockWidgetList.append(dockWidget(i)); return dockWidgetList; } int DockAreaWidget::openDockWidgetsCount() const { int count = 0; for (int i = 0; i < d->m_contentsLayout->count(); ++i) { if (!dockWidget(i)->isClosed()) ++count; } return count; } QList DockAreaWidget::openedDockWidgets() const { QList dockWidgetList; for (int i = 0; i < d->m_contentsLayout->count(); ++i) { DockWidget *currentDockWidget = dockWidget(i); if (!currentDockWidget->isClosed()) dockWidgetList.append(currentDockWidget); } return dockWidgetList; } int DockAreaWidget::indexOfFirstOpenDockWidget() const { for (int i = 0; i < d->m_contentsLayout->count(); ++i) { if (!dockWidget(i)->isClosed()) return i; } return -1; } int DockAreaWidget::dockWidgetsCount() const { return d->m_contentsLayout->count(); } DockWidget *DockAreaWidget::dockWidget(int index) const { return qobject_cast(d->m_contentsLayout->widget(index)); } void DockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) { qCInfo(adsLog) << Q_FUNC_INFO; if (fromIndex >= d->m_contentsLayout->count() || fromIndex < 0 || toIndex >= d->m_contentsLayout->count() || toIndex < 0 || fromIndex == toIndex) { qCInfo(adsLog) << "Invalid index for tab movement" << fromIndex << toIndex; return; } auto widget = d->m_contentsLayout->widget(fromIndex); d->m_contentsLayout->removeWidget(widget); d->m_contentsLayout->insertWidget(toIndex, widget); setCurrentIndex(toIndex); } void DockAreaWidget::toggleDockWidgetView(DockWidget *dockWidget, bool open) { Q_UNUSED(dockWidget) Q_UNUSED(open) updateTitleBarVisibility(); } void DockAreaWidget::updateTitleBarVisibility() { DockContainerWidget *container = dockContainer(); if (!container) return; if (!d->m_titleBar) return; bool autoHide = isAutoHide(); if (!DockManager::testConfigFlag(DockManager::AlwaysShowTabs)) { bool hidden = container->hasTopLevelDockWidget() && (container->isFloating() || DockManager::testConfigFlag( DockManager::HideSingleCentralWidgetTitleBar)); hidden |= (d->m_flags.testFlag(HideSingleWidgetTitleBar) && openDockWidgetsCount() == 1); hidden &= !autoHide; // Titlebar must always be visible when auto hidden so it can be dragged d->m_titleBar->setVisible(!hidden); } if (isAutoHideFeatureEnabled()) { d->m_titleBar->showAutoHideControls(autoHide); updateTitleBarButtonVisibility(container->topLevelDockArea() == this); } } void DockAreaWidget::markTitleBarMenuOutdated() { if (d->m_titleBar) d->m_titleBar->markTabsMenuOutdated(); } void DockAreaWidget::updateAutoHideButtonCheckState() { auto autoHideButton = titleBarButton(TitleBarButtonAutoHide); autoHideButton->blockSignals(true); autoHideButton->setChecked(isAutoHide()); autoHideButton->blockSignals(false); } void DockAreaWidget::updateTitleBarButtonVisibility(bool isTopLevel) const { d->updateTitleBarButtonVisibility(isTopLevel); } void DockAreaWidget::updateTitleBarButtonsToolTips() { internal::setToolTip(titleBarButton(TitleBarButtonClose), titleBar()->titleBarButtonToolTip(TitleBarButtonClose)); internal::setToolTip(titleBarButton(TitleBarButtonAutoHide), titleBar()->titleBarButtonToolTip(TitleBarButtonAutoHide)); } void DockAreaWidget::saveState(QXmlStreamWriter &stream) const { stream.writeStartElement("area"); stream.writeAttribute("tabs", QString::number(d->m_contentsLayout->count())); auto localDockWidget = currentDockWidget(); QString name = localDockWidget ? localDockWidget->objectName() : ""; stream.writeAttribute("current", name); if (d->m_allowedAreas != DefaultAllowedAreas) stream.writeAttribute("allowedAreas", QString::number(d->m_allowedAreas, 16)); if (d->m_flags != DefaultFlags) stream.writeAttribute("flags", QString::number(d->m_flags, 16)); qCInfo(adsLog) << Q_FUNC_INFO << "TabCount: " << d->m_contentsLayout->count() << " Current: " << name; for (int i = 0; i < d->m_contentsLayout->count(); ++i) dockWidget(i)->saveState(stream); stream.writeEndElement(); } bool DockAreaWidget::restoreState(DockingStateReader &stateReader, DockAreaWidget *&createdWidget, bool testing, DockContainerWidget *container) { QString currentDockWidget = stateReader.attributes().value("current").toString(); #ifdef ADS_DEBUG_PRINT bool ok; int tabs = stateReader.attributes().value("tabs").toInt(&ok); if (!ok) return false; qCInfo(adsLog) << "Restore NodeDockArea Tabs: " << tabs << " Current: " << currentDockWidget; #endif auto dockManager = container->dockManager(); DockAreaWidget *dockArea = nullptr; if (!testing) { dockArea = new DockAreaWidget(dockManager, container); const auto allowedAreasAttribute = stateReader.attributes().value("allowedAreas"); if (!allowedAreasAttribute.isEmpty()) dockArea->setAllowedAreas((DockWidgetArea) allowedAreasAttribute.toInt(nullptr, 16)); const auto flagsAttribute = stateReader.attributes().value("flags"); if (!flagsAttribute.isEmpty()) dockArea->setDockAreaFlags( (DockAreaWidget::DockAreaFlags) flagsAttribute.toInt(nullptr, 16)); } while (stateReader.readNextStartElement()) { if (stateReader.name() != QLatin1String("widget")) continue; auto objectName = stateReader.attributes().value("name"); if (objectName.isEmpty()) { qCInfo(adsLog) << "Error: Empty name!"; return false; } QVariant closedVar = QVariant(stateReader.attributes().value("closed").toString()); if (!closedVar.canConvert()) return false; bool closed = closedVar.value(); stateReader.skipCurrentElement(); DockWidget *dockWidget = dockManager->findDockWidget(objectName.toString()); if (!dockWidget || testing) continue; qCInfo(adsLog) << "Dock Widget found" << dockWidget->objectName() << "parent" << dockWidget->parent(); if (dockWidget->autoHideDockContainer()) dockWidget->autoHideDockContainer()->cleanupAndDelete(); // We hide the DockArea here to prevent the short display (the flashing) of the dock // areas during application startup dockArea->hide(); dockArea->addDockWidget(dockWidget); dockWidget->setToggleViewActionChecked(!closed); dockWidget->setClosedState(closed); dockWidget->setProperty(internal::g_closedProperty, closed); dockWidget->setProperty(internal::g_dirtyProperty, false); } if (testing) return true; if (!dockArea->dockWidgetsCount()) { delete dockArea; dockArea = nullptr; } else { dockArea->setProperty("currentDockWidget", currentDockWidget); } createdWidget = dockArea; return true; } DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const { auto openDockWidgets = openedDockWidgets(); if (openDockWidgets.count() > 1 || (openDockWidgets.count() == 1 && openDockWidgets[0] != dockWidget)) { if (openDockWidgets.last() == dockWidget) { DockWidget *nextDockWidget = openDockWidgets[openDockWidgets.count() - 2]; // search backwards for widget with tab for (int i = openDockWidgets.count() - 2; i >= 0; --i) { auto dw = openDockWidgets[i]; if (!dw->features().testFlag(DockWidget::NoTab)) return dw; } // return widget without tab return nextDockWidget; } else { int indexOfDockWidget = openDockWidgets.indexOf(dockWidget); DockWidget *nextDockWidget = openDockWidgets[indexOfDockWidget + 1]; // search forwards for widget with tab for (int i = indexOfDockWidget + 1; i < openDockWidgets.count(); ++i) { auto dw = openDockWidgets[i]; if (!dw->features().testFlag(DockWidget::NoTab)) return dw; } // search backwards for widget with tab for (int i = indexOfDockWidget - 1; i >= 0; --i) { auto dw = openDockWidgets[i]; if (!dw->features().testFlag(DockWidget::NoTab)) return dw; } // return widget without tab return nextDockWidget; } } else { return nullptr; } } DockWidget::DockWidgetFeatures DockAreaWidget::features(eBitwiseOperator mode) const { if (BitwiseAnd == mode) { DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); for (const auto dockWidget : dockWidgets()) features &= dockWidget->features(); return features; } else { DockWidget::DockWidgetFeatures features(DockWidget::NoDockWidgetFeatures); for (const auto dockWidget : dockWidgets()) features |= dockWidget->features(); return features; } } void DockAreaWidget::toggleView(bool open) { setVisible(open); emit viewToggled(open); } void DockAreaWidget::setVisible(bool visible) { Super::setVisible(visible); if (d->m_updateTitleBarButtons) d->updateTitleBarButtonStates(); } void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) { d->m_allowedAreas = areas; } DockWidgetAreas DockAreaWidget::allowedAreas() const { return d->m_allowedAreas; } DockAreaWidget::DockAreaFlags DockAreaWidget::dockAreaFlags() const { return d->m_flags; } void DockAreaWidget::setDockAreaFlags(DockAreaFlags flags) { auto changedFlags = d->m_flags ^ flags; d->m_flags = flags; if (changedFlags.testFlag(HideSingleWidgetTitleBar)) updateTitleBarVisibility(); } void DockAreaWidget::setDockAreaFlag(eDockAreaFlag flag, bool on) { auto flags = dockAreaFlags(); internal::setFlag(flags, flag, on); setDockAreaFlags(flags); } QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const { return d->m_titleBar->button(which); } void DockAreaWidget::closeArea() { // If there is only one single dock widget and this widget has the DeleteOnClose feature or // CustomCloseHandling, then we delete the dock widget now; in the case of CustomCloseHandling, // the DockWidget class will emit its closeRequested signal and not actually delete unless // the signal is handled in a way that deletes it auto openDockWidgets = openedDockWidgets(); if (openDockWidgets.count() == 1 && (openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose) || openDockWidgets[0]->features().testFlag(DockWidget::CustomCloseHandling)) && !isAutoHide()) { openDockWidgets[0]->closeDockWidgetInternal(); } else { for (auto dockWidget : openedDockWidgets()) { if ((dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) && dockWidget->features().testFlag(DockWidget::DockWidgetForceCloseWithArea)) || dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) { dockWidget->closeDockWidgetInternal(); } else if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) && isAutoHide()) { dockWidget->closeDockWidgetInternal(); } else { dockWidget->toggleView(false); } } } } enum eBorderLocation { BorderNone = 0, BorderLeft = 0x01, BorderRight = 0x02, BorderTop = 0x04, BorderBottom = 0x08, BorderVertical = BorderLeft | BorderRight, BorderHorizontal = BorderTop | BorderBottom, BorderTopLeft = BorderTop | BorderLeft, BorderTopRight = BorderTop | BorderRight, BorderBottomLeft = BorderBottom | BorderLeft, BorderBottomRight = BorderBottom | BorderRight, BorderVerticalBottom = BorderVertical | BorderBottom, BorderVerticalTop = BorderVertical | BorderTop, BorderHorizontalLeft = BorderHorizontal | BorderLeft, BorderHorizontalRight = BorderHorizontal | BorderRight, BorderAll = BorderVertical | BorderHorizontal }; SideBarLocation DockAreaWidget::calculateSideTabBarArea() const { auto container = dockContainer(); auto contentRect = container->contentRect(); int borders = BorderNone; // contains all borders that are touched by the dock ware auto dockAreaTopLeft = mapTo(container, rect().topLeft()); auto dockAreaRect = rect(); dockAreaRect.moveTo(dockAreaTopLeft); const qreal aspectRatio = dockAreaRect.width() / (qMax(1, dockAreaRect.height()) * 1.0); const qreal sizeRatio = (qreal) contentRect.width() / dockAreaRect.width(); static const int minBorderDistance = 16; bool horizontalOrientation = (aspectRatio > 1.0) && (sizeRatio < 3.0); // measure border distances - a distance less than 16 px means we touch the border int borderDistance[4]; int distance = qAbs(contentRect.topLeft().y() - dockAreaRect.topLeft().y()); borderDistance[SideBarLocation::SideBarTop] = (distance < minBorderDistance) ? 0 : distance; if (!borderDistance[SideBarLocation::SideBarTop]) borders |= BorderTop; distance = qAbs(contentRect.bottomRight().y() - dockAreaRect.bottomRight().y()); borderDistance[SideBarLocation::SideBarBottom] = (distance < minBorderDistance) ? 0 : distance; if (!borderDistance[SideBarLocation::SideBarBottom]) borders |= BorderBottom; distance = qAbs(contentRect.topLeft().x() - dockAreaRect.topLeft().x()); borderDistance[SideBarLocation::SideBarLeft] = (distance < minBorderDistance) ? 0 : distance; if (!borderDistance[SideBarLocation::SideBarLeft]) borders |= BorderLeft; distance = qAbs(contentRect.bottomRight().x() - dockAreaRect.bottomRight().x()); borderDistance[SideBarLocation::SideBarRight] = (distance < minBorderDistance) ? 0 : distance; if (!borderDistance[SideBarLocation::SideBarRight]) borders |= BorderRight; auto sideTab = SideBarLocation::SideBarRight; switch (borders) { // 1. It's touching all borders case BorderAll: sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight; break; // 2. It's touching 3 borders case BorderVerticalBottom: sideTab = SideBarLocation::SideBarBottom; break; case BorderVerticalTop: sideTab = SideBarLocation::SideBarTop; break; case BorderHorizontalLeft: sideTab = SideBarLocation::SideBarLeft; break; case BorderHorizontalRight: sideTab = SideBarLocation::SideBarRight; break; // 3. Its touching horizontal or vertical borders case BorderVertical: sideTab = SideBarLocation::SideBarBottom; break; case BorderHorizontal: sideTab = SideBarLocation::SideBarRight; break; // 4. Its in a corner case BorderTopLeft: sideTab = horizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarLeft; break; case BorderTopRight: sideTab = horizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarRight; break; case BorderBottomLeft: sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarLeft; break; case BorderBottomRight: sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight; break; // 5 Ists touching only one border case BorderLeft: sideTab = SideBarLocation::SideBarLeft; break; case BorderRight: sideTab = SideBarLocation::SideBarRight; break; case BorderTop: sideTab = SideBarLocation::SideBarTop; break; case BorderBottom: sideTab = SideBarLocation::SideBarBottom; break; } return sideTab; } void DockAreaWidget::setAutoHide(bool enable, SideBarLocation location, int tabIndex) { if (!isAutoHideFeatureEnabled()) return; if (!enable) { if (isAutoHide()) d->m_autoHideDockContainer->moveContentsToParent(); return; } // If this is already an auto hide container, then move it to new location. if (isAutoHide()) { d->m_autoHideDockContainer->moveToNewSideBarLocation(location, tabIndex); return; } auto area = (SideBarNone == location) ? calculateSideTabBarArea() : location; for (const auto dockWidget : openedDockWidgets()) { if (enable == isAutoHide()) continue; if (!dockWidget->features().testFlag(DockWidget::DockWidgetPinnable)) continue; dockContainer()->createAndSetupAutoHideContainer(area, dockWidget, tabIndex++); } } void DockAreaWidget::toggleAutoHide(SideBarLocation location) { if (!isAutoHideFeatureEnabled()) return; setAutoHide(!isAutoHide(), location); } void DockAreaWidget::closeOtherAreas() { dockContainer()->closeOtherAreas(this); } void DockAreaWidget::setFloating() { d->m_titleBar->setAreaFloating(); } DockAreaTitleBar *DockAreaWidget::titleBar() const { return d->m_titleBar; } bool DockAreaWidget::isCentralWidgetArea() const { if (dockWidgetsCount() != 1) return false; return dockManager()->centralWidget() == dockWidgets().constFirst(); } bool DockAreaWidget::containsCentralWidget() const { auto centralWidget = dockManager()->centralWidget(); for (const auto &dockWidget : dockWidgets()) { if (dockWidget == centralWidget) return true; } return false; } QSize DockAreaWidget::minimumSizeHint() const { QSize s(0, 0); if (d->m_contentsLayout) { for (int i = 0; i < d->m_contentsLayout->count(); ++i) { auto widget = d->m_contentsLayout->widget(i); s = s.expandedTo(widget->minimumSizeHint()); } } if (d->m_titleBar->isVisible()) s += QSize(0, d->m_titleBar->minimumSizeHint().height()); return s; } void DockAreaWidget::onDockWidgetFeaturesChanged() { if (d->m_titleBar) d->updateTitleBarButtonStates(); } bool DockAreaWidget::isTopLevelArea() const { auto container = dockContainer(); if (!container) return false; return (container->topLevelDockArea() == this); } #ifdef Q_OS_WIN bool DockAreaWidget::event(QEvent *e) { switch (e->type()) { case QEvent::PlatformSurface: return true; default: break; } return Super::event(e); } #endif } // namespace ADS