diff --git a/src/libs/advanceddockingsystem/CMakeLists.txt b/src/libs/advanceddockingsystem/CMakeLists.txt index 19d89f41d6c..2ec5c94b422 100644 --- a/src/libs/advanceddockingsystem/CMakeLists.txt +++ b/src/libs/advanceddockingsystem/CMakeLists.txt @@ -3,6 +3,9 @@ add_qtc_library(AdvancedDockingSystem SOURCES ads_globals.cpp ads_globals.h advanceddockingsystemtr.h + autohidedockcontainer.cpp autohidedockcontainer.h + autohidesidebar.cpp autohidesidebar.h + autohidetab.cpp autohidetab.h dockareatabbar.cpp dockareatabbar.h dockareatitlebar.cpp dockareatitlebar.h dockareawidget.cpp dockareawidget.h @@ -19,6 +22,8 @@ add_qtc_library(AdvancedDockingSystem floatingdockcontainer.cpp floatingdockcontainer.h floatingdragpreview.cpp floatingdragpreview.h iconprovider.cpp iconprovider.h + pushbutton.cpp pushbutton.h + resizehandle.cpp resizehandle.h workspace.cpp workspace.h workspacedialog.cpp workspacedialog.h workspaceinputdialog.cpp workspaceinputdialog.h @@ -28,6 +33,7 @@ add_qtc_library(AdvancedDockingSystem extend_qtc_library(AdvancedDockingSystem INCLUDES linux + CONDITION UNIX AND NOT APPLE SOURCES linux/floatingwidgettitlebar.cpp linux/floatingwidgettitlebar.h ) diff --git a/src/libs/advanceddockingsystem/ads_globals.cpp b/src/libs/advanceddockingsystem/ads_globals.cpp index 252a31f9d30..e4615a69f87 100644 --- a/src/libs/advanceddockingsystem/ads_globals.cpp +++ b/src/libs/advanceddockingsystem/ads_globals.cpp @@ -14,10 +14,19 @@ #include #include +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include +#include +#include +#endif + namespace ADS { namespace internal { +const int g_floatingWidgetDragStartEvent = QEvent::registerEventType(); +const int g_dockedWidgetDragStartEvent = QEvent::registerEventType(); + void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to) { int index = splitter->indexOf(from); @@ -25,6 +34,16 @@ void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to) splitter->insertWidget(index, to); } +void hideEmptyParentSplitters(DockSplitter *splitter) +{ + while (splitter && splitter->isVisible()) { + if (!splitter->hasVisibleContent()) + splitter->hide(); + + splitter = internal::findParent(splitter); + } +} + DockInsertParam dockAreaInsertParameters(DockWidgetArea area) { switch (area) { @@ -54,18 +73,8 @@ QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity) return transparentPixmap; } -void hideEmptyParentSplitters(DockSplitter *splitter) -{ - while (splitter && splitter->isVisible()) { - if (!splitter->hasVisibleContent()) { - splitter->hide(); - } - splitter = internal::findParent(splitter); - } -} - void setButtonIcon(QAbstractButton *button, - QStyle::StandardPixmap standarPixmap, + QStyle::StandardPixmap standardPixmap, ADS::eIcon customIconId) { // First we try to use custom icons if available @@ -75,12 +84,12 @@ void setButtonIcon(QAbstractButton *button, return; } - if (Utils::HostOsInfo::isLinuxHost()) { - button->setIcon(button->style()->standardIcon(standarPixmap)); + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) { + button->setIcon(button->style()->standardIcon(standardPixmap)); } else { // The standard icons does not look good on high DPI screens so we create // our own "standard" icon here. - QPixmap normalPixmap = button->style()->standardPixmap(standarPixmap, nullptr, button); + QPixmap normalPixmap = button->style()->standardPixmap(standardPixmap, nullptr, button); icon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); icon.addPixmap(normalPixmap, QIcon::Normal); button->setIcon(icon); @@ -98,14 +107,22 @@ void repolishStyle(QWidget *widget, eRepolishChildOptions options) if (RepolishIgnoreChildren == options) return; - QList children = widget->findChildren(QString(), - (RepolishDirectChildren == options) ? Qt::FindDirectChildrenOnly : Qt::FindChildrenRecursively); - for (auto w : children) - { + QList children = widget->findChildren(QString(), + (RepolishDirectChildren == options) + ? Qt::FindDirectChildrenOnly + : Qt::FindChildrenRecursively); + for (auto w : children) { w->style()->unpolish(w); w->style()->polish(w); } } +QRect globalGeometry(QWidget *w) +{ + QRect g = w->geometry(); + g.moveTopLeft(w->mapToGlobal(QPoint(0, 0))); + return g; +} + } // namespace internal } // namespace ADS diff --git a/src/libs/advanceddockingsystem/ads_globals.h b/src/libs/advanceddockingsystem/ads_globals.h index d2f882d8e71..ef294ba4ef1 100644 --- a/src/libs/advanceddockingsystem/ads_globals.h +++ b/src/libs/advanceddockingsystem/ads_globals.h @@ -55,7 +55,12 @@ enum DockWidgetArea { }; Q_DECLARE_FLAGS(DockWidgetAreas, DockWidgetArea) -enum eTitleBarButton { TitleBarButtonTabsMenu, TitleBarButtonUndock, TitleBarButtonClose }; +enum eTitleBarButton { + TitleBarButtonTabsMenu, + TitleBarButtonUndock, + TitleBarButtonClose, + TitleBarButtonAutoHide +}; /** * The different dragging states @@ -71,42 +76,49 @@ enum eDragState { * The different icons used in the UI */ enum eIcon { - TabCloseIcon, //!< TabCloseIcon - DockAreaMenuIcon, //!< DockAreaMenuIcon - DockAreaUndockIcon, //!< DockAreaUndockIcon - DockAreaCloseIcon, //!< DockAreaCloseIcon - FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon + TabCloseIcon, //!< TabCloseIcon + AutoHideIcon, //!< AutoHideIcon + DockAreaMenuIcon, //!< DockAreaMenuIcon + DockAreaUndockIcon, //!< DockAreaUndockIcon + DockAreaCloseIcon, //!< DockAreaCloseIcon + FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon + FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon + FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon - IconCount, //!< just a delimiter for range checks + IconCount, //!< just a delimiter for range checks }; /** - * For bitwise combination of dock wdget features + * For bitwise combination of dock widget features */ -enum eBitwiseOperator -{ - BitwiseAnd, - BitwiseOr -}; +enum eBitwiseOperator { BitwiseAnd, BitwiseOr }; + +/** + * Each dock container supports 4 sidebars + */ +enum SideBarLocation { SideBarTop, SideBarLeft, SideBarRight, SideBarBottom, SideBarNone }; namespace internal { -const char *const closedProperty = "close"; -const char *const dirtyProperty = "dirty"; + +const char *const g_closedProperty = "close"; +const char *const g_dirtyProperty = "dirty"; + +extern const int g_floatingWidgetDragStartEvent; +extern const int g_dockedWidgetDragStartEvent; /** - * Replace the from widget in the given splitter with the To widget + * Replace the \p from widget in the given splitter with the \p to widget. */ void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to); /** - * This function walks the splitter tree upwards to hides all splitters - * that do not have visible content + * This function walks the splitter tree upwards to hide all splitters that do not + * have visible content. */ void hideEmptyParentSplitters(DockSplitter *firstParentSplitter); /** - * Convenience class for QPair to provide better naming than first and - * second + * Convenience class for QPair to provide better naming than first and second. */ class DockInsertParam : public QPair { @@ -121,18 +133,15 @@ public: }; /** - * Returns the insertion parameters for the given dock area + * Returns the insertion parameters for the given dock area. */ DockInsertParam dockAreaInsertParameters(DockWidgetArea area); /** - * Searches for the parent widget of the given type. - * Returns the parent widget of the given widget or 0 if the widget is not - * child of any widget of type T - * - * It is not safe to use this function in in DockWidget because only - * the current dock widget has a parent. All dock widgets that are not the - * current dock widget in a dock area have no parent. + * Searches for the parent widget of the given type. Returns the parent widget of the given + * widget or 0 if the widget is not child of any widget of type T. + * It is not safe to use this function in in DockWidget because only the current dock widget has a + * parent. All dock widgets that are not the current dock widget in a dock area have no parent. */ template T findParent(const QWidget *widget) @@ -149,14 +158,13 @@ T findParent(const QWidget *widget) } /** - * Creates a semi transparent pixmap from the given pixmap Source. - * The Opacity parameter defines the opacity from completely transparent (0.0) - * to completely opaque (1.0) + * Creates a semi transparent pixmap from the given pixmap source. The opacity parameter defines + * the opacity from completely transparent (0.0) to completely opaque (1.0). */ QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity); /** - * Helper function for settings flags in a QFlags instance. + * Helper function for setting flags in a QFlags instance. */ template void setFlag(T &flags, typename T::enum_type flag, bool on = true) @@ -165,8 +173,7 @@ void setFlag(T &flags, typename T::enum_type flag, bool on = true) } /** - * Helper function for settings tooltips without cluttering the code with - * tests for preprocessor macros + * Helper function for setting tooltips without cluttering the code with tests for preprocessor macros. */ template void setToolTip(QObjectPtr obj, const QString &tip) @@ -180,20 +187,20 @@ void setToolTip(QObjectPtr obj, const QString &tip) } /** - * Helper function to set the icon of a certain button. - * Use this function to set the icons for the dock area and dock widget buttons. - * The function first uses the CustomIconId to get an icon from the - * IconProvider. You can register your custom icons with the icon provider, if - * you do not want to use the default buttons and if you do not want to use - * stylesheets. - * If the IconProvider does not return a valid icon (icon is null), the function - * fetches the given standard pixmap from the QStyle. - * param[in] Button The button whose icons are to be set - * param[in] StandardPixmap The standard pixmap to be used for the button - * param[in] CustomIconId The identifier for the custom icon. + * Helper function to set the icon of a certain button. Use this function to set the icons for + * the dock area and dock widget buttons. + * The function first uses the \p customIconId to get an icon from the IconProvider. You can + * register your custom icons with the icon provider, if you do not want to use the default buttons + * and if you do not want to use stylesheets. + * If the IconProvider does not return a valid icon (icon is null), the function fetches the given + * standard pixmap from the QStyle. + * param[in] button The button whose icons are to be set + * param[in] standardPixmap The standard pixmap to be used for the button + * param[in] customIconId The identifier for the custom icon. */ -void setButtonIcon(QAbstractButton *button, QStyle::StandardPixmap standarPixmap, - ADS::eIcon CustomIconId); +void setButtonIcon(QAbstractButton *button, + QStyle::StandardPixmap standardPixmap, + ADS::eIcon customIconId); enum eRepolishChildOptions { @@ -203,10 +210,14 @@ enum eRepolishChildOptions }; /** - * Calls unpolish() / polish for the style of the given widget to update - * stylesheet if a property changes + * Calls unpolish() / polish for the style of the given widget to update stylesheet if a property changes. */ void repolishStyle(QWidget *widget, eRepolishChildOptions options = RepolishIgnoreChildren); +/** + * Returns the geometry of the given widget in global space. + */ +QRect globalGeometry(QWidget *widget); + } // namespace internal } // namespace ADS diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs index 8d758b3f7eb..e603a039827 100644 --- a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -15,6 +15,9 @@ QtcLibrary { files: [ "ads_globals.cpp", "ads_globals.h", "advanceddockingsystemtr.h", + "autohidedockcontainer.cpp", "autohidedockcontainer.h", + "autohidesidebar.cpp", "autohidesidebar.h", + "autohidetab.cpp", "autohidetab.h", "dockareatabbar.cpp", "dockareatabbar.h", "dockareatitlebar.cpp", "dockareatitlebar.h", "dockareawidget.cpp", "dockareawidget.h", @@ -31,6 +34,8 @@ QtcLibrary { "floatingdockcontainer.cpp", "floatingdockcontainer.h", "floatingdragpreview.cpp", "floatingdragpreview.h", "iconprovider.cpp", "iconprovider.h", + "pushbutton.cpp", "pushbutton.h", + "resizehandle.cpp", "resizehandle.h", "workspace.cpp", "workspace.h", "workspacedialog.cpp", "workspacedialog.h", "workspaceinputdialog.cpp", "workspaceinputdialog.h", diff --git a/src/libs/advanceddockingsystem/autohidedockcontainer.cpp b/src/libs/advanceddockingsystem/autohidedockcontainer.cpp new file mode 100644 index 00000000000..18a16ebaffc --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidedockcontainer.cpp @@ -0,0 +1,512 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidedockcontainer.h" + +#include "ads_globals_p.h" +#include "autohidesidebar.h" +#include "autohidetab.h" +#include "dockareawidget.h" +#include "dockcomponentsfactory.h" +#include "dockmanager.h" +#include "resizehandle.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ADS { + +static const int resizeMargin = 30; + +bool static isHorizontalArea(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarBottom: + return true; + case SideBarLocation::SideBarLeft: + case SideBarLocation::SideBarRight: + return false; + default: + return true; + } + + return true; +} + +Qt::Edge static edgeFromSideTabBarArea(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarTop: + return Qt::BottomEdge; + case SideBarLocation::SideBarBottom: + return Qt::TopEdge; + case SideBarLocation::SideBarLeft: + return Qt::RightEdge; + case SideBarLocation::SideBarRight: + return Qt::LeftEdge; + default: + return Qt::LeftEdge; + } + + return Qt::LeftEdge; +} + +int resizeHandleLayoutPosition(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarBottom: + case SideBarLocation::SideBarRight: + return 0; + + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarLeft: + return 1; + + default: + return 0; + } + + return 0; +} + +/** + * Private data of CAutoHideDockContainer - pimpl + */ +struct AutoHideDockContainerPrivate +{ + AutoHideDockContainer *q; + DockAreaWidget *m_dockArea{nullptr}; + DockWidget *m_dockWidget{nullptr}; + SideBarLocation m_sideTabBarArea = SideBarNone; + QBoxLayout *m_layout = nullptr; + ResizeHandle *m_resizeHandle = nullptr; + QSize m_size; // creates invalid size + QPointer m_sideTab; + + /** + * Private data constructor + */ + AutoHideDockContainerPrivate(AutoHideDockContainer *parent); + + /** + * Convenience function to get a dock widget area + */ + DockWidgetArea getDockWidgetArea(SideBarLocation area) + { + switch (area) { + case SideBarLocation::SideBarLeft: + return LeftDockWidgetArea; + case SideBarLocation::SideBarRight: + return RightDockWidgetArea; + case SideBarLocation::SideBarBottom: + return BottomDockWidgetArea; + case SideBarLocation::SideBarTop: + return TopDockWidgetArea; + default: + return LeftDockWidgetArea; + } + + return LeftDockWidgetArea; + } + + /** + * Update the resize limit of the resize handle + */ + void updateResizeHandleSizeLimitMax() + { + auto rect = q->dockContainer()->contentRect(); + const auto maxResizeHandleSize = m_resizeHandle->orientation() == Qt::Horizontal + ? rect.width() + : rect.height(); + m_resizeHandle->setMaxResizeSize(maxResizeHandleSize - resizeMargin); + } + + /** + * Convenience function to check, if this is an horizontal area + */ + bool isHorizontal() const { return isHorizontalArea(m_sideTabBarArea); } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent *event) + { + auto dockContainer = q->dockContainer(); + if (dockContainer) + dockContainer->handleAutoHideWidgetEvent(event, q); + } + +}; // struct AutoHideDockContainerPrivate + +AutoHideDockContainerPrivate::AutoHideDockContainerPrivate(AutoHideDockContainer *parent) + : q(parent) +{} + +DockContainerWidget *AutoHideDockContainer::dockContainer() const +{ + return internal::findParent(this); +} + +AutoHideDockContainer::AutoHideDockContainer(DockWidget *dockWidget, + SideBarLocation area, + DockContainerWidget *parent) + : QFrame(parent) + , d(new AutoHideDockContainerPrivate(this)) +{ + hide(); // auto hide dock container is initially always hidden + d->m_sideTabBarArea = area; + d->m_sideTab = componentsFactory()->createDockWidgetSideTab(nullptr); + connect(d->m_sideTab, &AutoHideTab::pressed, this, &AutoHideDockContainer::toggleCollapseState); + d->m_dockArea = new DockAreaWidget(dockWidget->dockManager(), parent); + d->m_dockArea->setObjectName("autoHideDockArea"); + d->m_dockArea->setAutoHideDockContainer(this); + + setObjectName("autoHideDockContainer"); + + d->m_layout = new QBoxLayout(isHorizontalArea(area) ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + d->m_resizeHandle = new ResizeHandle(edgeFromSideTabBarArea(area), this); + d->m_resizeHandle->setMinResizeSize(64); + bool opaqueResize = DockManager::testConfigFlag(DockManager::OpaqueSplitterResize); + d->m_resizeHandle->setOpaqueResize(opaqueResize); + d->m_size = d->m_dockArea->size(); + + addDockWidget(dockWidget); + parent->registerAutoHideWidget(this); + // The dock area should not be added to the layout before it contains the dock widget. If you + // add it to the layout before it contains the dock widget then you will likely see this + // warning for OpenGL widgets or QAxWidgets: + // setGeometry: Unable to set geometry XxY+Width+Height on QWidgetWindow/'WidgetClassWindow + d->m_layout->addWidget(d->m_dockArea); + d->m_layout->insertWidget(resizeHandleLayoutPosition(area), d->m_resizeHandle); +} + +void AutoHideDockContainer::updateSize() +{ + auto dockContainerParent = dockContainer(); + if (!dockContainerParent) + return; + + auto rect = dockContainerParent->contentRect(); + + switch (sideBarLocation()) { + case SideBarLocation::SideBarTop: + resize(rect.width(), qMin(rect.height() - resizeMargin, d->m_size.height())); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarLeft: + resize(qMin(d->m_size.width(), rect.width() - resizeMargin), rect.height()); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarRight: { + resize(qMin(d->m_size.width(), rect.width() - resizeMargin), rect.height()); + QPoint p = rect.topRight(); + p.rx() -= (width() - 1); + move(p); + } break; + + case SideBarLocation::SideBarBottom: { + resize(rect.width(), qMin(rect.height() - resizeMargin, d->m_size.height())); + QPoint p = rect.bottomLeft(); + p.ry() -= (height() - 1); + move(p); + } break; + + default: + break; + } +} + +AutoHideDockContainer::~AutoHideDockContainer() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + // Remove event filter in case there are any queued messages + qApp->removeEventFilter(this); + if (dockContainer()) + dockContainer()->removeAutoHideWidget(this); + + if (d->m_sideTab) + delete d->m_sideTab; + + delete d; +} + +AutoHideSideBar *AutoHideDockContainer::sideBar() const +{ + if (d->m_sideTab) { + return d->m_sideTab->sideBar(); + } else { + auto container = dockContainer(); + return container ? container->sideTabBar(d->m_sideTabBarArea) : nullptr; + } +} + +AutoHideTab *AutoHideDockContainer::autoHideTab() const +{ + return d->m_sideTab; +} + +DockWidget *AutoHideDockContainer::dockWidget() const +{ + return d->m_dockWidget; +} + +void AutoHideDockContainer::addDockWidget(DockWidget *dockWidget) +{ + if (d->m_dockWidget) { + // Remove the old dock widget at this area + d->m_dockArea->removeDockWidget(d->m_dockWidget); + } + + d->m_dockWidget = dockWidget; + d->m_sideTab->setDockWidget(dockWidget); + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + auto isRestoringState = dockWidget->dockManager()->isRestoringState(); + if (oldDockArea && !isRestoringState) { + // The initial size should be a little bit bigger than the original dock + // area size to prevent that the resize handle of this auto hid dock area + // is near of the splitter of the old dock area. + d->m_size = oldDockArea->size() + QSize(16, 16); + oldDockArea->removeDockWidget(dockWidget); + } + d->m_dockArea->addDockWidget(dockWidget); + updateSize(); +} + +SideBarLocation AutoHideDockContainer::sideBarLocation() const +{ + return d->m_sideTabBarArea; +} + +void AutoHideDockContainer::setSideBarLocation(SideBarLocation sideBarLocation) +{ + if (d->m_sideTabBarArea == sideBarLocation) + return; + + d->m_sideTabBarArea = sideBarLocation; + d->m_layout->removeWidget(d->m_resizeHandle); + d->m_layout->setDirection(isHorizontalArea(sideBarLocation) ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_layout->insertWidget(resizeHandleLayoutPosition(sideBarLocation), d->m_resizeHandle); + d->m_resizeHandle->setHandlePosition(edgeFromSideTabBarArea(sideBarLocation)); + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + +DockAreaWidget *AutoHideDockContainer::dockAreaWidget() const +{ + return d->m_dockArea; +} + +void AutoHideDockContainer::moveContentsToParent() +{ + cleanupAndDelete(); + // If we unpin the auto hide dock widget, then we insert it into the same + // location like it had as a auto hide widget. This brings the least surprise + // to the user and he does not have to search where the widget was inserted. + d->m_dockWidget->setDockArea(nullptr); + auto DockContainer = dockContainer(); + DockContainer->addDockWidget(d->getDockWidgetArea(d->m_sideTabBarArea), d->m_dockWidget); +} + +void AutoHideDockContainer::cleanupAndDelete() +{ + const auto dockWidget = d->m_dockWidget; + if (dockWidget) { + auto sideTab = d->m_sideTab; + sideTab->removeFromSideBar(); + sideTab->setParent(nullptr); + sideTab->hide(); + } + + hide(); + deleteLater(); +} + +void AutoHideDockContainer::saveState(QXmlStreamWriter &s) +{ + s.writeStartElement("widget"); + s.writeAttribute("name", d->m_dockWidget->objectName()); + s.writeAttribute("closed", QString::number(d->m_dockWidget->isClosed() ? 1 : 0)); + s.writeAttribute("size", + QString::number(d->isHorizontal() ? d->m_size.height() : d->m_size.width())); + s.writeEndElement(); +} + +void AutoHideDockContainer::toggleView(bool enable) +{ + if (enable) { + if (d->m_sideTab) + d->m_sideTab->show(); + } else { + if (d->m_sideTab) + d->m_sideTab->hide(); + + hide(); + qApp->removeEventFilter(this); + } +} + +void AutoHideDockContainer::collapseView(bool enable) +{ + if (enable) { + hide(); + qApp->removeEventFilter(this); + } else { + updateSize(); + d->updateResizeHandleSizeLimitMax(); + raise(); + show(); + d->m_dockWidget->dockManager()->setDockWidgetFocused(d->m_dockWidget); + qApp->installEventFilter(this); + } + + qCInfo(adsLog) << Q_FUNC_INFO << enable; + d->m_sideTab->updateStyle(); +} + +void AutoHideDockContainer::toggleCollapseState() +{ + collapseView(isVisible()); +} + +void AutoHideDockContainer::setSize(int size) +{ + if (d->isHorizontal()) + d->m_size.setHeight(size); + else + d->m_size.setWidth(size); + + updateSize(); +} + +/** + * Returns true if the object given in ancestor is an ancestor of the object given in descendant + */ +static bool objectIsAncestorOf(const QObject* descendant, const QObject* ancestor) +{ + if (!ancestor) + return false; + + while (descendant) { + if (descendant == ancestor) + return true; + + descendant = descendant->parent(); + } + + return false; +} + +/** + * Returns true if the object given in ancestor is the object given in descendant + * or if it is an ancestor of the object given in descendant + */ +static bool isObjectOrAncestor(const QObject *descendant, const QObject *ancestor) +{ + if (ancestor && (descendant == ancestor)) + return true; + else + return objectIsAncestorOf(descendant, ancestor); +} + +bool AutoHideDockContainer::eventFilter(QObject *watched, QEvent *event) +{ + // A switch case statement would be nicer here, but we cannot use + // internal::FloatingWidgetDragStartEvent in a switch case + if (event->type() == QEvent::Resize) { + if (!d->m_resizeHandle->isResizing()) + updateSize(); + } else if (event->type() == QEvent::MouseButtonPress) { + auto widget = qobject_cast(watched); + // Ignore non widget events + if (!widget) + return QFrame::eventFilter(watched, event); + + // Now check, if the user clicked into the side tab and ignore this event, + // because the side tab click handler will call collapseView(). If we + // do not ignore this here, then we will collapse the container and the side tab + // click handler will uncollapse it + if (widget == d->m_sideTab.data()) + return QFrame::eventFilter(watched, event); + + // Now we check, if the user clicked inside of this auto hide container. + // If the click is inside of this auto hide container, then we can + // ignore the event, because the auto hide overlay should not get collapsed if + // user works in it + if (isObjectOrAncestor(widget, this)) + return QFrame::eventFilter(watched, event); + + // Ignore the mouse click if it is not inside of this container + if (!isObjectOrAncestor(widget, dockContainer())) + return QFrame::eventFilter(watched, event); + + // User clicked into container - collapse the auto hide widget + collapseView(true); + } else if (event->type() == internal::g_floatingWidgetDragStartEvent) { + // If we are dragging our own floating widget, the we do not need to collapse the view + auto widget = dockContainer()->floatingWidget(); + if (widget != watched) + collapseView(true); + } else if (event->type() == internal::g_dockedWidgetDragStartEvent) { + collapseView(true); + } + + return QFrame::eventFilter(watched, event); +} + +void AutoHideDockContainer::resizeEvent(QResizeEvent *event) +{ + QFrame::resizeEvent(event); + + if (d->m_resizeHandle->isResizing()) { + d->m_size = this->size(); + d->updateResizeHandleSizeLimitMax(); + } +} + +void AutoHideDockContainer::leaveEvent(QEvent *event) +{ + // Resizing of the dock container via the resize handle in non opaque mode + // mays cause a leave event that is not really a leave event. Therefore + // we check here, if we are really outside of our rect. + auto pos = mapFromGlobal(QCursor::pos()); + if (!rect().contains(pos)) + d->forwardEventToDockContainer(event); + + QFrame::leaveEvent(event); +} + +bool AutoHideDockContainer::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Enter: + case QEvent::Hide: + d->forwardEventToDockContainer(event); + break; + + case QEvent::MouseButtonPress: + return true; + break; + + default: + break; + } + + return QFrame::event(event); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidedockcontainer.h b/src/libs/advanceddockingsystem/autohidedockcontainer.h new file mode 100644 index 00000000000..ea844a330a4 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidedockcontainer.h @@ -0,0 +1,142 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" +#include "autohidetab.h" + +#include + +QT_BEGIN_NAMESPACE +class QXmlStreamWriter; +QT_BEGIN_NAMESPACE + +namespace ADS { + +struct AutoHideDockContainerPrivate; +class DockManager; +class DockWidget; +class DockContainerWidget; +class AutoHideSideBar; +class DockAreaWidget; +class DockingStateReader; +struct SideTabBarPrivate; + +/** + * Auto hide container for hosting an auto hide dock widget + */ +class ADS_EXPORT AutoHideDockContainer : public QFrame +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation CONSTANT) // TODO +private: + AutoHideDockContainerPrivate *d; ///< private data (pimpl) + friend struct AutoHideDockContainerPrivate; + friend AutoHideSideBar; + friend SideTabBarPrivate; + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; + virtual void leaveEvent(QEvent *event) override; + virtual bool event(QEvent *event) override; + + /** + * Updates the size considering the size limits and the resize margins + */ + void updateSize(); + + /* + * Saves the state and size + */ + void saveState(QXmlStreamWriter &Stream); + +public: + /** + * Create Auto Hide widget with the given dock widget + */ + AutoHideDockContainer(DockWidget *dockWidget, SideBarLocation area, DockContainerWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~AutoHideDockContainer(); + + /** + * Get's the side tab bar + */ + AutoHideSideBar *sideBar() const; + + /** + * Returns the side tab + */ + AutoHideTab *autoHideTab() const; + + /** + * Get's the dock widget in this dock container + */ + DockWidget *dockWidget() const; + + /** + * Adds a dock widget and removes the previous dock widget + */ + void addDockWidget(DockWidget *dockWidget); + + /** + * Returns the side tab bar area of this Auto Hide dock container + */ + SideBarLocation sideBarLocation() const; + + /** + * Sets a new SideBarLocation. + * If a new side bar location is set, the auto hide dock container needs + * to update its resize handle position + */ + void setSideBarLocation(SideBarLocation sideBarLocation); + + /** + * Returns the dock area widget of this Auto Hide dock container + */ + DockAreaWidget *dockAreaWidget() const; + + /** + * Returns the parent container that hosts this auto hide container + */ + DockContainerWidget *dockContainer() const; + + /** + * Moves the contents to the parent container widget + * Used before removing this Auto Hide dock container + */ + void moveContentsToParent(); + + /** + * Cleanups up the side tab widget and then deletes itself + */ + void cleanupAndDelete(); + + /** + * Toggles the auto Hide dock container widget. This will also hide the side tab widget. + */ + void toggleView(bool enable); + + /** + * Collapses the auto hide dock container widget + * Does not hide the side tab widget + */ + void collapseView(bool enable); + + /** + * Toggles the current collapse state + */ + void toggleCollapseState(); + + /** + * Use this instead of resize. + * Depending on the sidebar location this will set the width or height of this auto hide container. + */ + void setSize(int size); +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidesidebar.cpp b/src/libs/advanceddockingsystem/autohidesidebar.cpp new file mode 100644 index 00000000000..e5c3dbcb090 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidesidebar.cpp @@ -0,0 +1,316 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidesidebar.h" + +#include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidetab.h" +#include "dockareawidget.h" +#include "dockcontainerwidget.h" +#include "dockfocuscontroller.h" +#include "dockingstatereader.h" +#include "dockwidgettab.h" + +#include +#include +#include +#include +#include + +namespace ADS { + +class TabsWidget; + +/** + * Private data class of CSideTabBar class (pimpl) + */ +struct AutoHideSideBarPrivate +{ + /** + * Private data constructor + */ + AutoHideSideBarPrivate(AutoHideSideBar *parent); + + AutoHideSideBar *q; + DockContainerWidget *m_containerWidget; + TabsWidget *m_tabsContainerWidget; + QBoxLayout *m_tabsLayout; + Qt::Orientation m_orientation; + SideBarLocation m_sideTabArea = SideBarLocation::SideBarLeft; + + /** + * Convenience function to check if this is a horizontal side bar + */ + bool isHorizontal() const { return Qt::Horizontal == m_orientation; } + + /** + * Called from viewport to forward event handling to this + */ + void handleViewportEvent(QEvent* e); +}; // struct AutoHideSideBarPrivate + +/** + * This widget stores the tab buttons + */ +class TabsWidget : public QWidget +{ +public: + using QWidget::QWidget; + using Super = QWidget; + AutoHideSideBarPrivate *eventHandler; + + /** + * Returns the size hint as minimum size hint + */ + virtual QSize minimumSizeHint() const override { return Super::sizeHint(); } + + /** + * Forward event handling to EventHandler + */ + virtual bool event(QEvent *e) override + { + eventHandler->handleViewportEvent(e); + return Super::event(e); + } +}; + +AutoHideSideBarPrivate::AutoHideSideBarPrivate(AutoHideSideBar *parent) + : q(parent) +{} + +void AutoHideSideBarPrivate::handleViewportEvent(QEvent* e) +{ + switch (e->type()) { + case QEvent::ChildRemoved: + if (m_tabsLayout->isEmpty()) + q->hide(); + break; + + default: + break; + } +} + +AutoHideSideBar::AutoHideSideBar(DockContainerWidget *parent, SideBarLocation area) + : Super(parent) + , d(new AutoHideSideBarPrivate(this)) +{ + d->m_sideTabArea = area; + d->m_containerWidget = parent; + d->m_orientation = (area == SideBarLocation::SideBarBottom + || area == SideBarLocation::SideBarTop) + ? Qt::Horizontal + : Qt::Vertical; + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->m_tabsContainerWidget = new TabsWidget(); + d->m_tabsContainerWidget->eventHandler = d; + d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->m_tabsContainerWidget->setObjectName("sideTabsContainerWidget"); + + d->m_tabsLayout = new QBoxLayout(d->m_orientation == Qt::Vertical ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); + d->m_tabsLayout->setSpacing(12); + d->m_tabsLayout->addStretch(1); + d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); + setWidget(d->m_tabsContainerWidget); + + setFocusPolicy(Qt::NoFocus); + if (d->isHorizontal()) + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + else + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + hide(); +} + +AutoHideSideBar::~AutoHideSideBar() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + // The SideTabeBar is not the owner of the tabs and to prevent deletion + // we set the parent here to nullptr to remove it from the children + auto tabs = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (auto tab : tabs) + tab->setParent(nullptr); + + delete d; +} + +void AutoHideSideBar::insertTab(int index, AutoHideTab *sideTab) +{ + sideTab->setSideBar(this); + sideTab->installEventFilter(this); + if (index < 0) + d->m_tabsLayout->insertWidget(d->m_tabsLayout->count() - 1, sideTab); + else + d->m_tabsLayout->insertWidget(index, sideTab); + + show(); +} + +AutoHideDockContainer *AutoHideSideBar::insertDockWidget(int index, DockWidget *dockWidget) +{ + auto autoHideContainer = new AutoHideDockContainer(dockWidget, + d->m_sideTabArea, + d->m_containerWidget); + dockWidget->dockManager()->dockFocusController()->clearDockWidgetFocus(dockWidget); + auto tab = autoHideContainer->autoHideTab(); + dockWidget->setSideTabWidget(tab); + insertTab(index, tab); + return autoHideContainer; +} + +void AutoHideSideBar::removeAutoHideWidget(AutoHideDockContainer *autoHideWidget) +{ + autoHideWidget->autoHideTab()->removeFromSideBar(); + auto dockContainer = autoHideWidget->dockContainer(); + if (dockContainer) + dockContainer->removeAutoHideWidget(autoHideWidget); + + autoHideWidget->setParent(nullptr); +} + +void AutoHideSideBar::addAutoHideWidget(AutoHideDockContainer *autoHideWidget) +{ + auto sideBar = autoHideWidget->autoHideTab()->sideBar(); + if (sideBar == this) + return; + + if (sideBar) + sideBar->removeAutoHideWidget(autoHideWidget); + + autoHideWidget->setParent(d->m_containerWidget); + autoHideWidget->setSideBarLocation(d->m_sideTabArea); + d->m_containerWidget->registerAutoHideWidget(autoHideWidget); + insertTab(-1, autoHideWidget->autoHideTab()); +} + +void AutoHideSideBar::removeTab(AutoHideTab *sideTab) +{ + sideTab->removeEventFilter(this); + d->m_tabsLayout->removeWidget(sideTab); + if (d->m_tabsLayout->isEmpty()) + hide(); +} + +bool AutoHideSideBar::eventFilter(QObject *watched, QEvent *event) +{ + auto tab = qobject_cast(watched); + if (!tab) + return false; + + switch (event->type()) { + case QEvent::ShowToParent: + show(); + break; + + case QEvent::HideToParent: + if (!hasVisibleTabs()) + hide(); + break; + + default: + break; + } + + return false; +} + +Qt::Orientation AutoHideSideBar::orientation() const +{ + return d->m_orientation; +} + +AutoHideTab *AutoHideSideBar::tabAt(int index) const +{ + return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); +} + +int AutoHideSideBar::tabCount() const +{ + return d->m_tabsLayout->count() - 1; +} + +int AutoHideSideBar::visibleTabCount() const +{ + int count = 0; + auto parent = parentWidget(); + for (auto i = 0; i < tabCount(); i++) { + if (tabAt(i)->isVisibleTo(parent)) + count++; + } + + return count; +} + +bool AutoHideSideBar::hasVisibleTabs() const +{ + auto parent = parentWidget(); + for (auto i = 0; i < tabCount(); i++) { + if (tabAt(i)->isVisibleTo(parent)) + return true; + } + + return false; +} + +SideBarLocation AutoHideSideBar::sideBarLocation() const +{ + return d->m_sideTabArea; +} + +void AutoHideSideBar::saveState(QXmlStreamWriter &s) const +{ + if (!tabCount()) + return; + + s.writeStartElement("sideBar"); + s.writeAttribute("area", QString::number(sideBarLocation())); + s.writeAttribute("tabs", QString::number(tabCount())); + + for (auto i = 0; i < tabCount(); ++i) { + auto tab = tabAt(i); + if (!tab) + continue; + + tab->dockWidget()->autoHideDockContainer()->saveState(s); + } + + s.writeEndElement(); +} + +QSize AutoHideSideBar::minimumSizeHint() const +{ + QSize size = sizeHint(); + size.setWidth(10); + return size; +} + +QSize AutoHideSideBar::sizeHint() const +{ + return d->m_tabsContainerWidget->sizeHint(); +} + +int AutoHideSideBar::spacing() const +{ + return d->m_tabsLayout->spacing(); +} + +void AutoHideSideBar::setSpacing(int spacing) +{ + d->m_tabsLayout->setSpacing(spacing); +} + +DockContainerWidget *AutoHideSideBar::dockContainer() const +{ + return d->m_containerWidget; +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidesidebar.h b/src/libs/advanceddockingsystem/autohidesidebar.h new file mode 100644 index 00000000000..0376fd4ba35 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidesidebar.h @@ -0,0 +1,157 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include "autohidetab.h" + +#include + +class QXmlStreamWriter; + +namespace ADS { + +struct AutoHideSideBarPrivate; +class DockContainerWidgetPrivate; +class DockContainerWidget; +class AutoHideTab; +class AutoHideDockContainer; +class DockingStateReader; + +/** + * Side tab bar widget that is shown at the edges of a dock container. + * The tab bar is only visible, if it contains visible content, that means if + * it contains visible tabs. If it is empty or all tabs are hidden, then the + * side bar is also hidden. As soon as one single tab becomes visible, this + * tab bar will be shown. + * The CAutoHideSideBar uses a QScrollArea here, to enable proper resizing. + * If the side bar contains many tabs, then the tabs are simply clipped - this + * is the same like in visual studio + */ +class ADS_EXPORT AutoHideSideBar : public QScrollArea +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation) + Q_PROPERTY(Qt::Orientation orientation READ orientation) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + +private: + AutoHideSideBarPrivate* d; ///< private data (pimpl) + friend struct AutoHideSideBarPrivate; + friend class DockWidgetSideTab; + friend DockContainerWidgetPrivate; + friend DockContainerWidget; + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &stream) const; + + /** + * Inserts the given dock widget tab at the given position. + * An Index value of -1 appends the side tab at the end. + */ + void insertTab(int index, AutoHideTab *sideTab); + +public: + using Super = QScrollArea; + + /** + * Default Constructor + */ + AutoHideSideBar(DockContainerWidget *parent, SideBarLocation area); + + /** + * Virtual Destructor + */ + virtual ~AutoHideSideBar(); + + /** + * Removes the given DockWidgetSideTab from the tabbar + */ + void removeTab(AutoHideTab *sideTab); + + /** + * Insert dock widget into the side bar. The function creates the auto hide dock container, + * inserts the auto hide tab + */ + AutoHideDockContainer *insertDockWidget(int index, DockWidget *dockWidget); + + /** + * Removes the auto hide widget from this side bar + */ + void removeAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Adds the given AutoHideWidget to this sidebar. If the AutoHideWidget is in another sidebar, + * then it will be removed from this sidebar. + */ + void addAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Returns orientation of side tab. + */ + Qt::Orientation orientation() const; + + /** + * Get the side tab widget at position, returns nullptr if it's out of bounds. + */ + AutoHideTab *tabAt(int index) const; + + /** + * Gets the count of the tab widgets. + */ + int tabCount() const; + + /** + * Returns the number of visible tabs to its parent widget. + */ + int visibleTabCount() const; + + /** + * Returns true, if the sidebar contains visible tabs to its parent widget. + * The function returns as soon as it finds the first visible tab. That means, if you just want + * to find out if there are visible tabs then this function is quicker than visibleTabCount(). + */ + bool hasVisibleTabs() const; + + /** + * Getter for side tab bar area property + */ + SideBarLocation sideBarLocation() const; + + /** + * Overrides the minimumSizeHint() function of QScrollArea + * The minimumSizeHint() is bigger than the sizeHint () for the scroll + * area because even if the scrollbars are invisible, the required speace + * is reserved in the minimumSizeHint(). This override simply returns sizeHint(); + */ + virtual QSize minimumSizeHint() const override; + + /** + * The function provides a sizeHint that matches the height of the internal viewport. + */ + virtual QSize sizeHint() const override; + + /** + * Getter for spacing property - returns the spacing of the tabs + */ + int spacing() const; + + /** + * Setter for spacing property - sets the spacing + */ + void setSpacing(int spacing); + + /** + * Returns the dock container that hosts this sideBar() + */ + DockContainerWidget *dockContainer() const; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidetab.cpp b/src/libs/advanceddockingsystem/autohidetab.cpp new file mode 100644 index 00000000000..1036856b402 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidetab.cpp @@ -0,0 +1,203 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidetab.h" + +#include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "dockmanager.h" +#include "dockwidget.h" + +#include +#include +#include + +namespace ADS { +/** + * Private data class of CDockWidgetTab class (pimpl) + */ +struct AutoHideTabPrivate +{ + AutoHideTab *q; + DockWidget *m_dockWidget = nullptr; + AutoHideSideBar *m_sideBar = nullptr; + Qt::Orientation m_orientation{Qt::Vertical}; + QElapsedTimer m_timeSinceHoverMousePress; + + /** + * Private data constructor + */ + AutoHideTabPrivate(AutoHideTab *parent); + + /** + * Update the orientation, visibility and spacing based on the area of the side bar + */ + void updateOrientation(); + + /** + * Convenience function to ease dock container access + */ + DockContainerWidget *dockContainer() const + { + return m_dockWidget ? m_dockWidget->dockContainer() : nullptr; + } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent *event) + { + auto container = dockContainer(); + if (container) + container->handleAutoHideWidgetEvent(event, q); + } +}; // struct DockWidgetTabPrivate + +AutoHideTabPrivate::AutoHideTabPrivate(AutoHideTab *parent) + : q(parent) +{} + +void AutoHideTabPrivate::updateOrientation() +{ + bool iconOnly = DockManager::testAutoHideConfigFlag(DockManager::AutoHideSideBarsIconOnly); + if (iconOnly && !q->icon().isNull()) { + q->setText(""); + q->setOrientation(Qt::Horizontal); + } else { + auto area = m_sideBar->sideBarLocation(); + q->setOrientation((area == SideBarBottom || area == SideBarTop) ? Qt::Horizontal + : Qt::Vertical); + } +} + +void AutoHideTab::setSideBar(AutoHideSideBar *sideTabBar) +{ + d->m_sideBar = sideTabBar; + if (d->m_sideBar) + d->updateOrientation(); +} + +AutoHideSideBar *AutoHideTab::sideBar() const +{ + return d->m_sideBar; +} + +void AutoHideTab::removeFromSideBar() +{ + if (d->m_sideBar == nullptr) + return; + + d->m_sideBar->removeTab(this); + setSideBar(nullptr); +} + +AutoHideTab::AutoHideTab(QWidget *parent) + : PushButton(parent) + , d(new AutoHideTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation); + setFocusPolicy(Qt::NoFocus); +} + +AutoHideTab::~AutoHideTab() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void AutoHideTab::updateStyle() +{ + internal::repolishStyle(this, internal::RepolishDirectChildren); + update(); +} + +SideBarLocation AutoHideTab::sideBarLocation() const +{ + if (d->m_sideBar) + return d->m_sideBar->sideBarLocation(); + + return SideBarLeft; +} + +void AutoHideTab::setOrientation(Qt::Orientation value) +{ + d->m_orientation = value; + if (orientation() == Qt::Horizontal) { + setMinimumWidth(100); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + } else { + setMinimumHeight(100); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + } + + PushButton::setButtonOrientation((Qt::Horizontal == value) ? PushButton::Horizontal + : PushButton::VerticalTopToBottom); + updateStyle(); +} + +Qt::Orientation AutoHideTab::orientation() const +{ + return d->m_orientation; +} + +bool AutoHideTab::isActiveTab() const +{ + if (d->m_dockWidget && d->m_dockWidget->autoHideDockContainer()) + return d->m_dockWidget->autoHideDockContainer()->isVisible(); + + return false; +} + +DockWidget *AutoHideTab::dockWidget() const +{ + return d->m_dockWidget; +} + +void AutoHideTab::setDockWidget(DockWidget *dockWidget) +{ + if (!dockWidget) + return; + + d->m_dockWidget = dockWidget; + setText(dockWidget->windowTitle()); + setIcon(d->m_dockWidget->icon()); + setToolTip(dockWidget->windowTitle()); +} + +bool AutoHideTab::event(QEvent *event) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideShowOnMouseOver)) + return Super::event(event); + + switch (event->type()) { + case QEvent::Enter: + case QEvent::Leave: + d->forwardEventToDockContainer(event); + break; + + case QEvent::MouseButtonPress: + // If AutoHideShowOnMouseOver is active, then the showing is triggered by a MousePressEvent + // sent to this tab. To prevent accidental hiding of the tab by a mouse click, we wait at + // least 500 ms before we accept the mouse click. + if (!event->spontaneous()) { + d->m_timeSinceHoverMousePress.restart(); + d->forwardEventToDockContainer(event); + } else if (d->m_timeSinceHoverMousePress.hasExpired(500)) { + d->forwardEventToDockContainer(event); + } + break; + + default: + break; + } + return Super::event(event); +} + +bool AutoHideTab::iconOnly() const +{ + return DockManager::testAutoHideConfigFlag(DockManager::AutoHideSideBarsIconOnly) + && !icon().isNull(); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidetab.h b/src/libs/advanceddockingsystem/autohidetab.h new file mode 100644 index 00000000000..ae74542914a --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidetab.h @@ -0,0 +1,107 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "pushbutton.h" + +#include "ads_globals.h" + +namespace ADS { + +struct AutoHideTabPrivate; +class DockWidget; +class AutoHideSideBar; +class DockWidgetTab; +class DockContainerWidgetPrivate; + +/** + * A dock widget Side tab that shows a title or an icon. + * The dock widget tab is shown in the side tab bar to switch between pinned dock widgets. + */ +class ADS_EXPORT AutoHideTab : public PushButton +{ + Q_OBJECT + + Q_PROPERTY(int sideBarLocation READ sideBarLocation CONSTANT) + Q_PROPERTY(Qt::Orientation orientation READ orientation CONSTANT) + Q_PROPERTY(bool activeTab READ isActiveTab CONSTANT) + Q_PROPERTY(bool iconOnly READ iconOnly CONSTANT) + +private: + AutoHideTabPrivate *d; ///< private data (pimpl) + friend struct AutoHideTabPrivate; + friend class DockWidget; + friend class AutoHideDockContainer; + friend class AutoHideSideBar; + friend class DockAreaWidget; + friend class DockContainerWidget; + friend DockContainerWidgetPrivate; + +protected: + void setSideBar(AutoHideSideBar *sideTabBar); + void removeFromSideBar(); + virtual bool event(QEvent *event) override; + +public: + using Super = PushButton; + + /** + * Default Constructor + * param[in] parent The parent widget of this title bar + */ + AutoHideTab(QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~AutoHideTab(); + + /** + * Update stylesheet style if a property changes. + */ + void updateStyle(); + + /** + * Getter for side tab bar area property. + */ + SideBarLocation sideBarLocation() const; + + /** + * Set orientation vertical or horizontal. + */ + void setOrientation(Qt::Orientation value); + + /** + * Returns the current orientation. + */ + Qt::Orientation orientation() const; + + /** + * Returns true, if this is the active tab. The tab is active if the auto hide widget is visible. + */ + bool isActiveTab() const; + + /** + * Returns the dock widget this belongs to. + */ + DockWidget *dockWidget() const; + + /** + * Sets the dock widget that is controlled by this tab. + */ + void setDockWidget(DockWidget *dockWidget); + + /** + * Returns true if the auto hide config flag AutoHideSideBarsIconOnly is set and if + * the tab has an icon - that means the icon is not null. + */ + bool iconOnly() const; + + /** + * Returns the side bar that contains this tab or a nullptr if the tab is not in a side bar. + */ + AutoHideSideBar *sideBar() const; +}; // class AutoHideTab + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp index 62f0b80b499..7aa9a687c06 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.cpp +++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp @@ -13,340 +13,353 @@ #include #include #include +#include #include #include -namespace ADS +namespace ADS { +/** + * Private data class of DockAreaTabBar class (pimpl) + */ +class DockAreaTabBarPrivate { +public: + DockAreaTabBar *q; + DockAreaWidget *m_dockArea = nullptr; + QWidget *m_tabsContainerWidget = nullptr; + QBoxLayout *m_tabsLayout = nullptr; + int m_currentIndex = -1; + /** - * Private data class of DockAreaTabBar class (pimpl) + * Private data constructor */ - class DockAreaTabBarPrivate - { - public: - DockAreaTabBar *q; - DockAreaWidget *m_dockArea = nullptr; - QWidget *m_tabsContainerWidget = nullptr; - QBoxLayout *m_tabsLayout = nullptr; - int m_currentIndex = -1; + DockAreaTabBarPrivate(DockAreaTabBar *parent); - /** - * Private data constructor - */ - DockAreaTabBarPrivate(DockAreaTabBar *parent); + /** + * Update tabs after current index changed or when tabs are removed. + * The function reassigns the stylesheet to update the tabs. + */ + void updateTabs(); - /** - * Update tabs after current index changed or when tabs are removed. - * The function reassigns the stylesheet to update the tabs - */ - void updateTabs(); + /** + * Convenience function to access first tab. + */ + DockWidgetTab *firstTab() const { return q->tab(0); } - /** - * Convenience function to access first tab - */ - DockWidgetTab *firstTab() const { return q->tab(0); } + /** + * Convenience function to access last tab. + */ + DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); } +}; // class DockAreaTabBarPrivate - /** - * Convenience function to access last tab - */ - DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); } - }; // class DockAreaTabBarPrivate +DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent) + : q(parent) +{} - DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent) - : q(parent) - {} +void DockAreaTabBarPrivate::updateTabs() +{ + // Set active TAB and update all other tabs to be inactive + for (int i = 0; i < q->count(); ++i) { + auto tabWidget = q->tab(i); + if (!tabWidget) + continue; - void DockAreaTabBarPrivate::updateTabs() - { - // Set active TAB and update all other tabs to be inactive - for (int i = 0; i < q->count(); ++i) { - auto tabWidget = q->tab(i); - if (!tabWidget) - continue; + if (i == m_currentIndex) { + tabWidget->show(); + tabWidget->setActiveTab(true); + // Sometimes the synchronous calculation of the rectangular area fails. Therefore we + // use QTimer::singleShot here to execute the call within the event loop - see #520. + QTimer::singleShot(0, q, [&, tabWidget] { q->ensureWidgetVisible(tabWidget); }); + } else { + tabWidget->setActiveTab(false); + } + } +} - if (i == m_currentIndex) { - tabWidget->show(); - tabWidget->setActiveTab(true); - q->ensureWidgetVisible(tabWidget); - } else { - tabWidget->setActiveTab(false); +DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) + : QScrollArea(parent) + , d(new DockAreaTabBarPrivate(this)) +{ + d->m_dockArea = parent; + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->m_tabsContainerWidget = new QWidget(); + d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->m_tabsContainerWidget->setObjectName("tabsContainerWidget"); + d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); + d->m_tabsLayout->setSpacing(0); + d->m_tabsLayout->addStretch(1); + d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); + setWidget(d->m_tabsContainerWidget); +} + +DockAreaTabBar::~DockAreaTabBar() +{ + delete d; +} + +void DockAreaTabBar::wheelEvent(QWheelEvent *event) +{ + event->accept(); + const int direction = event->angleDelta().y(); + if (direction < 0) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); + else + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); +} + +void DockAreaTabBar::setCurrentIndex(int index) +{ + if (index == d->m_currentIndex) + return; + + if (index < -1 || index > (count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + emit currentChanging(index); + d->m_currentIndex = index; + d->updateTabs(); + updateGeometry(); + emit currentChanged(index); +} + +int DockAreaTabBar::count() const +{ + // The tab bar contains a stretch item as last item + return d->m_tabsLayout->count() - 1; +} + +void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab) +{ + d->m_tabsLayout->insertWidget(index, dockWidgetTab); + connect(dockWidgetTab, &DockWidgetTab::clicked, this, [this, dockWidgetTab] { + onTabClicked(dockWidgetTab); + }); + connect(dockWidgetTab, &DockWidgetTab::closeRequested, this, [this, dockWidgetTab] { + onTabCloseRequested(dockWidgetTab); + }); + connect(dockWidgetTab, &DockWidgetTab::closeOtherTabsRequested, this, [this, dockWidgetTab] { + onCloseOtherTabsRequested(dockWidgetTab); + }); + connect(dockWidgetTab, + &DockWidgetTab::moved, + this, + [this, dockWidgetTab](const QPoint &globalPosition) { + onTabWidgetMoved(dockWidgetTab, globalPosition); + }); + connect(dockWidgetTab, &DockWidgetTab::elidedChanged, this, &DockAreaTabBar::elidedChanged); + dockWidgetTab->installEventFilter(this); + emit tabInserted(index); + if (index <= d->m_currentIndex) + setCurrentIndex(d->m_currentIndex + 1); + else if (d->m_currentIndex == -1) + setCurrentIndex(index); + + updateGeometry(); +} + +void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab) +{ + if (!count()) + return; + + qCInfo(adsLog) << Q_FUNC_INFO; + int newCurrentIndex = currentIndex(); + int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab); + if (count() == 1) + newCurrentIndex = -1; + + if (newCurrentIndex > removeIndex) { + newCurrentIndex--; + } else if (newCurrentIndex == removeIndex) { + newCurrentIndex = -1; + // First we walk to the right to search for the next visible tab + for (int i = (removeIndex + 1); i < count(); ++i) { + if (tab(i)->isVisibleTo(this)) { + newCurrentIndex = i - 1; + break; } } - } - DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) - : QScrollArea(parent) - , d(new DockAreaTabBarPrivate(this)) - { - d->m_dockArea = parent; - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - setFrameStyle(QFrame::NoFrame); - setWidgetResizable(true); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - d->m_tabsContainerWidget = new QWidget(); - d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - d->m_tabsContainerWidget->setObjectName("tabsContainerWidget"); - d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); - d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); - d->m_tabsLayout->setSpacing(0); - d->m_tabsLayout->addStretch(1); - d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); - setWidget(d->m_tabsContainerWidget); - } - - DockAreaTabBar::~DockAreaTabBar() { delete d; } - - void DockAreaTabBar::wheelEvent(QWheelEvent *event) - { - event->accept(); - const int direction = event->angleDelta().y(); - if (direction < 0) - horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); - else - horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); - } - - void DockAreaTabBar::setCurrentIndex(int index) - { - if (index == d->m_currentIndex) - return; - - if (index < -1 || index > (count() - 1)) { - qWarning() << Q_FUNC_INFO << "Invalid index" << index; - return; - } - - emit currentChanging(index); - d->m_currentIndex = index; - d->updateTabs(); - updateGeometry(); - emit currentChanged(index); - } - - int DockAreaTabBar::count() const - { - // The tab bar contains a stretch item as last item - return d->m_tabsLayout->count() - 1; - } - - void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab) - { - d->m_tabsLayout->insertWidget(index, dockWidgetTab); - connect(dockWidgetTab, &DockWidgetTab::clicked, - this, [this, dockWidgetTab] { onTabClicked(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::closeRequested, - this, [this, dockWidgetTab] { onTabCloseRequested(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::closeOtherTabsRequested, - this, [this, dockWidgetTab] { onCloseOtherTabsRequested(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::moved, - this, [this, dockWidgetTab](const QPoint &globalPosition) { - onTabWidgetMoved(dockWidgetTab, globalPosition); - }); - connect(dockWidgetTab, &DockWidgetTab::elidedChanged, - this, &DockAreaTabBar::elidedChanged); - dockWidgetTab->installEventFilter(this); - emit tabInserted(index); - if (index <= d->m_currentIndex) - setCurrentIndex(d->m_currentIndex + 1); - else if (d->m_currentIndex == -1) - setCurrentIndex(index); - - updateGeometry(); - } - - void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab) - { - if (!count()) - return; - - qCInfo(adsLog) << Q_FUNC_INFO; - int newCurrentIndex = currentIndex(); - int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab); - if (count() == 1) - newCurrentIndex = -1; - - if (newCurrentIndex > removeIndex) { - newCurrentIndex--; - } else if (newCurrentIndex == removeIndex) { - newCurrentIndex = -1; - // First we walk to the right to search for the next visible tab - for (int i = (removeIndex + 1); i < count(); ++i) { + // If there is no visible tab right to this tab then we walk to the left to find a visible tab. + if (newCurrentIndex < 0) { + for (int i = (removeIndex - 1); i >= 0; --i) { if (tab(i)->isVisibleTo(this)) { - newCurrentIndex = i - 1; + newCurrentIndex = i; break; } } - - // If there is no visible tab right to this tab then we walk to - // the left to find a visible tab - if (newCurrentIndex < 0) { - for (int i = (removeIndex - 1); i >= 0; --i) { - if (tab(i)->isVisibleTo(this)) { - newCurrentIndex = i; - break; - } - } - } - } - - emit removingTab(removeIndex); - d->m_tabsLayout->removeWidget(dockWidgetTab); - dockWidgetTab->disconnect(this); - dockWidgetTab->removeEventFilter(this); - qCInfo(adsLog) << "NewCurrentIndex " << newCurrentIndex; - if (newCurrentIndex != d->m_currentIndex) - setCurrentIndex(newCurrentIndex); - else - d->updateTabs(); - - updateGeometry(); - } - - int DockAreaTabBar::currentIndex() const { return d->m_currentIndex; } - - DockWidgetTab *DockAreaTabBar::currentTab() const - { - if (d->m_currentIndex < 0) - return nullptr; - else - return qobject_cast( - d->m_tabsLayout->itemAt(d->m_currentIndex)->widget()); - } - - void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab) - { - const int index = d->m_tabsLayout->indexOf(sourceTab); - if (index < 0) - return; - - setCurrentIndex(index); - emit tabBarClicked(index); - } - - void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab) - { - const int index = d->m_tabsLayout->indexOf(sourceTab); - closeTab(index); - } - - void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab) - { - for (int i = 0; i < count(); ++i) { - auto currentTab = tab(i); - if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) { - // If the dock widget is deleted with the closeTab() call, its tab it will no longer - // be in the layout, and thus the index needs to be updated to not skip any tabs - int offset = currentTab->dockWidget()->features().testFlag( - DockWidget::DockWidgetDeleteOnClose) - ? 1 - : 0; - closeTab(i); - // If the the dock widget blocks closing, i.e. if the flag - // CustomCloseHandling is set, and the dock widget is still open, - // then we do not need to correct the index - if (currentTab->dockWidget()->isClosed()) - i -= offset; - } } } - DockWidgetTab *DockAreaTabBar::tab(int index) const - { - if (index >= count() || index < 0) - return nullptr; + emit removingTab(removeIndex); + d->m_tabsLayout->removeWidget(dockWidgetTab); + dockWidgetTab->disconnect(this); + dockWidgetTab->removeEventFilter(this); + qCInfo(adsLog) << "NewCurrentIndex" << newCurrentIndex; + if (newCurrentIndex != d->m_currentIndex) + setCurrentIndex(newCurrentIndex); + else + d->updateTabs(); - return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); - } + updateGeometry(); +} - void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition) - { - const int fromIndex = d->m_tabsLayout->indexOf(sourceTab); - auto mousePos = mapFromGlobal(globalPosition); - mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x()); - mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x()); - int toIndex = -1; - // Find tab under mouse - for (int i = 0; i < count(); ++i) { - DockWidgetTab *dropTab = tab(i); - if (dropTab == sourceTab || !dropTab->isVisibleTo(this) - || !dropTab->geometry().contains(mousePos)) - continue; +int DockAreaTabBar::currentIndex() const +{ + return d->m_currentIndex; +} - toIndex = d->m_tabsLayout->indexOf(dropTab); - if (toIndex == fromIndex) - toIndex = -1; +DockWidgetTab *DockAreaTabBar::currentTab() const +{ + if (d->m_currentIndex < 0) + return nullptr; + else + return qobject_cast(d->m_tabsLayout->itemAt(d->m_currentIndex)->widget()); +} - break; - } +void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab) +{ + const int index = d->m_tabsLayout->indexOf(sourceTab); + if (index < 0) + return; - if (toIndex > -1) { - d->m_tabsLayout->removeWidget(sourceTab); - d->m_tabsLayout->insertWidget(toIndex, sourceTab); - qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex; - emit tabMoved(fromIndex, toIndex); - setCurrentIndex(toIndex); - } else { - // Ensure that the moved tab is reset to its start position - d->m_tabsLayout->update(); + setCurrentIndex(index); + emit tabBarClicked(index); +} + +void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab) +{ + const int index = d->m_tabsLayout->indexOf(sourceTab); + closeTab(index); +} + +void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab) +{ + for (int i = 0; i < count(); ++i) { + auto currentTab = tab(i); + if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) { + // If the dock widget is deleted with the closeTab() call, its tab it will no longer + // be in the layout, and thus the index needs to be updated to not skip any tabs. + int offset = currentTab->dockWidget()->features().testFlag( + DockWidget::DockWidgetDeleteOnClose) + ? 1 + : 0; + closeTab(i); + // If the the dock widget blocks closing, i.e. if the flag CustomCloseHandling is set, + // and the dock widget is still open, then we do not need to correct the index. + if (currentTab->dockWidget()->isClosed()) + i -= offset; } } +} - void DockAreaTabBar::closeTab(int index) - { - if (index < 0 || index >= count()) - return; +DockWidgetTab *DockAreaTabBar::tab(int index) const +{ + if (index >= count() || index < 0) + return nullptr; - auto dockWidgetTab = tab(index); - if (dockWidgetTab->isHidden()) - return; + return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); +} - emit tabCloseRequested(index); +void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition) +{ + const int fromIndex = d->m_tabsLayout->indexOf(sourceTab); + auto mousePos = mapFromGlobal(globalPosition); + mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x()); + mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x()); + int toIndex = -1; + // Find tab under mouse + for (int i = 0; i < count(); ++i) { + DockWidgetTab *dropTab = tab(i); + if (dropTab == sourceTab || !dropTab->isVisibleTo(this) + || !dropTab->geometry().contains(mousePos)) + continue; + + toIndex = d->m_tabsLayout->indexOf(dropTab); + if (toIndex == fromIndex) + toIndex = -1; + + break; } - bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event) - { - bool result = Super::eventFilter(watched, event); - DockWidgetTab *dockWidgetTab = qobject_cast(watched); - if (!dockWidgetTab) - return result; + if (toIndex > -1) { + d->m_tabsLayout->removeWidget(sourceTab); + d->m_tabsLayout->insertWidget(toIndex, sourceTab); + qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex; + emit tabMoved(fromIndex, toIndex); + setCurrentIndex(toIndex); + } else { + // Ensure that the moved tab is reset to its start position + d->m_tabsLayout->update(); + } +} - switch (event->type()) { - case QEvent::Hide: - emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab)); - updateGeometry(); - break; - case QEvent::Show: - emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab)); - updateGeometry(); - break; - default: - break; - } +void DockAreaTabBar::closeTab(int index) +{ + if (index < 0 || index >= count()) + return; + auto dockWidgetTab = tab(index); + if (dockWidgetTab->isHidden()) + return; + + emit tabCloseRequested(index); +} + +bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event) +{ + bool result = Super::eventFilter(watched, event); + DockWidgetTab *dockWidgetTab = qobject_cast(watched); + if (!dockWidgetTab) return result; + + switch (event->type()) { + case QEvent::Hide: + emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + case QEvent::Show: + emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + // Setting the text of a tab will cause a LayoutRequest event + case QEvent::LayoutRequest: + updateGeometry(); + break; + default: + break; } - bool DockAreaTabBar::isTabOpen(int index) const - { - if (index < 0 || index >= count()) - return false; + return result; +} - return !tab(index)->isHidden(); - } +bool DockAreaTabBar::isTabOpen(int index) const +{ + if (index < 0 || index >= count()) + return false; - QSize DockAreaTabBar::minimumSizeHint() const - { - QSize size = sizeHint(); - size.setWidth(10); - return size; - } + return !tab(index)->isHidden(); +} - QSize DockAreaTabBar::sizeHint() const - { - return d->m_tabsContainerWidget->sizeHint(); - } +QSize DockAreaTabBar::minimumSizeHint() const +{ + QSize size = sizeHint(); + size.setWidth(10); + return size; +} + +QSize DockAreaTabBar::sizeHint() const +{ + return d->m_tabsContainerWidget->sizeHint(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatabbar.h b/src/libs/advanceddockingsystem/dockareatabbar.h index 956300e07f0..7cde0192634 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.h +++ b/src/libs/advanceddockingsystem/dockareatabbar.h @@ -101,7 +101,7 @@ public: /** * Overrides the minimumSizeHint() function of QScrollArea * The minimumSizeHint() is bigger than the sizeHint () for the scroll - * area because even if the scrollbars are invisible, the required speace + * area because even if the scrollbars are invisible, the required space * is reserved in the minimumSizeHint(). This override simply returns * sizeHint(); */ diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index 02c83489fa7..273984855c1 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -15,10 +15,14 @@ #include "dockwidgettab.h" #include "floatingdockcontainer.h" #include "floatingdragpreview.h" -#include "iconprovider.h" + +#include "autohidedockcontainer.h" +#include "dockfocuscontroller.h" +#include "elidinglabel.h" #include +#include #include #include #include @@ -29,532 +33,704 @@ #include -namespace ADS +namespace ADS { + +static const char *const g_locationProperty = "Location"; + +/** + * Private data class of DockAreaTitleBar class (pimpl) + */ +class DockAreaTitleBarPrivate { +public: + DockAreaTitleBar *q; + QPointer m_tabsMenuButton; + QPointer m_autoHideButton; + QPointer m_undockButton; + QPointer m_closeButton; + QBoxLayout *m_layout = nullptr; + DockAreaWidget *m_dockArea = nullptr; + DockAreaTabBar *m_tabBar = nullptr; + ElidingLabel *m_autoHideTitleLabel; + bool m_menuOutdated = true; + QMenu *m_tabsMenu; + QList m_dockWidgetActionsButtons; + + QPoint m_dragStartMousePos; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + /** - * Private data class of DockAreaTitleBar class (pimpl) + * Private data constructor */ - class DockAreaTitleBarPrivate + DockAreaTitleBarPrivate(DockAreaTitleBar *parent); + + /** + * Creates the title bar close and menu buttons + */ + void createButtons(); + + /** + * Creates the auto hide title label, only displayed when the dock area is overlayed + */ + void createAutoHideTitleLabel(); + + /** + * Creates the internal TabBar + */ + void createTabBar(); + + /** + * Convenience function for DockManager access + */ + DockManager *dockManager() const { return m_dockArea->dockManager(); } + + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) { - public: - DockAreaTitleBar *q; - QPointer m_tabsMenuButton; - QPointer m_undockButton; - QPointer m_closeButton; - QBoxLayout *m_layout = nullptr; - DockAreaWidget *m_dockArea = nullptr; - DockAreaTabBar *m_tabBar = nullptr; - bool m_menuOutdated = true; - QList m_dockWidgetActionsButtons; + return DockManager::testConfigFlag(flag); + } - QPoint m_dragStartMousePos; - eDragState m_dragState = DraggingInactive; - AbstractFloatingWidget *m_floatingWidget = nullptr; - - /** - * Private data constructor - */ - DockAreaTitleBarPrivate(DockAreaTitleBar *parent); - - /** - * Creates the title bar close and menu buttons - */ - void createButtons(); - - /** - * Creates the internal TabBar - */ - void createTabBar(); - - /** - * Convenience function for DockManager access - */ - DockManager *dockManager() const { return m_dockArea->dockManager(); } - - /** - * Returns true if the given config flag is set - * Convenience function to ease config flag testing - */ - static bool testConfigFlag(DockManager::eConfigFlag flag) - { - return DockManager::testConfigFlag(flag); - } - - /** - * Test function for current drag state - */ - bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } - - - /** - * Starts floating - */ - void startFloating(const QPoint &offset); - - /** - * Makes the dock area floating - */ - AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState); - }; // class DockAreaTitleBarPrivate - - DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent) - : q(parent) - {} - - void DockAreaTitleBarPrivate::createButtons() + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testAutoHideConfigFlag(DockManager::eAutoHideFlag flag) { - const QSize iconSize(11, 11); - const QSize buttonSize(17, 17); - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + return DockManager::testAutoHideConfigFlag(flag); + } - // Tabs menu button - m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton)); - m_tabsMenuButton->setObjectName("tabsMenuButton"); - //m_tabsMenuButton->setAutoRaise(true); - m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup); - internal::setButtonIcon(m_tabsMenuButton, - QStyle::SP_TitleBarUnshadeButton, - ADS::DockAreaMenuIcon); - QMenu *tabsMenu = new QMenu(m_tabsMenuButton); + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + /** + * Starts floating + */ + void startFloating(const QPoint &offset); + + /** + * Makes the dock area floating + */ + AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState); + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction *createAutoHideToAction(const QString &title, SideBarLocation location, QMenu *menu) + { + auto action = menu->addAction(title); + action->setProperty("Location", location); + QObject::connect(action, + &QAction::triggered, + q, + &DockAreaTitleBar::onAutoHideToActionClicked); + return action; + } +}; // class DockAreaTitleBarPrivate + +DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent) + : q(parent) +{} + +void DockAreaTitleBarPrivate::createButtons() +{ + const QSize iconSize(11, 11); + const QSize buttonSize(17, 17); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + // Tabs menu button + m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton)); + m_tabsMenuButton->setObjectName("tabsMenuButton"); + //m_tabsMenuButton->setAutoRaise(true); + m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup); + internal::setButtonIcon(m_tabsMenuButton, + QStyle::SP_TitleBarUnshadeButton, + ADS::DockAreaMenuIcon); + QMenu *tabsMenu = new QMenu(m_tabsMenuButton); #ifndef QT_NO_TOOLTIP - tabsMenu->setToolTipsVisible(true); + tabsMenu->setToolTipsVisible(true); #endif - QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow); - m_tabsMenuButton->setMenu(tabsMenu); - internal::setToolTip(m_tabsMenuButton, Tr::tr("List All Tabs")); - m_tabsMenuButton->setSizePolicy(sizePolicy); - m_tabsMenuButton->setIconSize(iconSize); - m_tabsMenuButton->setFixedSize(buttonSize); - m_layout->addWidget(m_tabsMenuButton, 0); - QObject::connect(m_tabsMenuButton->menu(), - &QMenu::triggered, - q, - &DockAreaTitleBar::onTabsMenuActionTriggered); + QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow); + m_tabsMenuButton->setMenu(tabsMenu); + internal::setToolTip(m_tabsMenuButton, Tr::tr("List All Tabs")); + m_tabsMenuButton->setSizePolicy(sizePolicy); + m_tabsMenuButton->setIconSize(iconSize); + m_tabsMenuButton->setFixedSize(buttonSize); + m_layout->addWidget(m_tabsMenuButton, 0); + QObject::connect(m_tabsMenuButton->menu(), + &QMenu::triggered, + q, + &DockAreaTitleBar::onTabsMenuActionTriggered); - // Undock button - m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton)); - m_undockButton->setObjectName("detachGroupButton"); - //m_undockButton->setAutoRaise(true); - internal::setToolTip(m_undockButton, Tr::tr("Detach Group")); - internal::setButtonIcon(m_undockButton, - QStyle::SP_TitleBarNormalButton, - ADS::DockAreaUndockIcon); - m_undockButton->setSizePolicy(sizePolicy); - m_undockButton->setIconSize(iconSize); - m_undockButton->setFixedSize(buttonSize); - m_layout->addWidget(m_undockButton, 0); - QObject::connect(m_undockButton, - &QToolButton::clicked, - q, - &DockAreaTitleBar::onUndockButtonClicked); + // Undock button + m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton)); + m_undockButton->setObjectName("detachGroupButton"); + //m_undockButton->setAutoRaise(true); + internal::setToolTip(m_undockButton, Tr::tr("Detach Group")); + internal::setButtonIcon(m_undockButton, + QStyle::SP_TitleBarNormalButton, + ADS::DockAreaUndockIcon); + m_undockButton->setSizePolicy(sizePolicy); + m_undockButton->setIconSize(iconSize); + m_undockButton->setFixedSize(buttonSize); + m_layout->addWidget(m_undockButton, 0); + QObject::connect(m_undockButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onUndockButtonClicked); - // Close button - m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); - m_closeButton->setObjectName("dockAreaCloseButton"); - //m_closeButton->setAutoRaise(true); - internal::setButtonIcon(m_closeButton, - QStyle::SP_TitleBarCloseButton, - ADS::DockAreaCloseIcon); - if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) - internal::setToolTip(m_closeButton, Tr::tr("Close Active Tab")); - else - internal::setToolTip(m_closeButton, Tr::tr("Close Group")); + // AutoHide Button + const auto autoHideEnabled = testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled); + m_autoHideButton = new TitleBarButton( + testAutoHideConfigFlag(DockManager::DockAreaHasAutoHideButton) && autoHideEnabled); + m_autoHideButton->setObjectName("dockAreaAutoHideButton"); + //m_autoHideButton->setAutoRaise(true); + internal::setToolTip(m_autoHideButton, q->titleBarButtonToolTip(TitleBarButtonAutoHide)); + internal::setButtonIcon(m_autoHideButton, QStyle::SP_DialogOkButton, ADS::AutoHideIcon); + m_autoHideButton->setSizePolicy(sizePolicy); + m_autoHideButton->setCheckable(testAutoHideConfigFlag(DockManager::AutoHideButtonCheckable)); + m_autoHideButton->setChecked(false); + m_layout->addWidget(m_autoHideButton, 0); + QObject::connect(m_autoHideButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onAutoHideButtonClicked); - m_closeButton->setSizePolicy(sizePolicy); - m_closeButton->setIconSize(iconSize); - m_closeButton->setFixedSize(buttonSize); - m_layout->addWidget(m_closeButton, 0); - QObject::connect(m_closeButton, - &QToolButton::clicked, - q, - &DockAreaTitleBar::onCloseButtonClicked); + // Close button + m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); + m_closeButton->setObjectName("dockAreaCloseButton"); + //m_closeButton->setAutoRaise(true); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, ADS::DockAreaCloseIcon); + if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + internal::setToolTip(m_closeButton, Tr::tr("Close Active Tab")); + else + internal::setToolTip(m_closeButton, Tr::tr("Close Group")); - m_layout->addSpacing(1); + m_closeButton->setSizePolicy(sizePolicy); + m_closeButton->setIconSize(iconSize); + m_closeButton->setFixedSize(buttonSize); + m_layout->addWidget(m_closeButton, 0); + QObject::connect(m_closeButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onCloseButtonClicked); + + m_layout->addSpacing(1); +} + +void DockAreaTitleBarPrivate::createAutoHideTitleLabel() +{ + m_autoHideTitleLabel = new ElidingLabel(""); + m_autoHideTitleLabel->setObjectName("autoHideTitleLabel"); + m_layout->addWidget(m_autoHideTitleLabel); +} +void DockAreaTitleBarPrivate::createTabBar() +{ + m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea); + m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + m_layout->addWidget(m_tabBar); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabClosed, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabOpened, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabInserted, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::removingTab, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabMoved, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::currentChanged, + q, + &DockAreaTitleBar::onCurrentTabChanged); + QObject::connect(m_tabBar, &DockAreaTabBar::tabBarClicked, q, &DockAreaTitleBar::tabBarClicked); + QObject::connect(m_tabBar, + &DockAreaTabBar::elidedChanged, + q, + &DockAreaTitleBar::markTabsMenuOutdated); +} + +AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset, + eDragState dragState) +{ + QSize size = m_dockArea->size(); + m_dragState = dragState; + bool createFloatingDockContainer = (DraggingFloatingWidget != dragState); + FloatingDockContainer *floatingDockContainer = nullptr; + AbstractFloatingWidget *floatingWidget; + if (createFloatingDockContainer) { + if (m_dockArea->autoHideDockContainer()) + m_dockArea->autoHideDockContainer()->cleanupAndDelete(); + floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); + } else { + auto w = new FloatingDragPreview(m_dockArea); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [this] { + m_dragState = DraggingInactive; + }); + floatingWidget = w; } - void DockAreaTitleBarPrivate::createTabBar() - { - m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea); - m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); - m_layout->addWidget(m_tabBar); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabClosed, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabOpened, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabInserted, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::removingTab, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabMoved, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::currentChanged, - q, - &DockAreaTitleBar::onCurrentTabChanged); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabBarClicked, - q, - &DockAreaTitleBar::tabBarClicked); - QObject::connect(m_tabBar, - &DockAreaTabBar::elidedChanged, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - } - - AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset, - eDragState dragState) - { - QSize size = m_dockArea->size(); - m_dragState = dragState; - bool opaqueUndocking = DockManager::testConfigFlag(DockManager::OpaqueUndocking) - || (DraggingFloatingWidget != dragState); - FloatingDockContainer *floatingDockContainer = nullptr; - AbstractFloatingWidget *floatingWidget; - if (opaqueUndocking) { - floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); - } else { - auto w = new FloatingDragPreview(m_dockArea); - QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [this] { - m_dragState = DraggingInactive; - }); - floatingWidget = w; + floatingWidget->startFloating(offset, size, dragState, nullptr); + if (floatingDockContainer) { + auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget(); + if (topLevelDockWidget) { + topLevelDockWidget->emitTopLevelChanged(true); } - - floatingWidget->startFloating(offset, size, dragState, nullptr); - if (floatingDockContainer) { - auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget(); - if (topLevelDockWidget) { - topLevelDockWidget->emitTopLevelChanged(true); - } - } - - return floatingWidget; } - void DockAreaTitleBarPrivate::startFloating(const QPoint &offset) - { - m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget); + return floatingWidget; +} + +void DockAreaTitleBarPrivate::startFloating(const QPoint &offset) +{ + if (m_dockArea->autoHideDockContainer()) + m_dockArea->autoHideDockContainer()->hide(); + + m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget); + qApp->postEvent(m_dockArea, new QEvent((QEvent::Type) internal::g_dockedWidgetDragStartEvent)); +} + +TitleBarButton::TitleBarButton(bool visible, QWidget *parent) + : TitleBarButtonType(parent) + , m_visible(visible) + , m_hideWhenDisabled( + DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons)) +{ + setFocusPolicy(Qt::NoFocus); +} + +void TitleBarButton::setVisible(bool visible) +{ + // 'visible' can stay 'true' if and only if this button is configured to generaly visible: + visible = visible && m_visible; + + // 'visible' can stay 'true' unless: this button is configured to be invisible when it + // is disabled and it is currently disabled: + if (visible && m_hideWhenDisabled) + visible = isEnabled(); + + Super::setVisible(visible); +} + +bool TitleBarButton::event(QEvent *event) +{ + if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) { + // force setVisible() call + // Calling setVisible() directly here doesn't work well when button is expected to be shown first time + const bool visible = isEnabled(); + QMetaObject::invokeMethod( + this, [this, visible] { setVisible(visible); }, Qt::QueuedConnection); } - TitleBarButton::TitleBarButton(bool visible, QWidget *parent) - : TitleBarButtonType(parent) - , m_visible(visible) - , m_hideWhenDisabled(DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons)) - {} + return Super::event(event); +} - void TitleBarButton::setVisible(bool visible) - { - // 'visible' can stay 'true' if and only if this button is configured to generaly visible: - visible = visible && m_visible; +SpacerWidget::SpacerWidget(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setStyleSheet("border: none; background: none;"); +} - // 'visible' can stay 'true' unless: this button is configured to be invisible when it - // is disabled and it is currently disabled: - if (visible && m_hideWhenDisabled) - visible = isEnabled(); +DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) + : QFrame(parent) + , d(new DockAreaTitleBarPrivate(this)) +{ + d->m_dockArea = parent; - Super::setVisible(visible); - } + setObjectName("dockAreaTitleBar"); + d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - bool TitleBarButton::event(QEvent *event) - { - if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) { - // force setVisible() call - // Calling setVisible() directly here doesn't work well when button is expected to be shown first time - const bool visible = isEnabled(); - QMetaObject::invokeMethod(this, [this, visible] { setVisible(visible); }, Qt::QueuedConnection); - } + d->createTabBar(); + d->createAutoHideTitleLabel(); + d->m_autoHideTitleLabel->setVisible(false); // Default hidden + d->m_layout->addWidget(new SpacerWidget(this)); + d->createButtons(); + setFocusPolicy(Qt::NoFocus); +} - return Super::event(event); - } +DockAreaTitleBar::~DockAreaTitleBar() +{ + if (!d->m_closeButton.isNull()) + delete d->m_closeButton; - SpacerWidget::SpacerWidget(QWidget *parent) - : QWidget(parent) - { - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - setStyleSheet("border: none; background: none;"); - } + if (!d->m_tabsMenuButton.isNull()) + delete d->m_tabsMenuButton; - DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) - : QFrame(parent) - , d(new DockAreaTitleBarPrivate(this)) - { - d->m_dockArea = parent; + if (!d->m_undockButton.isNull()) + delete d->m_undockButton; - setObjectName("dockAreaTitleBar"); - d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight); - d->m_layout->setContentsMargins(0, 0, 0, 0); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + delete d; +} - d->createTabBar(); - d->m_layout->addWidget(new SpacerWidget(this)); - d->createButtons(); - } +DockAreaTabBar *DockAreaTitleBar::tabBar() const +{ + return d->m_tabBar; +} - DockAreaTitleBar::~DockAreaTitleBar() { - if (!d->m_closeButton.isNull()) - delete d->m_closeButton; - - if (!d->m_tabsMenuButton.isNull()) - delete d->m_tabsMenuButton; - - if (!d->m_undockButton.isNull()) - delete d->m_undockButton; - - delete d; - } - - DockAreaTabBar *DockAreaTitleBar::tabBar() const { return d->m_tabBar; } - - void DockAreaTitleBar::markTabsMenuOutdated() { - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaDynamicTabsMenuButtonVisibility)) { - bool hasElidedTabTitle = false; - for (int i = 0; i < d->m_tabBar->count(); ++i) { - if (!d->m_tabBar->isTabOpen(i)) - continue; - - DockWidgetTab* tab = d->m_tabBar->tab(i); - if (tab->isTitleElided()) { - hasElidedTabTitle = true; - break; - } - } - const bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1)); - QMetaObject::invokeMethod(this, [this, visible] { - d->m_tabsMenuButton->setVisible(visible); }, Qt::QueuedConnection); - } - d->m_menuOutdated = true; - } - - void DockAreaTitleBar::onTabsMenuAboutToShow() - { - if (!d->m_menuOutdated) - return; - - QMenu *menu = d->m_tabsMenuButton->menu(); - menu->clear(); +void DockAreaTitleBar::markTabsMenuOutdated() +{ + if (DockAreaTitleBarPrivate::testConfigFlag( + DockManager::DockAreaDynamicTabsMenuButtonVisibility)) { + bool hasElidedTabTitle = false; for (int i = 0; i < d->m_tabBar->count(); ++i) { if (!d->m_tabBar->isTabOpen(i)) continue; - auto tab = d->m_tabBar->tab(i); - QAction *action = menu->addAction(tab->icon(), tab->text()); - internal::setToolTip(action, tab->toolTip()); - action->setData(i); - } - - d->m_menuOutdated = false; - } - - void DockAreaTitleBar::onCloseButtonClicked() - { - qCInfo(adsLog) << Q_FUNC_INFO; - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) - d->m_tabBar->closeTab(d->m_tabBar->currentIndex()); - else - d->m_dockArea->closeArea(); - } - - void DockAreaTitleBar::onUndockButtonClicked() - { - if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); - } - - void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action) - { - int index = action->data().toInt(); - d->m_tabBar->setCurrentIndex(index); - emit tabBarClicked(index); - } - - void DockAreaTitleBar::updateDockWidgetActionsButtons() - { - QTC_ASSERT(d->m_tabBar->currentTab(), return ); - QTC_ASSERT(d->m_tabBar->currentTab()->dockWidget(), return ); - DockWidget* dockWidget = d->m_tabBar->currentTab()->dockWidget(); - if (!d->m_dockWidgetActionsButtons.isEmpty()) { - for (auto button : std::as_const(d->m_dockWidgetActionsButtons)) { - d->m_layout->removeWidget(button); - delete button; + DockWidgetTab *tab = d->m_tabBar->tab(i); + if (tab->isTitleElided()) { + hasElidedTabTitle = true; + break; } - d->m_dockWidgetActionsButtons.clear(); } + const bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1)); + QMetaObject::invokeMethod( + this, + [this, visible] { d->m_tabsMenuButton->setVisible(visible); }, + Qt::QueuedConnection); + } + d->m_menuOutdated = true; +} - auto actions = dockWidget->titleBarActions(); - if (actions.isEmpty()) - return; +void DockAreaTitleBar::onTabsMenuAboutToShow() +{ + if (!d->m_menuOutdated) + return; - int insertIndex = indexOf(d->m_tabsMenuButton); - for (auto action : actions) { - auto button = new TitleBarButton(true, this); - button->setDefaultAction(action); - button->setAutoRaise(true); - button->setPopupMode(QToolButton::InstantPopup); - button->setObjectName(action->objectName()); - d->m_layout->insertWidget(insertIndex++, button, 0); - d->m_dockWidgetActionsButtons.append(button); - } + QMenu *menu = d->m_tabsMenuButton->menu(); + menu->clear(); + for (int i = 0; i < d->m_tabBar->count(); ++i) { + if (!d->m_tabBar->isTabOpen(i)) + continue; + + auto tab = d->m_tabBar->tab(i); + QAction *action = menu->addAction(tab->icon(), tab->text()); + internal::setToolTip(action, tab->toolTip()); + action->setData(i); } - void DockAreaTitleBar::onCurrentTabChanged(int index) - { - if (index < 0) - return; + d->m_menuOutdated = false; +} - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { - DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget(); - d->m_closeButton->setEnabled( - dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); +void DockAreaTitleBar::onCloseButtonClicked() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideCloseButtonCollapsesDock) + && d->m_dockArea->autoHideDockContainer()) + d->m_dockArea->autoHideDockContainer()->collapseView(true); + else if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + d->m_tabBar->closeTab(d->m_tabBar->currentIndex()); + else + d->m_dockArea->closeArea(); +} + +void DockAreaTitleBar::onUndockButtonClicked() +{ + if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); +} + +void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action) +{ + int index = action->data().toInt(); + d->m_tabBar->setCurrentIndex(index); + emit tabBarClicked(index); +} + +void DockAreaTitleBar::updateDockWidgetActionsButtons() +{ + auto tab = d->m_tabBar->currentTab(); + if (!tab) + return; + + QTC_ASSERT(tab, return); + QTC_ASSERT(tab->dockWidget(), return); + DockWidget *dockWidget = tab->dockWidget(); + if (!d->m_dockWidgetActionsButtons.isEmpty()) { + for (auto button : std::as_const(d->m_dockWidgetActionsButtons)) { + d->m_layout->removeWidget(button); + delete button; } - - updateDockWidgetActionsButtons(); + d->m_dockWidgetActionsButtons.clear(); } - QAbstractButton *DockAreaTitleBar::button(eTitleBarButton which) const - { - switch (which) { - case TitleBarButtonTabsMenu: - return d->m_tabsMenuButton; - case TitleBarButtonUndock: - return d->m_undockButton; - case TitleBarButtonClose: - return d->m_closeButton; - } - return nullptr; + auto actions = dockWidget->titleBarActions(); + if (actions.isEmpty()) + return; + + int insertIndex = indexOf(d->m_tabsMenuButton); + for (auto action : actions) { + auto button = new TitleBarButton(true, this); + button->setDefaultAction(action); + button->setAutoRaise(true); + button->setPopupMode(QToolButton::InstantPopup); + button->setObjectName(action->objectName()); + d->m_layout->insertWidget(insertIndex++, button, 0); + d->m_dockWidgetActionsButtons.append(button); + } +} + +void DockAreaTitleBar::onCurrentTabChanged(int index) +{ + if (index < 0) + return; + + if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { + DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget(); + d->m_closeButton->setEnabled( + dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); } - void DockAreaTitleBar::setVisible(bool visible) - { - Super::setVisible(visible); - markTabsMenuOutdated(); + updateDockWidgetActionsButtons(); +} + +void DockAreaTitleBar::onAutoHideButtonClicked() +{ + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideButtonTogglesArea) + || qApp->keyboardModifiers().testFlag(Qt::ControlModifier)) + d->m_dockArea->toggleAutoHide(); + else + d->m_dockArea->currentDockWidget()->toggleAutoHide(); +} + +void DockAreaTitleBar::onAutoHideDockAreaActionClicked() +{ + d->m_dockArea->toggleAutoHide(); +} + +void DockAreaTitleBar::onAutoHideToActionClicked() +{ + int location = sender()->property(g_locationProperty).toInt(); + d->m_dockArea->toggleAutoHide((SideBarLocation) location); +} + +QAbstractButton *DockAreaTitleBar::button(eTitleBarButton which) const +{ + switch (which) { + case TitleBarButtonTabsMenu: + return d->m_tabsMenuButton; + case TitleBarButtonUndock: + return d->m_undockButton; + case TitleBarButtonClose: + return d->m_closeButton; + case TitleBarButtonAutoHide: + return d->m_autoHideButton; } + return nullptr; +} +ElidingLabel *DockAreaTitleBar::autoHideTitleLabel() const +{ + return d->m_autoHideTitleLabel; +} - void DockAreaTitleBar::mousePressEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - event->accept(); - d->m_dragStartMousePos = event->pos(); - d->m_dragState = DraggingMousePressed; - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - d->m_tabBar->currentTab()->setFocus(Qt::OtherFocusReason); +void DockAreaTitleBar::setVisible(bool visible) +{ + Super::setVisible(visible); + markTabsMenuOutdated(); +} - return; - } - Super::mousePressEvent(event); - } - - void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - qCInfo(adsLog) << Q_FUNC_INFO; - event->accept(); - auto CurrentDragState = d->m_dragState; - d->m_dragStartMousePos = QPoint(); - d->m_dragState = DraggingInactive; - if (DraggingFloatingWidget == CurrentDragState) - d->m_floatingWidget->finishDragging(); - - return; - } - Super::mouseReleaseEvent(event); - } - - void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event) - { - Super::mouseMoveEvent(event); - if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { - d->m_dragState = DraggingInactive; - return; - } - - // move floating window - if (d->isDraggingState(DraggingFloatingWidget)) { - d->m_floatingWidget->moveFloating(); - return; - } - - // If this is the last dock area in a floating dock container it does not make - // sense to move it to a new floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { - return; - } - - // If one single dock widget in this area is not floatable then the whole - // area is not floatable - // If we do non opaque undocking, then we can create the floating drag - // preview if the dock widget is movable - auto features = d->m_dockArea->features(); - if (!features.testFlag(DockWidget::DockWidgetFloatable) - && !(features.testFlag(DockWidget::DockWidgetMovable) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { - return; - } - - int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength(); - if (dragDistance >= DockManager::startDragDistance()) { - qCInfo(adsLog) << "DockAreaTitlBar::startFloating"; - d->startFloating(d->m_dragStartMousePos); - auto overlay = d->m_dockArea->dockManager()->containerOverlay(); - overlay->setAllowedAreas(OuterDockAreas); - } +void DockAreaTitleBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + d->m_dragStartMousePos = event->pos(); + d->m_dragState = DraggingMousePressed; + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->dockManager()->dockFocusController()->setDockWidgetTabFocused( + d->m_tabBar->currentTab()); return; } + Super::mousePressEvent(event); +} - void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event) - { - // If this is the last dock area in a dock container it does not make - // sense to move it to a new floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->dockContainer()->dockAreaCount() == 1) - return; - - if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - return; - - d->makeAreaFloating(event->pos(), DraggingInactive); - } - - void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) - { +void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + qCInfo(adsLog) << Q_FUNC_INFO; event->accept(); - if (d->isDraggingState(DraggingFloatingWidget)) - return; + auto currentDragState = d->m_dragState; + d->m_dragStartMousePos = QPoint(); + d->m_dragState = DraggingInactive; + if (DraggingFloatingWidget == currentDragState) + d->m_floatingWidget->finishDragging(); - QMenu menu(this); - auto action = menu.addAction(Tr::tr("Detach Area"), - this, - &DockAreaTitleBar::onUndockButtonClicked); - action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)); + return; + } + Super::mouseReleaseEvent(event); +} + +void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event) +{ + Super::mouseMoveEvent(event); + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + return; + } + + // If this is the last dock area in a floating dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1 + && !d->m_dockArea->isAutoHide()) { + return; + } + + // If one single dock widget in this area is not floatable then the whole + // area is not floatable + // We can create the floating drag preview if the dock widget is movable + auto features = d->m_dockArea->features(); + if (!features.testFlag(DockWidget::DockWidgetFloatable) + && !(features.testFlag(DockWidget::DockWidgetMovable))) { + return; + } + + int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength(); + if (dragDistance >= DockManager::startDragDistance()) { + qCInfo(adsLog) << "DockAreaTitlBar::startFloating"; + d->startFloating(d->m_dragStartMousePos); + auto overlay = d->m_dockArea->dockManager()->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + } + + return; +} + +void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->dockAreaCount() == 1) + return; + + if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + return; + + d->makeAreaFloating(event->pos(), DraggingInactive); +} + +void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + return; + + const bool isAutoHide = d->m_dockArea->isAutoHide(); + const bool isTopLevelArea = d->m_dockArea->isTopLevelArea(); + + QMenu menu(this); + if (!isTopLevelArea) { + QAction *detachAction = menu.addAction(isAutoHide ? tr("Detach") : tr("Detach Group")); + detachAction->connect(detachAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onUndockButtonClicked); + detachAction->setEnabled( + d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + QAction *pinAction = menu.addAction(isAutoHide ? tr("Unpin (Dock)") : tr("Pin Group")); + pinAction->connect(pinAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onAutoHideDockAreaActionClicked); + + auto areaIsPinnable = d->m_dockArea->features().testFlag(DockWidget::DockWidgetPinnable); + pinAction->setEnabled(areaIsPinnable); + if (!isAutoHide) { + auto tmp = menu.addMenu(tr("Pin Group To...")); + tmp->setEnabled(areaIsPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, tmp); + d->createAutoHideToAction(tr("Left"), SideBarLeft, tmp); + d->createAutoHideToAction(tr("Right"), SideBarRight, tmp); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, tmp); + } + } menu.addSeparator(); - action = menu.addAction(Tr::tr("Close Area"), this, &DockAreaTitleBar::onCloseButtonClicked); - action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); - menu.addAction(Tr::tr("Close Other Areas"), d->m_dockArea, &DockAreaWidget::closeOtherAreas); - menu.exec(event->globalPos()); + } + QAction *closeAction = menu.addAction(isAutoHide ? tr("Close") : tr("Close Group")); + closeAction->connect(closeAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onCloseButtonClicked); + closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + + if (!isAutoHide && !isTopLevelArea) { + QAction *closeOthersAction = menu.addAction(tr("Close Other Groups")); + closeOthersAction->connect(closeOthersAction, + &QAction::triggered, + d->m_dockArea, + &DockAreaWidget::closeOtherAreas); } - void DockAreaTitleBar::insertWidget(int index, QWidget *widget) - { - d->m_layout->insertWidget(index, widget); - } + menu.exec(event->globalPos()); +} - int DockAreaTitleBar::indexOf(QWidget *widget) const - { - return d->m_layout->indexOf(widget); +void DockAreaTitleBar::insertWidget(int index, QWidget *widget) +{ + d->m_layout->insertWidget(index, widget); +} + +int DockAreaTitleBar::indexOf(QWidget *widget) const +{ + return d->m_layout->indexOf(widget); +} + +QString DockAreaTitleBar::titleBarButtonToolTip(eTitleBarButton button) const +{ + switch (button) { + case TitleBarButtonAutoHide: + if (d->m_dockArea->isAutoHide()) + return tr("Unpin (Dock)"); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideButtonTogglesArea)) + return tr("Pin Group"); + else + return tr("Pin Active Tab (Press Ctrl to Pin Group)"); + break; + + case TitleBarButtonClose: + if (d->m_dockArea->isAutoHide()) + return tr("Close"); + + if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + return tr("Close Active Tab"); + else + return tr("Close Group"); + break; + + default: + break; } + return QString(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.h b/src/libs/advanceddockingsystem/dockareatitlebar.h index 888e3736af7..c1c4603fd73 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.h +++ b/src/libs/advanceddockingsystem/dockareatitlebar.h @@ -17,6 +17,7 @@ namespace ADS { class DockAreaTabBar; class DockAreaWidget; class DockAreaTitleBarPrivate; +class ElidingLabel; using TitleBarButtonType = QToolButton; @@ -81,6 +82,9 @@ private: void onUndockButtonClicked(); void onTabsMenuActionTriggered(QAction *action); void onCurrentTabChanged(int index); + void onAutoHideButtonClicked(); + void onAutoHideDockAreaActionClicked(); + void onAutoHideToActionClicked(); protected: /** @@ -137,6 +141,11 @@ public: */ QAbstractButton *button(eTitleBarButton which) const; + /** + * Returns the auto hide title label, used when the dock area is expanded and auto hidden + */ + ElidingLabel* autoHideTitleLabel() const; + /** * Updates the visibility of the dock widget actions in the title bar */ @@ -166,6 +175,12 @@ public: */ int indexOf(QWidget *widget) const; + /** + * Close group tool tip based on the current state + * Auto hide widgets can only have one dock widget so it does not make sense for the tooltip to show close group + */ + QString titleBarButtonToolTip(eTitleBarButton button) const; + signals: /** * This signal is emitted if a tab in the tab bar is clicked by the user diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index 522b6b985ee..0a0fef497af 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -4,15 +4,17 @@ #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 "dockoverlay.h" #include "docksplitter.h" #include "dockwidget.h" #include "dockwidgettab.h" +#include "elidinglabel.h" #include "floatingdockcontainer.h" #include @@ -30,641 +32,1148 @@ #include -namespace ADS +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() { - static const char *const INDEX_PROPERTY = "index"; - static const char *const ACTION_PROPERTY = "action"; + 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; +/** + * 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: +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); - - 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); - } - - if (auto layoutItem = m_parentLayout->takeAt(1)) - layoutItem->widget()->setParent(nullptr); - - 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(); } - }; - - /** - * Private data class of DockAreaWidget class (pimpl) - */ - struct DockAreaWidgetPrivate - { - DockAreaWidget *q = nullptr; - QBoxLayout *m_layout = nullptr; - DockAreaLayout *m_contentsLayout = nullptr; - DockAreaTitleBar *m_titleBar = nullptr; - DockManager *m_dockManager = nullptr; - bool m_updateTitleBarButtons = false; - DockWidgetAreas m_allowedAreas = AllDockAreas; - QSize m_minSizeHint; - - /** - * 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(ACTION_PROPERTY)); - } - - /** - * Returns the index of the given dock widget - */ - int dockWidgetIndex(DockWidget *dockWidget) const - { - return dockWidget->property(INDEX_PROPERTY).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(); - - /** - * Scans all contained dock widgets for the max. minimum size hint - */ - void updateMinimumSizeHint() - { - m_minSizeHint = QSize(); - for (int i = 0; i < m_contentsLayout->count(); ++i) - { - auto widget = m_contentsLayout->widget(i); - m_minSizeHint.setHeight(qMax(m_minSizeHint.height(), - widget->minimumSizeHint().height())); - m_minSizeHint.setWidth(qMax(m_minSizeHint.width(), - widget->minimumSizeHint().width())); - } - } - }; - // struct DockAreaWidgetPrivate - - DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *parent) - : q(parent) + DockAreaLayout(QBoxLayout *parentLayout) + : m_parentLayout(parentLayout) {} - void DockAreaWidgetPrivate::createTitleBar() + /** + * 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) { - 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); - } + widget->setParent(nullptr); + if (index < 0) + index = m_widgets.count(); - 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->updateDockWidgetActionsButtons(); - m_updateTitleBarButtons = false; - } - - 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 = new DockAreaLayout(d->m_layout); - if (d->m_dockManager) - emit d->m_dockManager->dockAreaCreated(this); - } - - DockAreaWidget::~DockAreaWidget() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d->m_contentsLayout; - delete d; - } - - DockManager *DockAreaWidget::dockManager() const { return d->m_dockManager; } - - DockContainerWidget *DockAreaWidget::dockContainer() const - { - return internal::findParent(this); - } - - void DockAreaWidget::addDockWidget(DockWidget *dockWidget) - { - insertDockWidget(d->m_contentsLayout->count(), dockWidget); - } - - void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool activate) - { - 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()); - dockWidget->setProperty(INDEX_PROPERTY, index); - d->m_minSizeHint.setHeight(qMax(d->m_minSizeHint.height(), - dockWidget->minimumSizeHint().height())); - d->m_minSizeHint.setWidth(qMax(d->m_minSizeHint.width(), - dockWidget->minimumSizeHint().width())); - if (activate) + m_widgets.insert(index, widget); + if (m_currentIndex < 0) { setCurrentIndex(index); - - // If this dock area is hidden, then we need to make it visible again - // by calling dockWidget->toggleViewInternal(true); - if (!this->isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) - dockWidget->toggleViewInternal(true); - - d->updateTitleBarButtonStates(); + } else { + if (index <= m_currentIndex) + ++m_currentIndex; + } } - void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) + /** + * Removes the given widget from the layout + */ + void removeWidget(QWidget *widget) { - qCInfo(adsLog) << Q_FUNC_INFO; - auto nextOpen = nextOpenDockWidget(dockWidget); + if (currentWidget() == widget) { + auto layoutItem = m_parentLayout->takeAt(1); + if (layoutItem) + layoutItem->widget()->setParent(nullptr); - d->m_contentsLayout->removeWidget(dockWidget); - auto tabWidget = dockWidget->tabWidget(); - tabWidget->hide(); - d->tabBar()->removeTab(tabWidget); - DockContainerWidget *dockContainerWidget = dockContainer(); - if (nextOpen) { - setCurrentDockWidget(nextOpen); - } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() >= 1) { - qCInfo(adsLog) << "Dock Area empty"; - dockContainerWidget->removeDockArea(this); - this->deleteLater(); - } else { - // 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(); + 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); } - d->updateTitleBarButtonStates(); - updateTitleBarVisibility(); - d->updateMinimumSizeHint(); - auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); - if (topLevelDockWidget) - topLevelDockWidget->emitTopLevelChanged(true); + 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 *m_contentsLayout = nullptr; + DockAreaTitleBar *m_titleBar = nullptr; + DockManager *m_dockManager = nullptr; + AutoHideDockContainer *m_autoHideDockContainer = nullptr; + bool m_updateTitleBarButtons = false; + DockWidgetAreas m_allowedAreas = AllDockAreas; + QSize m_minSizeHint; + 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); + + /** + * Scans all contained dock widgets for the max. minimum size hint + */ + void updateMinimumSizeHint() + { + m_minSizeHint = QSize(); + for (int i = 0; i < m_contentsLayout->count(); ++i) { + auto widget = m_contentsLayout->widget(i); + m_minSizeHint.setHeight( + qMax(m_minSizeHint.height(), widget->minimumSizeHint().height())); + m_minSizeHint.setWidth(qMax(m_minSizeHint.width(), widget->minimumSizeHint().width())); + } + } +}; +// 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; + + 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() && !q->isAutoHide()); + m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide()); + } 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 = new DockAreaLayout(d->m_layout); + if (d->m_dockManager) + emit d->m_dockManager->dockAreaCreated(this); +} + +DockAreaWidget::~DockAreaWidget() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d->m_contentsLayout; + delete d; +} + +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(); +} + +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); + d->m_minSizeHint.setHeight( + qMax(d->m_minSizeHint.height(), dockWidget->minimumSizeHint().height())); + d->m_minSizeHint.setWidth(qMax(d->m_minSizeHint.width(), dockWidget->minimumSizeHint().width())); + if (activate) { + setCurrentIndex(index); + dockWidget->setClosedState( + false); // Set current index can show the widget without changing the close state, added to keep the close state consistent + } + + // If this dock area is hidden, then we need to make it visible again + // by calling dockWidget->toggleViewInternal(true); + if (!this->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); + 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(); + d->updateMinimumSizeHint(); + auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); + if (topLevelDockWidget) + topLevelDockWidget->emitTopLevelChanged(true); #if (ADS_DEBUG_LEVEL > 0) - dockContainerWidget->dumpLayout(); + dockContainerWidget->dumpLayout(); #endif +} + +void DockAreaWidget::hideAreaWithNoVisibleContent() +{ + this->toggleView(false); + + // Hide empty parent splitters + auto splitter = internal::findParent(this); + internal::hideEmptyParentSplitters(splitter); + + //Hide empty floating widget + DockContainerWidget *container = this->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(); } - void DockAreaWidget::hideAreaWithNoVisibleContent() - { - this->toggleView(false); + if (isAutoHide()) + autoHideDockContainer()->hide(); +} - // Hide empty parent splitters - auto splitter = internal::findParent(this); - internal::hideEmptyParentSplitters(splitter); +void DockAreaWidget::onTabCloseRequested(int index) +{ + qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; + auto *currentDockWidget = dockWidget(index); + if (currentDockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + || currentDockWidget->features().testFlag(DockWidget::CustomCloseHandling)) + currentDockWidget->closeDockWidgetInternal(); + else + currentDockWidget->toggleView(false); +} - //Hide empty floating widget - DockContainerWidget *container = this->dockContainer(); - if (!container->isFloating() - && DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) - return; +DockWidget *DockAreaWidget::currentDockWidget() const +{ + int currentIdx = currentIndex(); + if (currentIdx < 0) + return nullptr; - updateTitleBarVisibility(); - auto topLevelWidget = container->topLevelDockWidget(); - auto floatingWidget = container->floatingWidget(); - if (topLevelWidget) { - if (floatingWidget) - floatingWidget->updateWindowTitle(); + return dockWidget(currentIdx); +} - DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); - } else if (container->openedDockAreas().isEmpty() && floatingWidget) { - floatingWidget->hide(); - } +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); + dockWidget->setClosedState( + false); // Set current index can show the widget without changing the close state, added to keep the close state consistent +} + +void DockAreaWidget::setCurrentIndex(int index) +{ + auto currentTabBar = d->tabBar(); + if (index < 0 || index > (currentTabBar->count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; } - void DockAreaWidget::onTabCloseRequested(int index) - { - qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; - auto *currentDockWidget = dockWidget(index); - if (currentDockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) - currentDockWidget->closeDockWidgetInternal(); - else - currentDockWidget->toggleView(false); + 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; +} - 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); - } - - 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) +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(dockWidget(i)); + } + return dockWidgetList; +} - return dockWidgetList; +int DockAreaWidget::indexOfFirstOpenDockWidget() const +{ + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + if (!dockWidget(i)->isClosed()) + return i; } - int DockAreaWidget::openDockWidgetsCount() const - { - int count = 0; - for (int i = 0; i < d->m_contentsLayout->count(); ++i) { - if (!dockWidget(i)->isClosed()) - ++count; - } - return count; + 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; } - 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(dockWidget(i)); - } - return dockWidgetList; + 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); } - int DockAreaWidget::indexOfFirstOpenDockWidget() const - { - for (int i = 0; i < d->m_contentsLayout->count(); ++i) { - if (!dockWidget(i)->isClosed()) - return i; + if (isAutoHideFeatureEnabled()) { + auto tabBar = d->m_titleBar->tabBar(); + tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden + d->m_titleBar->autoHideTitleLabel()->setVisible( + autoHide); // Always show when auto hidden, never otherwise + 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; } - return - 1; + 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); } - int DockAreaWidget::dockWidgetsCount() const { return d->m_contentsLayout->count(); } + if (testing) + return true; - DockWidget *DockAreaWidget::dockWidget(int index) const - { - return qobject_cast(d->m_contentsLayout->widget(index)); + if (!dockArea->dockWidgetsCount()) { + delete dockArea; + dockArea = nullptr; + } else { + dockArea->setProperty("currentDockWidget", currentDockWidget); } - 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; - } + createdWidget = dockArea; + return true; +} - 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 (DockManager::testConfigFlag(DockManager::AlwaysShowTabs)) - return; - - if (d->m_titleBar) { - bool hidden = container->hasTopLevelDockWidget() && (container->isFloating() - || DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)); - d->m_titleBar->setVisible(!hidden); - } - } - - void DockAreaWidget::markTitleBarMenuOutdated() - { - if (d->m_titleBar) - d->m_titleBar->markTabsMenuOutdated(); - } - - 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); - 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(); - } - - DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const - { - auto openDockWidgets = openedDockWidgets(); - if (openDockWidgets.count() > 1 - || (openDockWidgets.count() == 1 && openDockWidgets[0] != dockWidget)) { - DockWidget *nextDockWidget; - if (openDockWidgets.last() == dockWidget) { - nextDockWidget = openDockWidgets[openDockWidgets.count() - 2]; - } else { - int nextIndex = openDockWidgets.indexOf(dockWidget) + 1; - nextDockWidget = openDockWidgets[nextIndex]; +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 { - return nullptr; + 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(); +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; + } else { + DockWidget::DockWidgetFeatures features(DockWidget::NoDockWidgetFeatures); + for (const auto dockWidget : dockWidgets()) + features |= dockWidget->features(); - return features; - } + return features; } +} - void DockAreaWidget::toggleView(bool open) - { - setVisible(open); +void DockAreaWidget::toggleView(bool open) +{ + setVisible(open); - emit viewToggled(open); - } + emit viewToggled(open); +} - void DockAreaWidget::setVisible(bool visible) - { - Super::setVisible(visible); - if (d->m_updateTitleBarButtons) - d->updateTitleBarButtonStates(); - } +void DockAreaWidget::setVisible(bool visible) +{ + Super::setVisible(visible); + if (d->m_updateTitleBarButtons) + d->updateTitleBarButtonStates(); +} - void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) - { - d->m_allowedAreas = areas; - } +void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) +{ + d->m_allowedAreas = areas; +} - DockWidgetAreas DockAreaWidget::allowedAreas() const - { - return d->m_allowedAreas; - } +DockWidgetAreas DockAreaWidget::allowedAreas() const +{ + return d->m_allowedAreas; +} - QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const - { - return d->m_titleBar->button(which); - } +DockAreaWidget::DockAreaFlags DockAreaWidget::dockAreaFlags() const +{ + return d->m_flags; +} - void DockAreaWidget::closeArea() - { - // If there is only one single dock widget and this widget has the - // DeleteOnClose feature, then we delete the dock widget now - auto openDockWidgets = openedDockWidgets(); - if (openDockWidgets.count() == 1 - && openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - openDockWidgets[0]->closeDockWidgetInternal(); - } else { - for (auto dockWidget : openedDockWidgets()) +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 CDockWidget 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); + } } } +} - void DockAreaWidget::closeOtherAreas() { dockContainer()->closeOtherAreas(this); } +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 +}; - DockAreaTitleBar *DockAreaWidget::titleBar() const { return d->m_titleBar; } +SideBarLocation DockAreaWidget::calculateSideTabBarArea() const +{ + auto container = dockContainer(); + auto contentRect = container->contentRect(); - QSize DockAreaWidget::minimumSizeHint() const - { - return d->m_minSizeHint.isValid() ? d->m_minSizeHint : Super::minimumSizeHint(); + 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) +{ + if (!isAutoHideFeatureEnabled()) + return; + + if (!enable) { + if (isAutoHide()) + autoHideDockContainer()->moveContentsToParent(); + + 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); + } +} + +void DockAreaWidget::toggleAutoHide(SideBarLocation location) +{ + if (!isAutoHideFeatureEnabled()) + return; + + setAutoHide(!isAutoHide(), location); +} + +void DockAreaWidget::closeOtherAreas() +{ + dockContainer()->closeOtherAreas(this); +} + +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 +{ + if (!d->m_minSizeHint.isValid()) + return Super::minimumSizeHint(); + + if (d->m_titleBar->isVisible()) + return d->m_minSizeHint + QSize(0, d->m_titleBar->minimumSizeHint().height()); + else + return d->m_minSizeHint; +} + +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 diff --git a/src/libs/advanceddockingsystem/dockareawidget.h b/src/libs/advanceddockingsystem/dockareawidget.h index a0482136bf5..ca23226fdbe 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.h +++ b/src/libs/advanceddockingsystem/dockareawidget.h @@ -4,6 +4,7 @@ #pragma once #include "ads_globals.h" +#include "autohidetab.h" #include "dockwidget.h" #include @@ -20,6 +21,8 @@ class DockManager; class DockContainerWidget; class DockContainerWidgetPrivate; class DockAreaTitleBar; +class DockingStateReader; +class AutoHideDockContainer; /** * DockAreaWidget manages multiple instances of DockWidgets. @@ -39,6 +42,9 @@ private: friend class DockWidget; friend class DockManagerPrivate; friend class DockManager; + friend class AutoHideDockContainer; + + void onDockWidgetFeaturesChanged(); void onTabCloseRequested(int index); @@ -48,7 +54,34 @@ private: */ void reorderDockWidget(int fromIndex, int toIndex); + /* + * Update the auto hide button checked state based on if it's contained in an auto hide container or not + */ + void updateAutoHideButtonCheckState(); + + /* + * Update the title bar button tooltips + */ + void updateTitleBarButtonsToolTips(); + + /** + * Calculate the auto hide side bar location depending on the dock area + * widget position in the container + */ + SideBarLocation calculateSideTabBarArea() const; + protected: + +#ifdef Q_OS_WIN + /** + * Reimplements QWidget::event to handle QEvent::PlatformSurface + * This is here to fix issue #294 Tab refresh problem with a QGLWidget + * that exists since Qt version 5.12.7. So this function is here to + * work around a Qt issue. + */ + virtual bool event(QEvent *event) override; +#endif + /** * Inserts a dock widget into dock area. * All dockwidgets in the dock area tabified in a stacked layout with tabs. @@ -110,11 +143,26 @@ protected: */ void markTitleBarMenuOutdated(); + /* + * Update the title bar button visibility based on if it's top level or not + */ + void updateTitleBarButtonVisibility(bool isTopLevel) const; + void toggleView(bool open); public: using Super = QFrame; + /** + * Dock area related flags + */ + enum eDockAreaFlag + { + HideSingleWidgetTitleBar = 0x0001, + DefaultFlags = 0x0000 + }; + Q_DECLARE_FLAGS(DockAreaFlags, eDockAreaFlag) + /** * Default Constructor */ @@ -135,6 +183,27 @@ public: * if there is no */ DockContainerWidget *dockContainer() const; + /** + * Returns the auto hide dock container widget this dock area widget belongs to or 0 + * if there is no + */ + AutoHideDockContainer *autoHideDockContainer() const; + + /** + * Returns true if the dock area is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Sets the current auto hide dock container + */ + void setAutoHideDockContainer(AutoHideDockContainer *autoHideDockContainer); + + /** + * Returns the largest minimumSizeHint() of the dock widgets in this area. + * The minimum size hint is updated if a dock widget is removed or added. + */ + virtual QSize minimumSizeHint() const override; /** * Returns the rectangle of the title area @@ -205,6 +274,12 @@ public: */ void saveState(QXmlStreamWriter &stream) const; + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + static bool restoreState(DockingStateReader& stream, DockAreaWidget*& createdWidget, bool testing, DockContainerWidget* parentContainer); + /** * This functions returns the dock widget features of all dock widget in * this area. @@ -242,6 +317,41 @@ public: */ DockAreaTitleBar *titleBar() const; + /** + * Returns the dock area flags - a combination of flags that configure the + * appearance and features of the dock area. + * \see setDockAreaFlasg() + */ + DockAreaFlags dockAreaFlags() const; + + /** + * Sets the dock area flags - a combination of flags that configure the + * appearance and features of the dock area + */ + void setDockAreaFlags(DockAreaFlags flags); + + /** + * Sets the dock area flag Flag on this widget if on is true; otherwise + * clears the flag. + */ + void setDockAreaFlag(eDockAreaFlag flag, bool on); + + /** + * Returns true if the area has a single dock widget and contains the central widget of it's manager. + */ + bool isCentralWidgetArea() const; + + /** + * Returns true if the area contains the central widget of it's manager. + */ + bool containsCentralWidget() const; + + /** + * If this dock area is the one and only visible area in a container, then + * this function returns true + */ + bool isTopLevelArea() const; + /** * This activates the tab for the given tab index. * If the dock widget for the given tab is not visible, the this function @@ -254,18 +364,24 @@ public: */ void closeArea(); + /** + * Sets the dock area into auto hide mode or into normal mode. + * If the dock area is switched to auto hide mode, then all dock widgets + * that are pinable will be added to the sidebar + */ + void setAutoHide(bool enable, SideBarLocation location = SideBarNone); + + /** + * Switches the dock area to auto hide mode or vice versa depending on its + * current state. + */ + void toggleAutoHide(SideBarLocation location = SideBarNone); + /** * This function closes all other areas except of this area */ void closeOtherAreas(); - /** - * Returns the largest minimumSizeHint() of the dock widgets in this - * area. - * The minimum size hint is updated if a dock widget is removed or added. - */ - QSize minimumSizeHint() const override; - signals: /** * This signal is emitted when user clicks on a tab at an index. diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp index 76afb45d2c9..cf975b8b2cd 100644 --- a/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp @@ -3,46 +3,52 @@ #include "dockcomponentsfactory.h" -#include "dockwidgettab.h" +#include "autohidetab.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" -#include "dockwidget.h" #include "dockareawidget.h" +#include "dockwidget.h" +#include "dockwidgettab.h" #include -namespace ADS +namespace ADS { + +static std::unique_ptr g_defaultFactory(new DockComponentsFactory()); + +DockWidgetTab *DockComponentsFactory::createDockWidgetTab(DockWidget *dockWidget) const { - static std::unique_ptr g_defaultFactory(new DockComponentsFactory()); + return new DockWidgetTab(dockWidget); +} - DockWidgetTab *DockComponentsFactory::createDockWidgetTab(DockWidget *dockWidget) const - { - return new DockWidgetTab(dockWidget); - } +AutoHideTab *DockComponentsFactory::createDockWidgetSideTab(DockWidget *dockWidget) const +{ + return new AutoHideTab(dockWidget); +} - DockAreaTabBar *DockComponentsFactory::createDockAreaTabBar(DockAreaWidget *dockArea) const - { - return new DockAreaTabBar(dockArea); - } +DockAreaTabBar *DockComponentsFactory::createDockAreaTabBar(DockAreaWidget *dockArea) const +{ + return new DockAreaTabBar(dockArea); +} - DockAreaTitleBar *DockComponentsFactory::createDockAreaTitleBar(DockAreaWidget *dockArea) const - { - return new DockAreaTitleBar(dockArea); - } +DockAreaTitleBar *DockComponentsFactory::createDockAreaTitleBar(DockAreaWidget *dockArea) const +{ + return new DockAreaTitleBar(dockArea); +} - const DockComponentsFactory *DockComponentsFactory::factory() - { - return g_defaultFactory.get(); - } +const DockComponentsFactory *DockComponentsFactory::factory() +{ + return g_defaultFactory.get(); +} - void DockComponentsFactory::setFactory(DockComponentsFactory *factory) - { - g_defaultFactory.reset(factory); - } +void DockComponentsFactory::setFactory(DockComponentsFactory *factory) +{ + g_defaultFactory.reset(factory); +} - void DockComponentsFactory::resetDefaultFactory() - { - g_defaultFactory.reset(new DockComponentsFactory()); - } +void DockComponentsFactory::resetDefaultFactory() +{ + g_defaultFactory.reset(new DockComponentsFactory()); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.h b/src/libs/advanceddockingsystem/dockcomponentsfactory.h index ce5c656ac04..5333af45034 100644 --- a/src/libs/advanceddockingsystem/dockcomponentsfactory.h +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.h @@ -12,6 +12,7 @@ class DockAreaTitleBar; class DockAreaTabBar; class DockAreaWidget; class DockWidget; +class AutoHideTab; /** * Factory for creation of certain GUI elements for the docking framework. @@ -37,6 +38,12 @@ public: */ virtual DockWidgetTab *createDockWidgetTab(DockWidget *dockWidget) const; + /** + * This default implementation just creates a dock widget side tab with + * new CDockWidgetTab(DockWidget). + */ + virtual AutoHideTab *createDockWidgetSideTab(DockWidget *dockWidget) const; + /** * This default implementation just creates a dock area tab bar with * new DockAreaTabBar(dockArea). diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 51ced580963..dcfb7964366 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -3,8 +3,10 @@ #include "dockcontainerwidget.h" -#include "ads_globals.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "autohidetab.h" #include "dockareawidget.h" #include "dockingstatereader.h" #include "dockmanager.h" @@ -15,1386 +17,1698 @@ #include #include -#include #include #include #include +#include #include +#include #include #include #include #include -namespace ADS +namespace ADS { + +static unsigned int g_zOrderCounter = 0; + +enum eDropMode { + DropModeIntoArea, ///< drop widget into a dock area + DropModeIntoContainer, ///< drop into container + DropModeInvalid ///< invalid mode - do not drop +}; + +/** + * Converts dock area ID to an index for array access + */ +static int areaIdToIndex(DockWidgetArea area) { - static unsigned int zOrderCounter = 0; + switch (area) { + case LeftDockWidgetArea: + return 0; + case RightDockWidgetArea: + return 1; + case TopDockWidgetArea: + return 2; + case BottomDockWidgetArea: + return 3; + case CenterDockWidgetArea: + return 4; + default: + return 4; + } +} - enum eDropMode { - DropModeIntoArea, ///< drop widget into a dock area - DropModeIntoContainer, ///< drop into container - DropModeInvalid ///< invalid mode - do not drop - }; +/** + * Helper function to ease insertion of dock area into splitter + */ +static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append) +{ + if (append) + splitter->addWidget(widget); + else + splitter->insertWidget(0, widget); +} + +/** + * Private data class of DockContainerWidget class (pimpl) + */ +class DockContainerWidgetPrivate +{ +public: + DockContainerWidget *q; + QPointer m_dockManager; + unsigned int m_zOrderIndex = 0; + QList m_dockAreas; + QList m_autoHideWidgets; + QMap m_sideTabBarWidgets; + QGridLayout *m_layout = nullptr; + QSplitter *m_rootSplitter = nullptr; + bool m_isFloating = false; + DockAreaWidget *m_lastAddedAreaCache[5]; + int m_visibleDockAreaCount = -1; + DockAreaWidget *m_topLevelDockArea = nullptr; + QTimer m_delayedAutoHideTimer; + AutoHideTab *m_delayedAutoHideTab; + bool m_delayedAutoHideShow = false; /** - * Converts dock area ID to an index for array access + * Private data constructor */ - static int areaIdToIndex(DockWidgetArea area) + DockContainerWidgetPrivate(DockContainerWidget *parent); + + /** + * Adds dock widget to container and returns the dock area that contains + * the inserted dock widget + */ + DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget); + + /** + * Adds dock widget to a existing DockWidgetArea + */ + DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *targetDockArea, + int index = -1); + + /** + * Add dock area to this container + */ + void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Drop floating widget into container + */ + void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area); + + /** + * Drop floating widget into dock area + */ + void dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * new dock widget area + */ + void moveToNewSection(QWidget *widget, DockAreaWidget *targetArea, DockWidgetArea area); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * a dock area in container + */ + void moveToContainer(QWidget *widget, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, DockAreaWidget *targetArea); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea); + + /** + * Adds new dock areas to the internal dock area list + */ + void addDockAreasToList(const QList newDockAreas); + + /** + * Wrapper function for DockAreas append, that ensures that dock area signals + * are properly connected to dock container slots + */ + void appendDockAreas(const QList newDockAreas); + + /** + * Save state of child nodes + */ + void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget); + + /** + * Save state of auto hide widgets + */ + void saveAutoHideWidgetsState(QXmlStreamWriter &stream); + + /** + * Restore state of child nodes. + * \param[in] Stream The data stream that contains the serialized state + * \param[out] CreatedWidget The widget created from parsed data or 0 if + * the parsed widget was an empty splitter + * \param[in] Testing If Testing is true, only the stream data is + * parsed without modifiying anything. + */ + bool restoreChildNodes(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a splitter. + * \see restoreChildNodes() for details + */ + bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a auto hide side bar + */ + bool restoreSideBar(DockingStateReader &stream, QWidget *&createdWidget, bool testing); + + /** + * Helper function for recursive dumping of layout + */ + void dumpRecursive(int level, QWidget *widget) const; + + /** + * Calculate the drop mode from the given target position + */ + eDropMode getDropMode(const QPoint &targetPosition); + + /** + * Initializes the visible dock area count variable if it is not initialized + * yet + */ + void initVisibleDockAreaCount() { - switch (area) { - case LeftDockWidgetArea: - return 0; - case RightDockWidgetArea: - return 1; - case TopDockWidgetArea: - return 2; - case BottomDockWidgetArea: - return 3; - case CenterDockWidgetArea: - return 4; - default: - return 4; - } + if (m_visibleDockAreaCount > -1) + return; + + m_visibleDockAreaCount = 0; + for (auto dockArea : std::as_const(m_dockAreas)) + m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1; } /** - * Helper function to ease insertion of dock area into splitter + * Access function for the visible dock area counter */ - static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append) + int visibleDockAreaCount() { - if (append) - splitter->addWidget(widget); - else - splitter->insertWidget(0, widget); + // Lazy initialization - we initialize the m_visibleDockAreaCount variable + // on first use + initVisibleDockAreaCount(); + return m_visibleDockAreaCount; } /** - * Private data class of DockContainerWidget class (pimpl) + * The visible dock area count changes, if dock areas are remove, added or + * when its view is toggled */ - class DockContainerWidgetPrivate + void onVisibleDockAreaCountChanged(); + + void emitDockAreasRemoved() { - public: - DockContainerWidget *q; - QPointer m_dockManager; - unsigned int m_zOrderIndex = 0; - QList m_dockAreas; - QGridLayout *m_layout = nullptr; - QSplitter *m_rootSplitter = nullptr; - bool m_isFloating = false; - DockAreaWidget *m_lastAddedAreaCache[5]; - int m_visibleDockAreaCount = -1; - DockAreaWidget *m_topLevelDockArea = nullptr; - - /** - * Private data constructor - */ - DockContainerWidgetPrivate(DockContainerWidget *parent); - - /** - * Adds dock widget to container and returns the dock area that contains - * the inserted dock widget - */ - DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget); - - /** - * Adds dock widget to a existing DockWidgetArea - */ - DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *targetDockArea); - - /** - * Add dock area to this container - */ - void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea); - - /** - * Drop floating widget into container - */ - void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area); - - /** - * Drop floating widget into dock area - */ - void dropIntoSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea, - DockWidgetArea area); - - /** - * Moves the dock widget or dock area given in Widget parameter to a - * new dock widget area - */ - void moveToNewSection(QWidget *widget, DockAreaWidget *targetArea, DockWidgetArea area); - - /** - * Moves the dock widget or dock area given in Widget parameter to a - * a dock area in container - */ - void moveToContainer(QWidget *widget, DockWidgetArea area); - - /** - * Creates a new tab for a widget dropped into the center of a section - */ - void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea); - - /** - * Creates a new tab for a widget dropped into the center of a section - */ - void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea); - - /** - * Adds new dock areas to the internal dock area list - */ - void addDockAreasToList(const QList newDockAreas); - - /** - * Wrapper function for DockAreas append, that ensures that dock area signals - * are properly connected to dock container slots - */ - void appendDockAreas(const QList newDockAreas); - - /** - * Save state of child nodes - */ - void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget); - - /** - * Restore state of child nodes. - * \param[in] Stream The data stream that contains the serialized state - * \param[out] CreatedWidget The widget created from parsed data or 0 if - * the parsed widget was an empty splitter - * \param[in] Testing If Testing is true, only the stream data is - * parsed without modifiying anything. - */ - bool restoreChildNodes(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing); - - /** - * Restores a splitter. - * \see restoreChildNodes() for details - */ - bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); - - /** - * Restores a dock area. - * \see restoreChildNodes() for details - */ - bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); - - /** - * Helper function for recursive dumping of layout - */ - void dumpRecursive(int level, QWidget *widget) const; - - /** - * Calculate the drop mode from the given target position - */ - eDropMode getDropMode(const QPoint &targetPosition); - - /** - * Initializes the visible dock area count variable if it is not initialized - * yet - */ - void initVisibleDockAreaCount() - { - if (m_visibleDockAreaCount > -1) - return; - - m_visibleDockAreaCount = 0; - for (auto dockArea : std::as_const(m_dockAreas)) - m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1; - } - - /** - * Access function for the visible dock area counter - */ - int visibleDockAreaCount() - { - // Lazy initialization - we initialize the m_visibleDockAreaCount variable - // on first use - initVisibleDockAreaCount(); - return m_visibleDockAreaCount; - } - - /** - * The visible dock area count changes, if dock areas are remove, added or - * when its view is toggled - */ - void onVisibleDockAreaCountChanged(); - - void emitDockAreasRemoved() - { - onVisibleDockAreaCountChanged(); - emit q->dockAreasRemoved(); - } - - void emitDockAreasAdded() - { - onVisibleDockAreaCountChanged(); - emit q->dockAreasAdded(); - } - - /** - * Helper function for creation of new splitter - */ - DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) - { - auto *splitter = new DockSplitter(orientation, parent); - splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize)); - splitter->setChildrenCollapsible(false); - return splitter; - } - - /** - * Ensures equal distribution of the sizes of a splitter if an dock widget - * is inserted from code - */ - void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0) - { - const int areaSize = (splitter->orientation() == Qt::Horizontal) ? splitter->width() - : splitter->height(); - auto splitterSizes = splitter->sizes(); - - const qreal totalRatio = splitterSizes.size() - 1.0 + lastRatio; - for (int i = 0; i < splitterSizes.size() - 1; ++i) - splitterSizes[i] = areaSize / totalRatio; - - splitterSizes.back() = areaSize * lastRatio / totalRatio; - splitter->setSizes(splitterSizes); - } - - void onDockAreaViewToggled(DockAreaWidget *dockArea, bool visible) - { - m_visibleDockAreaCount += visible ? 1 : -1; - onVisibleDockAreaCountChanged(); - emit q->dockAreaViewToggled(dockArea, visible); - } - }; // struct DockContainerWidgetPrivate - - DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent) - : q(parent) - { - std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr); + onVisibleDockAreaCountChanged(); + emit q->dockAreasRemoved(); } - eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition) + void emitDockAreasAdded() { - DockAreaWidget *dockArea = q->dockAreaAt(targetPosition); - auto dropArea = InvalidDockWidgetArea; - auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor(); + onVisibleDockAreaCountChanged(); + emit q->dockAreasAdded(); + } - if (dockArea) { - auto dropOverlay = m_dockManager->dockAreaOverlay(); - dropOverlay->setAllowedAreas(dockArea->allowedAreas()); - dropArea = dropOverlay->showOverlay(dockArea); - if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) - dropArea = InvalidDockWidgetArea; + /** + * Helper function for creation of new splitter + */ + DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) + { + auto *splitter = new DockSplitter(orientation, parent); + splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize)); + splitter->setChildrenCollapsible(false); + return splitter; + } - if (dropArea != InvalidDockWidgetArea) { - qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; - return DropModeIntoArea; + /** + * Ensures equal distribution of the sizes of a splitter if an dock widget + * is inserted from code + */ + void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0) + { + const int areaSize = (splitter->orientation() == Qt::Horizontal) ? splitter->width() + : splitter->height(); + auto splitterSizes = splitter->sizes(); + + const qreal totalRatio = splitterSizes.size() - 1.0 + lastRatio; + for (int i = 0; i < splitterSizes.size() - 1; ++i) + splitterSizes[i] = areaSize / totalRatio; + + splitterSizes.back() = areaSize * lastRatio / totalRatio; + splitter->setSizes(splitterSizes); + } + + /** + * This function forces the dock container widget to update handles of splitters + * based if a central widget exists. + */ + void updateSplitterHandles(QSplitter *splitter); + + /** + * If no central widget exists, the widgets resize with the container. + * If a central widget exists, the widgets surrounding the central widget + * do not resize its height or width. + */ + bool widgetResizesWithContainer(QWidget *widget); + + void onDockAreaViewToggled(DockAreaWidget *dockArea, bool visible) + { + m_visibleDockAreaCount += visible ? 1 : -1; + onVisibleDockAreaCountChanged(); + emit q->dockAreaViewToggled(dockArea, visible); + } +}; // struct DockContainerWidgetPrivate + +DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent) + : q(parent) +{ + std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr); + m_delayedAutoHideTimer.setSingleShot(true); + m_delayedAutoHideTimer.setInterval(500); + QObject::connect(&m_delayedAutoHideTimer, &QTimer::timeout, q, [this]() { + auto globalPos = m_delayedAutoHideTab->mapToGlobal(QPoint(0, 0)); + qApp->sendEvent(m_delayedAutoHideTab, + new QMouseEvent(QEvent::MouseButtonPress, + QPoint(0, 0), + globalPos, + Qt::LeftButton, + {Qt::LeftButton}, + Qt::NoModifier)); + }); +} + +eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition) +{ + DockAreaWidget *dockArea = q->dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor(); + + if (dockArea) { + auto dropOverlay = m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) + dropArea = InvalidDockWidgetArea; + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + return DropModeIntoArea; + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) + return DropModeIntoContainer; + } + + return DropModeInvalid; +} + +void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() +{ + auto topLevelDockArea = q->topLevelDockArea(); + + if (topLevelDockArea) { + this->m_topLevelDockArea = topLevelDockArea; + topLevelDockArea->updateTitleBarButtonVisibility(true); + } else if (this->m_topLevelDockArea) { + this->m_topLevelDockArea->updateTitleBarButtonVisibility(false); + this->m_topLevelDockArea = nullptr; + } +} + +void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget, + DockWidgetArea area) +{ + auto insertParam = internal::dockAreaInsertParameters(area); + DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer(); + auto newDockAreas + = floatingDockContainer->findChildren(QString(), + Qt::FindChildrenRecursively); + QSplitter *splitter = m_rootSplitter; + + if (m_dockAreas.count() <= 1) { + splitter->setOrientation(insertParam.orientation()); + } else if (splitter->orientation() != insertParam.orientation()) { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + updateSplitterHandles(newSplitter); + splitter = newSplitter; + delete layoutItem; + } + + // Now we can insert the floating widget content into this container + auto floatingSplitter = floatingDockContainer->rootSplitter(); + if (floatingSplitter->count() == 1) { + insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append()); + updateSplitterHandles(splitter); + } else if (floatingSplitter->orientation() == insertParam.orientation()) { + int insertIndex = insertParam.append() ? splitter->count() : 0; + while (floatingSplitter->count()) { + splitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + updateSplitterHandles(splitter); + } + } else { + insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append()); + } + + m_rootSplitter = splitter; + addDockAreasToList(newDockAreas); + + // If we dropped the floating widget into the main dock container that does + // not contain any dock widgets, then splitter is invisible and we need to + // show it to display the docked widgets + if (!splitter->isVisible()) + splitter->show(); + + q->dumpLayout(); +} + +void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea) +{ + DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); + auto newDockWidgets = floatingContainer->dockWidgets(); + auto topLevelDockArea = floatingContainer->topLevelDockArea(); + int newCurrentIndex = -1; + + // If the floating widget contains only one single dock are, then the + // current dock widget of the dock area will also be the future current + // dock widget in the drop area. + if (topLevelDockArea) + newCurrentIndex = topLevelDockArea->currentIndex(); + + for (int i = 0; i < newDockWidgets.count(); ++i) { + DockWidget *dockWidget = newDockWidgets[i]; + targetArea->insertDockWidget(i, dockWidget, false); + // If the floating widget contains multiple visible dock areas, then we + // simply pick the first visible open dock widget and make it + // the current one. + if (newCurrentIndex < 0 && !dockWidget->isClosed()) + newCurrentIndex = i; + } + targetArea->setCurrentIndex(newCurrentIndex); + targetArea->updateTitleBarVisibility(); + return; +} + +void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area) +{ + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) { + dropIntoCenterOfSection(floatingWidget, targetArea); + return; + } + + DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); + auto insertParam = internal::dockAreaInsertParameters(area); + auto newDockAreas + = floatingContainer->findChildren(QString(), Qt::FindChildrenRecursively); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); + + if (!targetAreaSplitter) { + QSplitter *splitter = createSplitter(insertParam.orientation()); + m_layout->replaceWidget(targetArea, splitter); + splitter->addWidget(targetArea); + targetAreaSplitter = splitter; + } + int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto floatingSplitter = floatingContainer->rootSplitter(); + + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + auto sizes = targetAreaSplitter->sizes(); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), + floatingSplitter); + updateSplitterHandles(targetAreaSplitter); + } else { + adjustSplitterSizes = (floatingSplitter->count() == 1); + int insertIndex = areaIndex + insertParam.insertOffset(); + while (floatingSplitter->count()) { + targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + updateSplitterHandles(targetAreaSplitter); } } - // mouse is over container - if (InvalidDockWidgetArea == dropArea) { - dropArea = containerDropArea; - qCInfo(adsLog) << "Container Drop Content: " << dropArea; - if (dropArea != InvalidDockWidgetArea) - return DropModeIntoContainer; + if (adjustSplitterSizes) { + int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + sizes[areaIndex] = size; + sizes.insert(areaIndex, size); + targetAreaSplitter->setSizes(sizes); } - - return DropModeInvalid; - } - - void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() - { - auto topLevelDockArea = q->topLevelDockArea(); - - if (topLevelDockArea) { - this->m_topLevelDockArea = topLevelDockArea; - topLevelDockArea->titleBarButton(TitleBarButtonUndock) - ->setVisible(false || !q->isFloating()); - topLevelDockArea->titleBarButton(TitleBarButtonClose) - ->setVisible(false || !q->isFloating()); - } else if (this->m_topLevelDockArea) { - this->m_topLevelDockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); - this->m_topLevelDockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); - this->m_topLevelDockArea = nullptr; - } - } - - void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget, - DockWidgetArea area) - { - auto insertParam = internal::dockAreaInsertParameters(area); - DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer(); - auto newDockAreas = floatingDockContainer - ->findChildren(QString(), - Qt::FindChildrenRecursively); - QSplitter *splitter = m_rootSplitter; - - if (m_dockAreas.count() <= 1) { - splitter->setOrientation(insertParam.orientation()); - } else if (splitter->orientation() != insertParam.orientation()) { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - splitter = newSplitter; - delete layoutItem; - } - - // Now we can insert the floating widget content into this container - auto floatingSplitter = floatingDockContainer->rootSplitter(); - if (floatingSplitter->count() == 1) { - insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append()); - } else if (floatingSplitter->orientation() == insertParam.orientation()) { - int insertIndex = insertParam.append() ? splitter->count() : 0; - while (floatingSplitter->count()) - splitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + } else { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + newSplitter->addWidget(floatingSplitter); + updateSplitterHandles(newSplitter); } else { - insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append()); + adjustSplitterSizes = (floatingSplitter->count() == 1); + while (floatingSplitter->count()) { + newSplitter->addWidget(floatingSplitter->widget(0)); + updateSplitterHandles(newSplitter); + } } - m_rootSplitter = splitter; - addDockAreasToList(newDockAreas); - - // If we dropped the floating widget into the main dock container that does - // not contain any dock widgets, then splitter is invisible and we need to - // show it to display the docked widgets - if (!splitter->isVisible()) - splitter->show(); - - q->dumpLayout(); + // Save the sizes before insertion and restore it later to prevent + // shrinking of existing area + auto sizes = targetAreaSplitter->sizes(); + insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); + updateSplitterHandles(newSplitter); + if (adjustSplitterSizes) { + int size = targetAreaSize / 2; + newSplitter->setSizes({size, size}); + } + targetAreaSplitter->insertWidget(areaIndex, newSplitter); + targetAreaSplitter->setSizes(sizes); + updateSplitterHandles(targetAreaSplitter); } - void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea) - { - DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); - auto newDockWidgets = floatingContainer->dockWidgets(); - auto topLevelDockArea = floatingContainer->topLevelDockArea(); - int newCurrentIndex = -1; + addDockAreasToList(newDockAreas); + q->dumpLayout(); +} - // If the floating widget contains only one single dock are, then the - // current dock widget of the dock area will also be the future current - // dock widget in the drop area. - if (topLevelDockArea) - newCurrentIndex = topLevelDockArea->currentIndex(); +void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea) +{ + auto droppedDockWidget = qobject_cast(widget); + auto droppedArea = qobject_cast(widget); + if (droppedDockWidget) { + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea == targetArea) + return; + + if (oldDockArea) + oldDockArea->removeDockWidget(droppedDockWidget); + + targetArea->insertDockWidget(0, droppedDockWidget, true); + } else { + QList newDockWidgets = droppedArea->dockWidgets(); + int newCurrentIndex = droppedArea->currentIndex(); for (int i = 0; i < newDockWidgets.count(); ++i) { DockWidget *dockWidget = newDockWidgets[i]; targetArea->insertDockWidget(i, dockWidget, false); - // If the floating widget contains multiple visible dock areas, then we - // simply pick the first visible open dock widget and make it - // the current one. - if (newCurrentIndex < 0 && !dockWidget->isClosed()) - newCurrentIndex = i; } targetArea->setCurrentIndex(newCurrentIndex); - targetArea->updateTitleBarVisibility(); + droppedArea->dockContainer()->removeDockArea(droppedArea); + droppedArea->deleteLater(); + } + + targetArea->updateTitleBarVisibility(); + return; +} + +void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, + DockAreaWidget *targetArea, + DockWidgetArea area) +{ + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) { + moveIntoCenterOfSection(widget, targetArea); return; } - void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea, - DockWidgetArea area) - { - // Dropping into center means all dock widgets in the dropped floating - // widget will become tabs of the drop area - if (CenterDockWidgetArea == area) { - dropIntoCenterOfSection(floatingWidget, targetArea); - return; - } - - auto insertParam = internal::dockAreaInsertParameters(area); - auto newDockAreas = floatingWidget->dockContainer() - ->findChildren(QString(), - Qt::FindChildrenRecursively); - QSplitter *targetAreaSplitter = internal::findParent(targetArea); - - if (!targetAreaSplitter) { - QSplitter *splitter = createSplitter(insertParam.orientation()); - m_layout->replaceWidget(targetArea, splitter); - splitter->addWidget(targetArea); - targetAreaSplitter = splitter; - } - int areaIndex = targetAreaSplitter->indexOf(targetArea); - auto widget = floatingWidget->dockContainer() - ->findChild(QString(), Qt::FindDirectChildrenOnly); - auto floatingSplitter = qobject_cast(widget); - - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - auto sizes = targetAreaSplitter->sizes(); - int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - bool adjustSplitterSizes = true; - if ((floatingSplitter->orientation() != insertParam.orientation()) - && floatingSplitter->count() > 1) { - targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), widget); - } else { - adjustSplitterSizes = (floatingSplitter->count() == 1); - int insertIndex = areaIndex + insertParam.insertOffset(); - while (floatingSplitter->count()) - targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); - } - - if (adjustSplitterSizes) { - int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; - sizes[areaIndex] = size; - sizes.insert(areaIndex, size); - targetAreaSplitter->setSizes(sizes); - } - } else { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - bool adjustSplitterSizes = true; - if ((floatingSplitter->orientation() != insertParam.orientation()) - && floatingSplitter->count() > 1) { - newSplitter->addWidget(widget); - } else { - adjustSplitterSizes = (floatingSplitter->count() == 1); - while (floatingSplitter->count()) { - newSplitter->addWidget(floatingSplitter->widget(0)); - } - } - - // Save the sizes before insertion and restore it later to prevent - // shrinking of existing area - auto sizes = targetAreaSplitter->sizes(); - insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); - if (adjustSplitterSizes) { - int size = targetAreaSize / 2; - newSplitter->setSizes({size, size}); - } - targetAreaSplitter->insertWidget(areaIndex, newSplitter); - targetAreaSplitter->setSizes(sizes); - } - - addDockAreasToList(newDockAreas); - q->dumpLayout(); - } - - void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, - DockAreaWidget *targetArea) - { - auto droppedDockWidget = qobject_cast(widget); - auto droppedArea = qobject_cast(widget); - - if (droppedDockWidget) { - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea == targetArea) - return; - - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - targetArea->insertDockWidget(0, droppedDockWidget, true); - } else { - QList newDockWidgets = droppedArea->dockWidgets(); - int newCurrentIndex = droppedArea->currentIndex(); - for (int i = 0; i < newDockWidgets.count(); ++i) { - DockWidget *dockWidget = newDockWidgets[i]; - targetArea->insertDockWidget(i, dockWidget, false); - } - targetArea->setCurrentIndex(newCurrentIndex); - droppedArea->dockContainer()->removeDockArea(droppedArea); - droppedArea->deleteLater(); - } - - targetArea->updateTitleBarVisibility(); - return; - } - - void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, - DockAreaWidget *targetArea, - DockWidgetArea area) - { - // Dropping into center means all dock widgets in the dropped floating - // widget will become tabs of the drop area - if (CenterDockWidgetArea == area) { - moveIntoCenterOfSection(widget, targetArea); - return; - } - - DockWidget *droppedDockWidget = qobject_cast(widget); - DockAreaWidget *droppedDockArea = qobject_cast(widget); - DockAreaWidget *newDockArea; - if (droppedDockWidget) { - newDockArea = new DockAreaWidget(m_dockManager, q); - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - newDockArea->addDockWidget(droppedDockWidget); - } else { - droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); - newDockArea = droppedDockArea; - } - - auto insertParam = internal::dockAreaInsertParameters(area); - QSplitter *targetAreaSplitter = internal::findParent(targetArea); - const int areaIndex = targetAreaSplitter->indexOf(targetArea); - auto sizes = targetAreaSplitter->sizes(); - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea); - const int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; - sizes[areaIndex] = size; - sizes.insert(areaIndex, size); - } else { - const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - newSplitter->addWidget(targetArea); - insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); - const int size = targetAreaSize / 2; - newSplitter->setSizes({size, size}); - targetAreaSplitter->insertWidget(areaIndex, newSplitter); - } - targetAreaSplitter->setSizes(sizes); - - addDockAreasToList({newDockArea}); - } - - void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area) - { - DockWidget *droppedDockWidget = qobject_cast(widget); - DockAreaWidget *droppedDockArea = qobject_cast(widget); - DockAreaWidget *newDockArea = nullptr; - - if (droppedDockWidget) { - newDockArea = new DockAreaWidget(m_dockManager, q); - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - newDockArea->addDockWidget(droppedDockWidget); - } else { - // We check, if we insert the dropped widget into the same place that - // it already has and do nothing, if it is the same place. It would - // also work without this check, but it looks nicer with the check - // because there will be no layout updates - auto splitter = internal::findParent(droppedDockArea); - auto insertParam = internal::dockAreaInsertParameters(area); - if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) { - if (insertParam.append() && splitter->lastWidget() == droppedDockArea) - return; - else if (!insertParam.append() && splitter->firstWidget() == droppedDockArea) - return; - } - droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); - newDockArea = droppedDockArea; - } - - addDockArea(newDockArea, area); - m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; - } - - void DockContainerWidgetPrivate::addDockAreasToList(const QList newDockAreas) - { - const int countBefore = m_dockAreas.count(); - const int newAreaCount = newDockAreas.count(); - appendDockAreas(newDockAreas); - // If the user dropped a floating widget that contains only one single - // visible dock area, then its title bar button TitleBarButtonUndock is - // likely hidden. We need to ensure, that it is visible - for (auto dockArea : newDockAreas) { - dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); - dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); - } - - // We need to ensure, that the dock area title bar is visible. The title bar - // is invisible, if the dock are is a single dock area in a floating widget. - if (1 == countBefore) - m_dockAreas.at(0)->updateTitleBarVisibility(); - - if (1 == newAreaCount) - m_dockAreas.last()->updateTitleBarVisibility(); - - emitDockAreasAdded(); - } - - void DockContainerWidgetPrivate::appendDockAreas(const QList newDockAreas) - { - m_dockAreas.append(newDockAreas); - for (auto dockArea : newDockAreas) { - QObject::connect(dockArea, &DockAreaWidget::viewToggled, - q, std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, - this, dockArea, std::placeholders::_1)); - } - } - - void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget) - { - QSplitter *splitter = qobject_cast(widget); - if (splitter) { - stream.writeStartElement("splitter"); - stream.writeAttribute("orientation", - QVariant::fromValue(splitter->orientation()).toString()); - stream.writeAttribute("count", QString::number(splitter->count())); - qCInfo(adsLog) << "NodeSplitter orientation:" << splitter->orientation() - << "WidgetCount:" << splitter->count(); - for (int i = 0; i < splitter->count(); ++i) - saveChildNodesState(stream, splitter->widget(i)); - - stream.writeStartElement("sizes"); - QStringList sizes; - for (auto size : splitter->sizes()) - sizes.append(QString::number(size)); - - stream.writeCharacters(sizes.join(" ")); - stream.writeEndElement(); // sizes - stream.writeEndElement(); // splitter - } else { - DockAreaWidget *dockArea = qobject_cast(widget); - if (dockArea) - dockArea->saveState(stream); - } - } - - bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString()); - - // Check if the orientation string is convertable - if (!orientationVar.canConvert()) - return false; - - Qt::Orientation orientation = orientationVar.value(); - - bool ok; - int widgetCount = stateReader.attributes().value("count").toInt(&ok); - if (!ok) - return false; - - qCInfo(adsLog) << "Restore NodeSplitter Orientation:" << orientation - << "WidgetCount:" << widgetCount; - QSplitter *splitter = nullptr; - if (!testing) - splitter = createSplitter(orientation); - - bool visible = false; - QList sizes; - while (stateReader.readNextStartElement()) { - QWidget *childNode = nullptr; - bool result = true; - if (stateReader.name() == QLatin1String("splitter")) { - result = restoreSplitter(stateReader, childNode, testing); - } else if (stateReader.name() == QLatin1String("area")) { - result = restoreDockArea(stateReader, childNode, testing); - } else if (stateReader.name() == QLatin1String("sizes")) { - QString size = stateReader.readElementText().trimmed(); - qCInfo(adsLog) << "Size: " << size; - QTextStream textStream(&size); - while (!textStream.atEnd()) { - int value; - textStream >> value; - sizes.append(value); - } - } else { - stateReader.skipCurrentElement(); - } - - if (!result) - return false; - - if (testing || !childNode) - continue; - - qCInfo(adsLog) << "ChildNode isVisible " << childNode->isVisible() << " isVisibleTo " - << childNode->isVisibleTo(splitter); - splitter->addWidget(childNode); - visible |= childNode->isVisibleTo(splitter); - } - - if (sizes.count() != widgetCount) - return false; - - if (!testing) { - if (!splitter->count()) { - delete splitter; - splitter = nullptr; - } else { - splitter->setSizes(sizes); - splitter->setVisible(visible); - } - createdWidget = splitter; - } else { - createdWidget = nullptr; - } - - return true; - } - - bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - 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 - - DockAreaWidget *dockArea = nullptr; - if (!testing) - dockArea = new DockAreaWidget(m_dockManager, q); - - 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 = m_dockManager->findDockWidget(objectName.toString()); - if (!dockWidget || testing) - continue; - - qCInfo(adsLog) << "Dock Widget found - parent " << dockWidget->parent(); - // We hide the DockArea here to prevent the short display (the flashing) - // of the dock areas during application startup - dockArea->hide(); - dockArea->addDockWidget(dockWidget); - dockWidget->setToggleViewActionChecked(!closed); - dockWidget->setClosedState(closed); - dockWidget->setProperty(internal::closedProperty, closed); - dockWidget->setProperty(internal::dirtyProperty, false); - } - - if (testing) - return true; - - if (!dockArea->dockWidgetsCount()) { - delete dockArea; - dockArea = nullptr; - } else { - dockArea->setProperty("currentDockWidget", currentDockWidget); - appendDockAreas({dockArea}); - } - - createdWidget = dockArea; - return true; - } - - bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - bool result = true; - while (stateReader.readNextStartElement()) { - if (stateReader.name() == QLatin1String("splitter")) { - result = restoreSplitter(stateReader, createdWidget, testing); - qCInfo(adsLog) << "Splitter"; - } else if (stateReader.name() == QLatin1String("area")) { - result = restoreDockArea(stateReader, createdWidget, testing); - qCInfo(adsLog) << "DockAreaWidget"; - } else { - stateReader.skipCurrentElement(); - qCInfo(adsLog) << "Unknown element" << stateReader.name(); - } - } - - return result; - } - - DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, - DockWidget *dockWidget) - { - DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); - newDockArea->addDockWidget(dockWidget); - addDockArea(newDockArea, area); - newDockArea->updateTitleBarVisibility(); - m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; - return newDockArea; - } - - void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area) - { - auto insertParam = internal::dockAreaInsertParameters(area); - // As long as we have only one dock area in the splitter we can adjust its orientation - if (m_dockAreas.count() <= 1) - m_rootSplitter->setOrientation(insertParam.orientation()); - - QSplitter *splitter = m_rootSplitter; - if (splitter->orientation() == insertParam.orientation()) { - insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); - if (splitter->isHidden()) - splitter->show(); - - } else { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - if (insertParam.append()) { - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - newSplitter->addWidget(newDockArea); - delete layoutItem; - } else { - newSplitter->addWidget(newDockArea); - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - delete layoutItem; - } - m_rootSplitter = newSplitter; - } - - addDockAreasToList({newDockArea}); - } - - void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const - { -#if defined(QT_DEBUG) - QSplitter *splitter = qobject_cast(widget); - QByteArray buf; - buf.fill(' ', level * 4); - if (splitter) { -#ifdef ADS_DEBUG_PRINT - qDebug("%sSplitter %s v: %s c: %s", - buf.data(), - (splitter->orientation() == Qt::Vertical) ? "--" : "|", - splitter->isHidden() ? " " : "v", - QString::number(splitter->count()).toStdString().c_str()); - std::cout << buf.data() << "Splitter " - << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " - << (splitter->isHidden() ? " " : "v") << " " - << QString::number(splitter->count()).toStdString() << std::endl; -#endif - for (int i = 0; i < splitter->count(); ++i) - dumpRecursive(level + 1, splitter->widget(i)); - } else { - DockAreaWidget *dockArea = qobject_cast(widget); - if (!dockArea) - return; - -#ifdef ADS_DEBUG_PRINT - qDebug("%sDockArea", buf.data()); - std::cout << buf.data() << (dockArea->isHidden() ? " " : "v") - << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea" - << std::endl; - buf.fill(' ', (level + 1) * 4); - for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) { - std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " "); - DockWidget *dockWidget = dockArea->dockWidget(i); - std::cout << (dockWidget->isHidden() ? " " : "v"); - std::cout << (dockWidget->isClosed() ? "c" : " ") << " "; - std::cout << dockWidget->windowTitle().toStdString() << std::endl; - } -#endif - } -#else - Q_UNUSED(level) - Q_UNUSED(widget) -#endif - } - - DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *targetDockArea) - { - if (CenterDockWidgetArea == area) { - targetDockArea->addDockWidget(dockWidget); - targetDockArea->updateTitleBarVisibility(); - return targetDockArea; - } - - DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); - newDockArea->addDockWidget(dockWidget); - auto insertParam = internal::dockAreaInsertParameters(area); - - QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); - int index = targetAreaSplitter->indexOf(targetDockArea); - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; - targetAreaSplitter->insertWidget(index + insertParam.insertOffset(), newDockArea); - // do nothing, if flag is not enabled - if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) - adjustSplitterSizesOnInsertion(targetAreaSplitter); - } else { - qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; - auto targetAreaSizes = targetAreaSplitter->sizes(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - newSplitter->addWidget(targetDockArea); - insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); - targetAreaSplitter->insertWidget(index, newSplitter); - if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) { - targetAreaSplitter->setSizes(targetAreaSizes); - adjustSplitterSizesOnInsertion(newSplitter); - } - } - - appendDockAreas({newDockArea}); - emitDockAreasAdded(); - return newDockArea; - } - - DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent) - : QFrame(parent) - , d(new DockContainerWidgetPrivate(this)) - { - d->m_dockManager = dockManager; - d->m_isFloating = floatingWidget() != nullptr; - - d->m_layout = new QGridLayout(); - d->m_layout->setContentsMargins(0, 1, 0, 1); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - - // The function d->createSplitter() accesses the config flags from dock - // manager which in turn requires a properly constructed dock manager. - // If this dock container is the dock manager, then it is not properly - // constructed yet because this base class constructor is called before - // the constructor of the DockManager private class - if (dockManager != this) { - d->m_dockManager->registerDockContainer(this); - createRootSplitter(); - } - } - - DockContainerWidget::~DockContainerWidget() - { - if (d->m_dockManager) - d->m_dockManager->removeDockContainer(this); - - delete d; - } - - DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) - { - DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea; + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); if (oldDockArea) - oldDockArea->removeDockWidget(dockWidget); + oldDockArea->removeDockWidget(droppedDockWidget); - dockWidget->setDockManager(d->m_dockManager); - if (dockAreaWidget) - return d->addDockWidgetToDockArea(area, dockWidget, dockAreaWidget); - else - return d->addDockWidgetToContainer(area, dockWidget); + newDockArea->addDockWidget(droppedDockWidget); + } else { + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; } - void DockContainerWidget::removeDockWidget(DockWidget * dockWidget) - { - DockAreaWidget *area = dockWidget->dockAreaWidget(); - if (area) - area->removeDockWidget(dockWidget); + auto insertParam = internal::dockAreaInsertParameters(area); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); + const int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto sizes = targetAreaSplitter->sizes(); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea); + updateSplitterHandles(targetAreaSplitter); + const int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + sizes[areaIndex] = size; + sizes.insert(areaIndex, size); + } else { + const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + updateSplitterHandles(newSplitter); + const int size = targetAreaSize / 2; + newSplitter->setSizes({size, size}); + targetAreaSplitter->insertWidget(areaIndex, newSplitter); + updateSplitterHandles(targetAreaSplitter); + } + targetAreaSplitter->setSizes(sizes); + + addDockAreasToList({newDockArea}); +} + +void DockContainerWidgetPrivate::updateSplitterHandles(QSplitter *splitter) +{ + if (!m_dockManager->centralWidget() || !splitter) + return; + + for (int i = 0; i < splitter->count(); ++i) + splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0); +} + +bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget *widget) +{ + if (!m_dockManager->centralWidget()) + return true; + + auto area = qobject_cast(widget); + if (area) + return area->isCentralWidgetArea(); + + auto innerSplitter = qobject_cast(widget); + if (innerSplitter) + return innerSplitter->isResizingWithContainer(); + + return false; +} + +void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area) +{ + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea = nullptr; + + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea) + oldDockArea->removeDockWidget(droppedDockWidget); + + newDockArea->addDockWidget(droppedDockWidget); + } else { + // We check, if we insert the dropped widget into the same place that + // it already has and do nothing, if it is the same place. It would + // also work without this check, but it looks nicer with the check + // because there will be no layout updates + auto splitter = internal::findParent(droppedDockArea); + auto insertParam = internal::dockAreaInsertParameters(area); + if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) { + if (insertParam.append() && splitter->lastWidget() == droppedDockArea) + return; + else if (!insertParam.append() && splitter->firstWidget() == droppedDockArea) + return; + } + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; } - unsigned int DockContainerWidget::zOrderIndex() const { return d->m_zOrderIndex; } + addDockArea(newDockArea, area); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; +} - bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const - { - return this->zOrderIndex() > other->zOrderIndex(); +void DockContainerWidgetPrivate::addDockAreasToList(const QList newDockAreas) +{ + const int countBefore = m_dockAreas.count(); + const int newAreaCount = newDockAreas.count(); + appendDockAreas(newDockAreas); + // If the user dropped a floating widget that contains only one single + // visible dock area, then its title bar button TitleBarButtonUndock is + // likely hidden. We need to ensure, that it is visible + for (auto dockArea : newDockAreas) { + dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); + dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); } - bool DockContainerWidget::event(QEvent *event) - { - bool result = QWidget::event(event); - if (event->type() == QEvent::WindowActivate) - d->m_zOrderIndex = ++zOrderCounter; - else if (event->type() == QEvent::Show && !d->m_zOrderIndex) - d->m_zOrderIndex = ++zOrderCounter; + // We need to ensure, that the dock area title bar is visible. The title bar + // is invisible, if the dock are is a single dock area in a floating widget. + if (1 == countBefore) + m_dockAreas.at(0)->updateTitleBarVisibility(); - return result; + if (1 == newAreaCount) + m_dockAreas.last()->updateTitleBarVisibility(); + + emitDockAreasAdded(); +} + +void DockContainerWidgetPrivate::appendDockAreas(const QList newDockAreas) +{ + m_dockAreas.append(newDockAreas); + for (auto dockArea : newDockAreas) { + QObject::connect(dockArea, + &DockAreaWidget::viewToggled, + q, + std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, + this, + dockArea, + std::placeholders::_1)); + } +} + +void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget) +{ + QSplitter *splitter = qobject_cast(widget); + if (splitter) { + stream.writeStartElement("splitter"); + stream.writeAttribute("orientation", + QVariant::fromValue(splitter->orientation()).toString()); + stream.writeAttribute("count", QString::number(splitter->count())); + qCInfo(adsLog) << "NodeSplitter orientation:" << splitter->orientation() + << "WidgetCount:" << splitter->count(); + for (int i = 0; i < splitter->count(); ++i) + saveChildNodesState(stream, splitter->widget(i)); + + stream.writeStartElement("sizes"); + QStringList sizes; + for (auto size : splitter->sizes()) + sizes.append(QString::number(size)); + + stream.writeCharacters(sizes.join(" ")); + stream.writeEndElement(); // sizes + stream.writeEndElement(); // splitter + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (dockArea) + dockArea->saveState(stream); + } +} + +void DockContainerWidgetPrivate::saveAutoHideWidgetsState(QXmlStreamWriter &s) +{ + for (const auto sideTabBar : m_sideTabBarWidgets.values()) { + if (!sideTabBar->tabCount()) + continue; + + sideTabBar->saveState(s); + } +} + +bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + bool result = true; + while (stateReader.readNextStartElement()) { + if (stateReader.name() == QLatin1String("splitter")) { + result = restoreSplitter(stateReader, createdWidget, testing); + qCInfo(adsLog) << "Splitter"; + } else if (stateReader.name() == QLatin1String("area")) { + result = restoreDockArea(stateReader, createdWidget, testing); + qCInfo(adsLog) << "DockAreaWidget"; + } else if (stateReader.name() == QLatin1String("sideBar")) { + result = restoreSideBar(stateReader, createdWidget, testing); + qCInfo(adsLog) << "SideBar"; + } else { + stateReader.skipCurrentElement(); + qCInfo(adsLog) << "Unknown element"; + } } - void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area) - { - DockContainerWidget *container = dockAreaWidget->dockContainer(); - if (container && container != this) - container->removeDockArea(dockAreaWidget); + return result; +} - d->addDockArea(dockAreaWidget, area); +bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString()); + + // Check if the orientation string is convertable + if (!orientationVar.canConvert()) + return false; + + Qt::Orientation orientation = orientationVar.value(); + + bool ok; + int widgetCount = stateReader.attributes().value("count").toInt(&ok); + if (!ok) + return false; + + qCInfo(adsLog) << "Restore NodeSplitter Orientation:" << orientation + << "WidgetCount:" << widgetCount; + QSplitter *splitter = nullptr; + if (!testing) + splitter = createSplitter(orientation); + + bool visible = false; + QList sizes; + while (stateReader.readNextStartElement()) { + QWidget *childNode = nullptr; + bool result = true; + if (stateReader.name() == QLatin1String("splitter")) { + result = restoreSplitter(stateReader, childNode, testing); + } else if (stateReader.name() == QLatin1String("area")) { + result = restoreDockArea(stateReader, childNode, testing); + } else if (stateReader.name() == QLatin1String("sizes")) { + QString size = stateReader.readElementText().trimmed(); + qCInfo(adsLog) << "Size:" << size; + QTextStream textStream(&size); + while (!textStream.atEnd()) { + int value; + textStream >> value; + sizes.append(value); + } + } else { + stateReader.skipCurrentElement(); + } + + if (!result) + return false; + + if (testing || !childNode) + continue; + + qCInfo(adsLog) << "ChildNode isVisible" << childNode->isVisible() << "isVisibleTo" + << childNode->isVisibleTo(splitter); + splitter->addWidget(childNode); + visible |= childNode->isVisibleTo(splitter); } - void DockContainerWidget::removeDockArea(DockAreaWidget *area) - { - qCInfo(adsLog) << Q_FUNC_INFO; - area->disconnect(this); - d->m_dockAreas.removeAll(area); - DockSplitter *splitter = internal::findParent(area); + if (sizes.count() != widgetCount) + return false; - // Remove area from parent splitter and recursively hide tree of parent - // splitters if it has no visible content - area->setParent(nullptr); - internal::hideEmptyParentSplitters(splitter); + if (!testing) { + if (!splitter->count()) { + delete splitter; + splitter = nullptr; + } else { + splitter->setSizes(sizes); + splitter->setVisible(visible); + } + createdWidget = splitter; + } else { + createdWidget = nullptr; + } - // Remove this area from cached areas - const auto &cache = d->m_lastAddedAreaCache; - if (auto p = std::find(cache, cache + sizeof(cache) / sizeof(cache[0]), area)) - d->m_lastAddedAreaCache[std::distance(cache, p)] = nullptr; + return true; +} - // If splitter has more than 1 widgets, we are finished and can leave - if (splitter->count() > 1) { +bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + DockAreaWidget *dockArea = nullptr; + auto result = DockAreaWidget::restoreState(stateReader, dockArea, testing, q); + if (result && dockArea) + appendDockAreas({dockArea}); + + createdWidget = dockArea; + return result; +} + +bool DockContainerWidgetPrivate::restoreSideBar(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + Q_UNUSED(createdWidget) + // Simply ignore side bar auto hide widgets from saved state if auto hide support is disabled + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return true; + + bool ok; + auto area = (SideBarLocation) stateReader.attributes().value("area").toInt(&ok); + if (!ok) + return false; + + while (stateReader.readNextStartElement()) { + if (stateReader.name() != QLatin1String("widget")) + continue; + + auto name = stateReader.attributes().value("name"); + if (name.isEmpty()) + return false; + + bool ok; + bool closed = stateReader.attributes().value("closed").toInt(&ok); + if (!ok) + return false; + + int size = stateReader.attributes().value("size").toInt(&ok); + if (!ok) + return false; + + stateReader.skipCurrentElement(); + DockWidget *dockWidget = m_dockManager->findDockWidget(name.toString()); + if (!dockWidget || testing) + continue; + + auto sideBar = q->sideTabBar(area); + AutoHideDockContainer *autoHideContainer; + if (dockWidget->isAutoHide()) { + autoHideContainer = dockWidget->autoHideDockContainer(); + if (autoHideContainer->sideBar() != sideBar) + sideBar->addAutoHideWidget(autoHideContainer); + } else { + autoHideContainer = sideBar->insertDockWidget(-1, dockWidget); + } + autoHideContainer->setSize(size); + dockWidget->setProperty(internal::g_closedProperty, closed); + dockWidget->setProperty(internal::g_dirtyProperty, false); + } + + return true; +} + +DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget) +{ + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + addDockArea(newDockArea, area); + newDockArea->updateTitleBarVisibility(); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; + return newDockArea; +} + +void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area) +{ + auto insertParam = internal::dockAreaInsertParameters(area); + // As long as we have only one dock area in the splitter we can adjust its orientation + if (m_dockAreas.count() <= 1) + m_rootSplitter->setOrientation(insertParam.orientation()); + + QSplitter *splitter = m_rootSplitter; + if (splitter->orientation() == insertParam.orientation()) { + insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); + updateSplitterHandles(splitter); + if (splitter->isHidden()) + splitter->show(); + + } else { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + if (insertParam.append()) { + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + newSplitter->addWidget(newDockArea); + updateSplitterHandles(newSplitter); + delete layoutItem; + } else { + newSplitter->addWidget(newDockArea); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + updateSplitterHandles(newSplitter); + delete layoutItem; + } + m_rootSplitter = newSplitter; + } + + addDockAreasToList({newDockArea}); +} + +void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const +{ +#if defined(QT_DEBUG) + QSplitter *splitter = qobject_cast(widget); + QByteArray buf; + buf.fill(' ', level * 4); + if (splitter) { +#ifdef ADS_DEBUG_PRINT + qDebug("%sSplitter %s v: %s c: %s", + buf.data(), + (splitter->orientation() == Qt::Vertical) ? "--" : "|", + splitter->isHidden() ? " " : "v", + QString::number(splitter->count()).toStdString().c_str()); + std::cout << buf.data() << "Splitter " + << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " + << (splitter->isHidden() ? " " : "v") << " " + << QString::number(splitter->count()).toStdString() << std::endl; +#endif + for (int i = 0; i < splitter->count(); ++i) + dumpRecursive(level + 1, splitter->widget(i)); + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (!dockArea) + return; + +#ifdef ADS_DEBUG_PRINT + qDebug("%sDockArea", buf.data()); + std::cout << buf.data() << (dockArea->isHidden() ? " " : "v") + << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea" << std::endl; + buf.fill(' ', (level + 1) * 4); + for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) { + std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " "); + DockWidget *dockWidget = dockArea->dockWidget(i); + std::cout << (dockWidget->isHidden() ? " " : "v"); + std::cout << (dockWidget->isClosed() ? "c" : " ") << " "; + std::cout << dockWidget->windowTitle().toStdString() << std::endl; + } +#endif + } +#else + Q_UNUSED(level) + Q_UNUSED(widget) +#endif +} + +DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *targetDockArea, + int index) +{ + if (CenterDockWidgetArea == area) { + targetDockArea->insertDockWidget(index, dockWidget); + targetDockArea->updateTitleBarVisibility(); + return targetDockArea; + } + + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + auto insertParam = internal::dockAreaInsertParameters(area); + + QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); + int targetIndex = targetAreaSplitter->indexOf(targetDockArea); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; + targetAreaSplitter->insertWidget(targetIndex + insertParam.insertOffset(), newDockArea); + // do nothing, if flag is not enabled + if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) + adjustSplitterSizesOnInsertion(targetAreaSplitter); + } else { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; + auto targetAreaSizes = targetAreaSplitter->sizes(); + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetDockArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + updateSplitterHandles(newSplitter); + targetAreaSplitter->insertWidget(targetIndex, newSplitter); + updateSplitterHandles(targetAreaSplitter); + if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) { + targetAreaSplitter->setSizes(targetAreaSizes); + adjustSplitterSizesOnInsertion(newSplitter); + } + } + + appendDockAreas({newDockArea}); + return newDockArea; +} + +DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent) + : QFrame(parent) + , d(new DockContainerWidgetPrivate(this)) +{ + d->m_dockManager = dockManager; + d->m_isFloating = floatingWidget() != nullptr; + + d->m_layout = new QGridLayout(); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + d->m_layout->setColumnStretch(1, 1); + d->m_layout->setRowStretch(1, 1); + setLayout(d->m_layout); + + // The function d->createSplitter() accesses the config flags from dock manager which in turn + // requires a properly constructed dock manager. If this dock container is the dock manager, + // then it is not properly constructed yet because this base class constructor is called before + // the constructor of the DockManager private class + if (dockManager != this) { + d->m_dockManager->registerDockContainer(this); + createRootSplitter(); + createSideTabBarWidgets(); + } +} + +DockContainerWidget::~DockContainerWidget() +{ + if (d->m_dockManager) + d->m_dockManager->removeDockContainer(this); + + delete d; +} + +DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget, + int index) +{ + auto currentTopLevelDockWIdget = topLevelDockWidget(); + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + if (oldDockArea) + oldDockArea->removeDockWidget(dockWidget); + + dockWidget->setDockManager(d->m_dockManager); + DockAreaWidget *dockArea; + if (dockAreaWidget) + dockArea = d->addDockWidgetToDockArea(area, dockWidget, dockAreaWidget, index); + else + dockArea = d->addDockWidgetToContainer(area, dockWidget); + + if (currentTopLevelDockWIdget) { + auto newTopLevelDockWidget = topLevelDockWidget(); + // If the container contained only one visible dock widget, we need to emit a top level + // event for this widget because it is not the one and only visible docked widget anymore. + if (!newTopLevelDockWidget) + DockWidget::emitTopLevelEventForWidget(currentTopLevelDockWIdget, false); + } + + return dockArea; +} + +AutoHideDockContainer *DockContainerWidget::createAndSetupAutoHideContainer(SideBarLocation area, + DockWidget *dockWidget) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + Q_ASSERT_X(false, + "CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer", + "Requested area does not exist in config"); + return nullptr; + } + if (d->m_dockManager != dockWidget->dockManager()) { + dockWidget->setDockManager( + d->m_dockManager); // Auto hide Dock Container needs a valid dock manager + } + + return sideTabBar(area)->insertDockWidget(-1, dockWidget); +} + +void DockContainerWidget::removeDockWidget(DockWidget *dockWidget) +{ + DockAreaWidget *area = dockWidget->dockAreaWidget(); + if (area) + area->removeDockWidget(dockWidget); +} + +unsigned int DockContainerWidget::zOrderIndex() const +{ + return d->m_zOrderIndex; +} + +bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const +{ + return this->zOrderIndex() > other->zOrderIndex(); +} + +bool DockContainerWidget::event(QEvent *event) +{ + bool result = QWidget::event(event); + if (event->type() == QEvent::WindowActivate) + d->m_zOrderIndex = ++g_zOrderCounter; + else if (event->type() == QEvent::Show && !d->m_zOrderIndex) + d->m_zOrderIndex = ++g_zOrderCounter; + + return result; +} + +QList DockContainerWidget::autoHideWidgets() const +{ + return d->m_autoHideWidgets; +} + +void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area) +{ + DockContainerWidget *container = dockAreaWidget->dockContainer(); + if (container && container != this) + container->removeDockArea(dockAreaWidget); + + d->addDockArea(dockAreaWidget, area); +} + +void DockContainerWidget::removeDockArea(DockAreaWidget *area) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + // If it is an auto hide area, then there is nothing much to do + if (area->isAutoHide()) { + area->setAutoHideDockContainer(nullptr); + return; + } + + area->disconnect(this); + d->m_dockAreas.removeAll(area); + DockSplitter *splitter = internal::findParent(area); + + // Remove area from parent splitter and recursively hide tree of parent + // splitters if it has no visible content + area->setParent(nullptr); + internal::hideEmptyParentSplitters(splitter); + + // Remove this area from cached areas + auto p = std::find(std::begin(d->m_lastAddedAreaCache), std::end(d->m_lastAddedAreaCache), area); + if (p != std::end(d->m_lastAddedAreaCache)) + *p = nullptr; + + // If splitter has more than 1 widget, we are finished and can leave + if (splitter->count() > 1) { + updateSplitterHandles(splitter); + emitAndExit(); + return; + } + + // If this is the RootSplitter we need to remove empty splitters to + // avoid too many empty splitters + if (splitter == d->m_rootSplitter) { + qCInfo(adsLog) << "Removed from RootSplitter"; + // If splitter is empty, we are finished + if (!splitter->count()) { + splitter->hide(); + updateSplitterHandles(splitter); emitAndExit(); return; } - // If this is the RootSplitter we need to remove empty splitters to - // avoid too many empty splitters - if (splitter == d->m_rootSplitter) { - qCInfo(adsLog) << "Removed from RootSplitter"; - // If splitter is empty, we are finished - if (!splitter->count()) { - splitter->hide(); - emitAndExit(); - return; - } - - QWidget *widget = splitter->widget(0); - QSplitter *childSplitter = qobject_cast(widget); - // If the one and only content widget of the splitter is not a splitter - // then we are finished - if (!childSplitter) { - emitAndExit(); - return; - } - - // We replace the superfluous RootSplitter with the ChildSplitter - childSplitter->setParent(nullptr); - QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter); - d->m_rootSplitter = childSplitter; - delete layoutItem; - qCInfo(adsLog) << "RootSplitter replaced by child splitter"; - } else if (splitter->count() == 1) { - qCInfo(adsLog) << "Replacing splitter with content"; - QSplitter *parentSplitter = internal::findParent(splitter); - auto sizes = parentSplitter->sizes(); - QWidget *widget = splitter->widget(0); - widget->setParent(this); - internal::replaceSplitterWidget(parentSplitter, splitter, widget); - parentSplitter->setSizes(sizes); + QWidget *widget = splitter->widget(0); + QSplitter *childSplitter = qobject_cast(widget); + // If the one and only content widget of the splitter is not a splitter + // then we are finished + if (!childSplitter) { + updateSplitterHandles(splitter); + emitAndExit(); + return; } - delete splitter; + // We replace the superfluous RootSplitter with the ChildSplitter + childSplitter->setParent(nullptr); + QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter); + d->m_rootSplitter = childSplitter; + delete layoutItem; + qCInfo(adsLog) << "RootSplitter replaced by child splitter"; + } else if (splitter->count() == 1) { + qCInfo(adsLog) << "Replacing splitter with content"; + QSplitter *parentSplitter = internal::findParent(splitter); + auto sizes = parentSplitter->sizes(); + QWidget *widget = splitter->widget(0); + widget->setParent(this); + internal::replaceSplitterWidget(parentSplitter, splitter, widget); + parentSplitter->setSizes(sizes); } - void DockContainerWidget::emitAndExit() const - { - DockWidget *topLevelWidget = topLevelDockWidget(); + delete splitter; + splitter = nullptr; - // Updated the title bar visibility of the dock widget if there is only - // one single visible dock widget - DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); - dumpLayout(); - d->emitDockAreasRemoved(); + // TODO Check goto... + updateSplitterHandles(splitter); + emitAndExit(); +} + +void DockContainerWidget::emitAndExit() const +{ + DockWidget *topLevelWidget = topLevelDockWidget(); + + // Updated the title bar visibility of the dock widget if there is only + // one single visible dock widget + DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); + dumpLayout(); + d->emitDockAreasRemoved(); +} + +DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const +{ + for (auto dockArea : std::as_const(d->m_dockAreas)) { + if (dockArea->isVisible() + && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition))) + return dockArea; } - DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const - { - for (auto dockArea : std::as_const(d->m_dockAreas)) { - if (dockArea->isVisible() - && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition))) - return dockArea; + return nullptr; +} + +DockAreaWidget *DockContainerWidget::dockArea(int index) const +{ + return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr; +} + +bool DockContainerWidget::isFloating() const +{ + return d->m_isFloating; +} + +int DockContainerWidget::dockAreaCount() const +{ + return d->m_dockAreas.count(); +} + +int DockContainerWidget::visibleDockAreaCount() const +{ + int result = 0; + for (auto dockArea : std::as_const(d->m_dockAreas)) + result += dockArea->isHidden() ? 0 : 1; + + return result; + + // TODO Cache or precalculate this to speed it up because it is used during + // movement of floating widget + //return d->visibleDockAreaCount(); +} + +void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget, + const QPoint &targetPosition) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget(); + DockWidget *singleDockWidget = topLevelDockWidget(); + DockAreaWidget *dockArea = dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); + bool dropped = false; + + if (dockArea) { + auto dropOverlay = d->m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) + dropArea = InvalidDockWidgetArea; + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + d->dropIntoSection(floatingWidget, dockArea, dropArea); + dropped = true; } - - return nullptr; } - DockAreaWidget *DockContainerWidget::dockArea(int index) const - { - return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr; - } - - bool DockContainerWidget::isFloating() const { return d->m_isFloating; } - - int DockContainerWidget::dockAreaCount() const { return d->m_dockAreas.count(); } - - int DockContainerWidget::visibleDockAreaCount() const - { - int result = 0; - for (auto dockArea : std::as_const(d->m_dockAreas)) - result += dockArea->isHidden() ? 0 : 1; - - return result; - - // TODO Cache or precalculate this to speed it up because it is used during - // movement of floating widget - //return d->visibleDockAreaCount(); - } - - void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget, - const QPoint &targetPosition) - { - qCInfo(adsLog) << Q_FUNC_INFO; - DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget(); - DockWidget *singleDockWidget = topLevelDockWidget(); - DockAreaWidget *dockArea = dockAreaAt(targetPosition); - auto dropArea = InvalidDockWidgetArea; - auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); - bool dropped = false; - - if (dockArea) { - auto dropOverlay = d->m_dockManager->dockAreaOverlay(); - dropOverlay->setAllowedAreas(dockArea->allowedAreas()); - dropArea = dropOverlay->showOverlay(dockArea); - if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) - dropArea = InvalidDockWidgetArea; - - if (dropArea != InvalidDockWidgetArea) { - qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; - d->dropIntoSection(floatingWidget, dockArea, dropArea); - dropped = true; - } + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) { + d->dropIntoContainer(floatingWidget, dropArea); + dropped = true; } - - // mouse is over container - if (InvalidDockWidgetArea == dropArea) { - dropArea = containerDropArea; - qCInfo(adsLog) << "Container Drop Content: " << dropArea; - if (dropArea != InvalidDockWidgetArea) { - d->dropIntoContainer(floatingWidget, dropArea); - dropped = true; - } - } - - if (dropped) { - floatingWidget->deleteLater(); - - // If we dropped a floating widget with only one single dock widget, then we - // drop a top level widget that changes from floating to docked now - DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); - - // If there was a top level widget before the drop, then it is not top - // level widget anymore - DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); - } - window()->activateWindow(); - if (singleDroppedDockWidget) - d->m_dockManager->notifyWidgetOrAreaRelocation(singleDroppedDockWidget); - - d->m_dockManager->notifyFloatingWidgetDrop(floatingWidget); } - void DockContainerWidget::dropWidget(QWidget *widget, DockWidgetArea dropArea, DockAreaWidget *targetAreaWidget) - { - DockWidget *singleDockWidget = topLevelDockWidget(); - if (targetAreaWidget) - d->moveToNewSection(widget, targetAreaWidget, dropArea); - else - d->moveToContainer(widget, dropArea); + // Remove the auto hide widgets from the FloatingWidget and insert them into this widget + for (auto autohideWidget : floatingWidget->dockContainer()->autoHideWidgets()) { + auto sideBar = sideTabBar(autohideWidget->sideBarLocation()); + sideBar->addAutoHideWidget(autohideWidget); + } + + if (dropped) { + // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 + floatingWidget->hideAndDeleteLater(); + + // If we dropped a floating widget with only one single dock widget, then we + // drop a top level widget that changes from floating to docked now + DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); // If there was a top level widget before the drop, then it is not top // level widget anymore DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); - DockWidget *dockWidget = qobject_cast(widget); - if (!dockWidget) - { - DockAreaWidget *dockArea = qobject_cast(widget); - auto openDockWidgets = dockArea->openedDockWidgets(); - if (openDockWidgets.count() == 1) - dockWidget = openDockWidgets[0]; - } - - window()->activateWindow(); - d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); } - QList DockContainerWidget::openedDockAreas() const - { - QList result; - for (auto dockArea : std::as_const(d->m_dockAreas)) { - if (!dockArea->isHidden()) - result.append(dockArea); - } + window()->activateWindow(); + if (singleDroppedDockWidget) + d->m_dockManager->notifyWidgetOrAreaRelocation(singleDroppedDockWidget); - return result; + d->m_dockManager->notifyFloatingWidgetDrop(floatingWidget); +} + +void DockContainerWidget::dropWidget(QWidget *widget, + DockWidgetArea dropArea, + DockAreaWidget *targetAreaWidget) +{ + DockWidget *singleDockWidget = topLevelDockWidget(); + if (targetAreaWidget) + d->moveToNewSection(widget, targetAreaWidget, dropArea); + else + d->moveToContainer(widget, dropArea); + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); + + window()->activateWindow(); + d->m_dockManager->notifyWidgetOrAreaRelocation(widget); +} + +QList DockContainerWidget::openedDockAreas() const +{ + QList result; + for (auto dockArea : std::as_const(d->m_dockAreas)) { + if (!dockArea->isHidden()) + result.append(dockArea); } - void DockContainerWidget::saveState(QXmlStreamWriter &stream) const - { - qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating(); + return result; +} - stream.writeStartElement("container"); - stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString()); - if (isFloating()) { - FloatingDockContainer *floatingDockContainer = floatingWidget(); - QByteArray geometry = floatingDockContainer->saveGeometry(); - stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64())); - } - d->saveChildNodesState(stream, d->m_rootSplitter); - stream.writeEndElement(); +QList DockContainerWidget::openedDockWidgets() const +{ + QList dockWidgetList; + for (auto dockArea : d->m_dockAreas) { + if (!dockArea->isHidden()) + dockWidgetList.append(dockArea->openedDockWidgets()); } + return dockWidgetList; +} - bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing) - { - QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString()); - if (!floatingVar.canConvert()) - return false; - - bool isFloating = floatingVar.value(); - qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating; - - QWidget *newRootSplitter{}; - if (!testing) { - d->m_visibleDockAreaCount = -1; // invalidate the dock area count - d->m_dockAreas.clear(); - std::fill(std::begin(d->m_lastAddedAreaCache), - std::end(d->m_lastAddedAreaCache), - nullptr); - } - - if (isFloating) { - qCInfo(adsLog) << "Restore floating widget"; - if (!stateReader.readNextStartElement() || stateReader.name() != QLatin1String("geometry")) - return false; - - QByteArray geometryString = stateReader - .readElementText( - DockingStateReader::ErrorOnUnexpectedElement) - .toLocal8Bit(); - QByteArray geometry = QByteArray::fromBase64(geometryString); - if (geometry.isEmpty()) - return false; - - if (!testing) { - FloatingDockContainer *floatingDockContainer = floatingWidget(); - floatingDockContainer->restoreGeometry(geometry); - } - } - - if (!d->restoreChildNodes(stateReader, newRootSplitter, testing)) - return false; - - if (testing) +bool DockContainerWidget::hasOpenDockAreas() const +{ + for (auto dockArea : d->m_dockAreas) { + if (!dockArea->isHidden()) return true; - - // If the root splitter is empty, rostoreChildNodes returns a 0 pointer - // and we need to create a new empty root splitter - if (!newRootSplitter) - newRootSplitter = d->createSplitter(Qt::Horizontal); - - d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); - QSplitter *oldRoot = d->m_rootSplitter; - d->m_rootSplitter = qobject_cast(newRootSplitter); - oldRoot->deleteLater(); - - return true; } - QSplitter *DockContainerWidget::rootSplitter() const { return d->m_rootSplitter; } + return false; +} - void DockContainerWidget::createRootSplitter() - { - if (d->m_rootSplitter) - return; +void DockContainerWidget::saveState(QXmlStreamWriter &stream) const +{ + qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating(); - d->m_rootSplitter = d->createSplitter(Qt::Horizontal); - d->m_layout->addWidget(d->m_rootSplitter); + stream.writeStartElement("container"); + stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString()); + if (isFloating()) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + QByteArray geometry = floatingDockContainer->saveGeometry(); + stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64())); + } + d->saveChildNodesState(stream, d->m_rootSplitter); + d->saveAutoHideWidgetsState(stream); + stream.writeEndElement(); +} + +bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing) +{ + QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString()); + if (!floatingVar.canConvert()) + return false; + + bool isFloating = floatingVar.value(); + qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating; + + QWidget *newRootSplitter{}; + if (!testing) { + d->m_visibleDockAreaCount = -1; // invalidate the dock area count + d->m_dockAreas.clear(); + std::fill(std::begin(d->m_lastAddedAreaCache), std::end(d->m_lastAddedAreaCache), nullptr); } - void DockContainerWidget::dumpLayout() const - { -#if (ADS_DEBUG_LEVEL > 0) - qDebug("\n\nDumping layout --------------------------"); - std::cout << "\n\nDumping layout --------------------------" << std::endl; - d->dumpRecursive(0, d->m_rootSplitter); - qDebug("--------------------------\n\n"); - std::cout << "--------------------------\n\n" << std::endl; -#endif - } - - DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const - { - return d->m_lastAddedAreaCache[areaIdToIndex(area)]; - } - - bool DockContainerWidget::hasTopLevelDockWidget() const - { - auto dockAreas = openedDockAreas(); - if (dockAreas.count() != 1) + if (isFloating) { + qCInfo(adsLog) << "Restore floating widget"; + if (!stateReader.readNextStartElement() || stateReader.name() != QLatin1String("geometry")) return false; - return dockAreas[0]->openDockWidgetsCount() == 1; - } + QByteArray geometryString + = stateReader.readElementText(DockingStateReader::ErrorOnUnexpectedElement).toLocal8Bit(); + QByteArray geometry = QByteArray::fromBase64(geometryString); + if (geometry.isEmpty()) + return false; - DockWidget *DockContainerWidget::topLevelDockWidget() const - { - auto dockArea = topLevelDockArea(); - if (!dockArea) - return nullptr; - - auto dockWidgets = dockArea->openedDockWidgets(); - if (dockWidgets.count() != 1) - return nullptr; - - return dockWidgets[0]; - } - - DockAreaWidget *DockContainerWidget::topLevelDockArea() const - { - auto dockAreas = openedDockAreas(); - if (dockAreas.count() != 1) - return nullptr; - - return dockAreas[0]; - } - - QList DockContainerWidget::dockWidgets() const - { - QList result; - for (const auto dockArea : std::as_const(d->m_dockAreas)) - result.append(dockArea->dockWidgets()); - - return result; - } - - DockWidget::DockWidgetFeatures DockContainerWidget::features() const - { - DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); - for (const auto dockArea : std::as_const(d->m_dockAreas)) - features &= dockArea->features(); - - return features; - } - - FloatingDockContainer *DockContainerWidget::floatingWidget() const - { - return internal::findParent(this); - } - - void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea) - { - for (const auto dockArea : std::as_const(d->m_dockAreas)) { - if (dockArea == keepOpenArea) - continue; - - if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) - continue; - - // We do not close areas with widgets with custom close handling - if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) - continue; - - dockArea->closeArea(); + if (!testing) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + if (floatingDockContainer) + floatingDockContainer->restoreGeometry(geometry); } } + if (!d->restoreChildNodes(stateReader, newRootSplitter, testing)) + return false; + + if (testing) + return true; + + // If the root splitter is empty, restoreChildNodes returns a nullptr + // and we need to create a new empty root splitter + if (!newRootSplitter) + newRootSplitter = d->createSplitter(Qt::Horizontal); + + d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); + QSplitter *oldRoot = d->m_rootSplitter; + d->m_rootSplitter = qobject_cast(newRootSplitter); + oldRoot->deleteLater(); + + return true; +} + +QSplitter *DockContainerWidget::rootSplitter() const +{ + return d->m_rootSplitter; +} + +void DockContainerWidget::createRootSplitter() +{ + if (d->m_rootSplitter) + return; + + d->m_rootSplitter = d->createSplitter(Qt::Horizontal); + // Add it to the center - the 0 and 2 indices are used for the SideTabBar widgets + d->m_layout->addWidget(d->m_rootSplitter, 1, 1); +} + +void DockContainerWidget::createSideTabBarWidgets() +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; + + { + auto area = SideBarLocation::SideBarLeft; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 1, 0); + } + + { + auto area = SideBarLocation::SideBarRight; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 1, 2); + } + + { + auto area = SideBarLocation::SideBarBottom; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 2, 1); + } + + { + auto area = SideBarLocation::SideBarTop; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 0, 1); + } +} + +void DockContainerWidget::dumpLayout() const +{ +#if (ADS_DEBUG_LEVEL > 0) + qDebug("\n\nDumping layout --------------------------"); + std::cout << "\n\nDumping layout --------------------------" << std::endl; + d->dumpRecursive(0, d->m_rootSplitter); + qDebug("--------------------------\n\n"); + std::cout << "--------------------------\n\n" << std::endl; +#endif +} + +DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const +{ + return d->m_lastAddedAreaCache[areaIdToIndex(area)]; +} + +bool DockContainerWidget::hasTopLevelDockWidget() const +{ + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) + return false; + + return dockAreas[0]->openDockWidgetsCount() == 1; +} + +DockWidget *DockContainerWidget::topLevelDockWidget() const +{ + auto dockArea = topLevelDockArea(); + if (!dockArea) + return nullptr; + + auto dockWidgets = dockArea->openedDockWidgets(); + if (dockWidgets.count() != 1) + return nullptr; + + return dockWidgets[0]; +} + +DockAreaWidget *DockContainerWidget::topLevelDockArea() const +{ + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) + return nullptr; + + return dockAreas[0]; +} + +QList DockContainerWidget::dockWidgets() const +{ + QList result; + for (const auto dockArea : std::as_const(d->m_dockAreas)) + result.append(dockArea->dockWidgets()); + + return result; +} + +void DockContainerWidget::updateSplitterHandles(QSplitter *splitter) +{ + d->updateSplitterHandles(splitter); +} + +void DockContainerWidget::registerAutoHideWidget(AutoHideDockContainer *autohideWidget) +{ + d->m_autoHideWidgets.append(autohideWidget); + Q_EMIT autoHideWidgetCreated(autohideWidget); + qCInfo(adsLog) << "d->AutoHideWidgets.count() " << d->m_autoHideWidgets.count(); +} + +void DockContainerWidget::removeAutoHideWidget(AutoHideDockContainer *autohideWidget) +{ + d->m_autoHideWidgets.removeAll(autohideWidget); +} + +DockWidget::DockWidgetFeatures DockContainerWidget::features() const +{ + DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); + for (const auto dockArea : std::as_const(d->m_dockAreas)) + features &= dockArea->features(); + + return features; +} + +FloatingDockContainer *DockContainerWidget::floatingWidget() const +{ + return internal::findParent(this); +} + +void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea) +{ + for (const auto dockArea : std::as_const(d->m_dockAreas)) { + if (dockArea == keepOpenArea) + continue; + + if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) + continue; + + // We do not close areas with widgets with custom close handling + if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) + continue; + + dockArea->closeArea(); + } +} + +AutoHideSideBar *DockContainerWidget::sideTabBar(SideBarLocation area) const +{ + return d->m_sideTabBarWidgets[area]; +} + +QRect DockContainerWidget::contentRect() const +{ + if (!d->m_rootSplitter) + return QRect(); + + return d->m_rootSplitter->geometry(); +} + +QRect DockContainerWidget::contentRectGlobal() const +{ + if (!d->m_rootSplitter) + return QRect(); + + return internal::globalGeometry(d->m_rootSplitter); +} + +DockManager *DockContainerWidget::dockManager() const +{ + return d->m_dockManager; +} + +void DockContainerWidget::handleAutoHideWidgetEvent(QEvent *e, QWidget *w) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideShowOnMouseOver)) + return; + + if (dockManager()->isRestoringState()) + return; + + auto autoHideTab = qobject_cast(w); + if (autoHideTab) { + switch (e->type()) { + case QEvent::Enter: + if (!autoHideTab->dockWidget()->isVisible()) { + d->m_delayedAutoHideTab = autoHideTab; + d->m_delayedAutoHideShow = true; + d->m_delayedAutoHideTimer.start(); + } else { + d->m_delayedAutoHideTimer.stop(); + } + break; + + case QEvent::MouseButtonPress: + d->m_delayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (autoHideTab->dockWidget()->isVisible()) { + d->m_delayedAutoHideTab = autoHideTab; + d->m_delayedAutoHideShow = false; + d->m_delayedAutoHideTimer.start(); + } else { + d->m_delayedAutoHideTimer.stop(); + } + break; + + default: + break; + } + return; + } + + auto autoHideContainer = qobject_cast(w); + if (autoHideContainer) { + switch (e->type()) { + case QEvent::Enter: + case QEvent::Hide: + d->m_delayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (autoHideContainer->isVisible()) { + d->m_delayedAutoHideTab = autoHideContainer->autoHideTab(); + d->m_delayedAutoHideShow = false; + d->m_delayedAutoHideTimer.start(); + } + break; + + default: + break; + } + return; + } +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.h b/src/libs/advanceddockingsystem/dockcontainerwidget.h index 5020b98bf13..58b9342cf52 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.h +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.h @@ -4,6 +4,7 @@ #pragma once #include "ads_globals.h" +#include "autohidetab.h" #include "dockwidget.h" #include @@ -24,13 +25,16 @@ class FloatingDockContainerPrivate; class FloatingDragPreview; class DockingStateReader; class FloatingDragPreviewPrivate; +class AutoHideSideBar; +class AutoHideTab; +struct AutoHideTabPrivate; +struct AutoHideDockContainerPrivate; +class AutoHideDockContainer; /** - * Container that manages a number of dock areas with single dock widgets - * or tabified dock widgets in each area. - * Each window that support docking has a DockContainerWidget. That means - * the main application window and all floating windows contain a - * DockContainerWidget instance. + * Container that manages a number of dock areas with single dock widgets or tabified dock widgets + * in each area. Each window that support docking has a DockContainerWidget. That means the main + * application window and all floating windows contain a DockContainerWidget instance. */ class ADS_EXPORT DockContainerWidget : public QFrame { @@ -47,6 +51,11 @@ private: friend class DockWidget; friend class FloatingDragPreview; friend class FloatingDragPreviewPrivate; + friend AutoHideDockContainer; + friend AutoHideTab; + friend AutoHideTabPrivate; + friend AutoHideDockContainerPrivate; + friend AutoHideSideBar; protected: /** @@ -59,11 +68,24 @@ protected: */ QSplitter *rootSplitter() const; + /** + * Creates and initializes a dockwidget auto hide container into the given area. Initializing + * inserts the tabs into the side tab widget and hides it + * Returns nullptr if you try and insert into an area where the configuration is not enabled + */ + AutoHideDockContainer *createAndSetupAutoHideContainer(SideBarLocation area, + DockWidget *dockWidget); + /** * Helper function for creation of the root splitter */ void createRootSplitter(); + /** + * Helper function for creation of the side tab bar widgets + */ + void createSideTabBarWidgets(); + /** * Drop floating widget into the container */ @@ -91,7 +113,7 @@ protected: /** * This function replaces the goto construct. Still need to write a good description. */ - void emitAndExit() const; // TODO rename + void emitAndExit() const; /** * Saves the state into the given stream @@ -134,6 +156,28 @@ protected: */ QList dockWidgets() const; + /** + * This function forces the dock container widget to update handles of splitters + * based on resize modes of dock widgets contained in the container. + */ + void updateSplitterHandles(QSplitter *splitter); + + /** + * Registers the given floating widget in the internal list of auto hide widgets + */ + void registerAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Remove the given auto hide widget from the list of registered auto hide widgets + */ + void removeAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Handles widget events of auto hide widgets to trigger delayed show + * or hide actions for auto hide container on auto hide tab mouse over + */ + void handleAutoHideWidgetEvent(QEvent *e, QWidget *w); + public: /** * Default Constructor @@ -154,7 +198,8 @@ public: */ DockAreaWidget *addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget = nullptr); + DockAreaWidget *dockAreaWidget = nullptr, + int index = -1); /** * Removes dockwidget @@ -190,6 +235,18 @@ public: */ QList openedDockAreas() const; + /** + * Returns a list for all open dock widgets in all open dock areas + */ + QList openedDockWidgets() const; + + /** + * This function returns true, if the container has open dock areas. + * This functions is a little bit faster than calling openedDockAreas().isEmpty() + * because it returns as soon as it finds an open dock area + */ + bool hasOpenDockAreas() const; + /** * This function returns true if this dock area has only one single * visible dock widget. @@ -239,6 +296,34 @@ public: */ void closeOtherAreas(DockAreaWidget *keepOpenArea); + /** + * Returns the side tab widget for the given area + */ + AutoHideSideBar *sideTabBar(SideBarLocation area) const; + + /** + * Access function for auto hide widgets + */ + QList autoHideWidgets() const; + + /** + * Returns the content rectangle without the autohide side bars + */ + QRect contentRect() const; + + /** + * Returns the content rectangle mapped to global space. + * The content rectangle is the rectangle of the dock manager widget minus + * the auto hide side bars. So that means, it is the geometry of the + * internal root splitter mapped to global space. + */ + QRect contentRectGlobal() const; + + /** + * Returns the dock manager that owns this container + */ + DockManager *dockManager() const; + signals: /** * This signal is emitted if one or multiple dock areas has been added to @@ -247,6 +332,11 @@ signals: */ void dockAreasAdded(); + /** + * This signal is emitted, if a new auto hide widget has been created. + */ + void autoHideWidgetCreated(AutoHideDockContainer *autoHideWidget); + /** * This signal is emitted if one or multiple dock areas has been removed */ diff --git a/src/libs/advanceddockingsystem/dockfocuscontroller.cpp b/src/libs/advanceddockingsystem/dockfocuscontroller.cpp index a5878ba856c..b35963c6db2 100644 --- a/src/libs/advanceddockingsystem/dockfocuscontroller.cpp +++ b/src/libs/advanceddockingsystem/dockfocuscontroller.cpp @@ -3,250 +3,332 @@ #include "dockfocuscontroller.h" -#include "dockareawidget.h" +#include "ads_globals_p.h" #include "dockareatitlebar.h" +#include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" #include "dockwidget.h" #include "dockwidgettab.h" #include "floatingdockcontainer.h" -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #include "linux/floatingwidgettitlebar.h" #endif #include #include +#include #include #include -namespace ADS +namespace ADS { + +static const char *const g_focusedDockWidgetProperty = "FocusedDockWidget"; + +class DockFocusControllerPrivate { +public: + DockFocusController *q; + QPointer m_focusedDockWidget = nullptr; + QPointer m_focusedArea = nullptr; + QPointer m_oldFocusedDockWidget = nullptr; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QPointer m_floatingWidget = nullptr; +#endif + DockManager *m_dockManager = nullptr; + bool m_forceFocusChangedSignal = false; + bool m_tabPressed = false; + /** - * Private data class of CDockFocusController class (pimpl) + * Private data constructor */ - class DockFocusControllerPrivate - { - public: - DockFocusController *q; - QPointer m_focusedDockWidget = nullptr; - QPointer m_focusedArea = nullptr; - #ifdef Q_OS_LINUX - QPointer m_floatingWidget = nullptr; - #endif - DockManager *m_dockManager = nullptr; + DockFocusControllerPrivate(DockFocusController *parent); - /** - * Private data constructor - */ - DockFocusControllerPrivate(DockFocusController *parent); + /** + * This function updates the focus style of the given dock widget and + * the dock area that it belongs to + */ + void updateDockWidgetFocus(DockWidget *dockWidget); +}; // class DockFocusControllerPrivate - /** - * This function updates the focus style of the given dock widget and - * the dock area that it belongs to - */ - void updateDockWidgetFocus(DockWidget *dockWidget); - }; // class DockFocusControllerPrivate +static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused) +{ + dockWidget->setProperty("focused", focused); + dockWidget->tabWidget()->setProperty("focused", focused); + dockWidget->tabWidget()->updateStyle(); + internal::repolishStyle(dockWidget); +} - static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused) - { - dockWidget->setProperty("focused", focused); - dockWidget->tabWidget()->setProperty("focused", focused); - dockWidget->tabWidget()->updateStyle(); - internal::repolishStyle(dockWidget); - } +static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool focused) +{ + dockArea->setProperty("focused", focused); + internal::repolishStyle(dockArea); + internal::repolishStyle(dockArea->titleBar()); +} - static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool focused) - { - dockArea->setProperty("focused", focused); - internal::repolishStyle(dockArea); - internal::repolishStyle(dockArea->titleBar()); - } +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +static void updateFloatingWidgetFocusStyle(FloatingDockContainer *floatingWidget, bool focused) +{ + if (floatingWidget->hasNativeTitleBar()) + return; -#ifdef Q_OS_LINUX - static void updateFloatingWidgetFocusStyle(FloatingDockContainer *floatingWidget, bool focused) - { - auto titleBar = qobject_cast(floatingWidget->titleBarWidget()); - if (!titleBar) - return; + auto titleBar = qobject_cast(floatingWidget->titleBarWidget()); + if (!titleBar) + return; - titleBar->setProperty("focused", focused); - titleBar->updateStyle(); - } + titleBar->setProperty("focused", focused); + titleBar->updateStyle(); +} #endif - DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent) - : q(parent) - {} +DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent) + : q(parent) +{} - void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget) - { - DockAreaWidget *newFocusedDockArea = nullptr; - if (m_focusedDockWidget) - updateDockWidgetFocusStyle(m_focusedDockWidget, false); +void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget) +{ + if (!dockWidget->features().testFlag(DockWidget::DockWidgetFocusable)) + return; - DockWidget *old = m_focusedDockWidget; - m_focusedDockWidget = dockWidget; - updateDockWidgetFocusStyle(m_focusedDockWidget, true); - newFocusedDockArea = m_focusedDockWidget->dockAreaWidget(); - if (newFocusedDockArea && (m_focusedArea != newFocusedDockArea)) - { - if (m_focusedArea) - { - QObject::disconnect(m_focusedArea, &DockAreaWidget::viewToggled, - q, &DockFocusController::onFocusedDockAreaViewToggled); - updateDockAreaFocusStyle(m_focusedArea, false); - } + QWindow *window = nullptr; + auto dockContainer = dockWidget->dockContainer(); + if (dockContainer) + window = dockContainer->window()->windowHandle(); - m_focusedArea = newFocusedDockArea; - updateDockAreaFocusStyle(m_focusedArea, true); - QObject::connect(m_focusedArea, &DockAreaWidget::viewToggled, - q, &DockFocusController::onFocusedDockAreaViewToggled); + if (window) + window->setProperty(g_focusedDockWidgetProperty, + QVariant::fromValue(QPointer(dockWidget))); + + DockAreaWidget *newFocusedDockArea = nullptr; + if (m_focusedDockWidget) + updateDockWidgetFocusStyle(m_focusedDockWidget, false); + + DockWidget *old = m_focusedDockWidget; + m_focusedDockWidget = dockWidget; + updateDockWidgetFocusStyle(m_focusedDockWidget, true); + newFocusedDockArea = m_focusedDockWidget->dockAreaWidget(); + if (newFocusedDockArea && (m_focusedArea != newFocusedDockArea)) { + if (m_focusedArea) { + QObject::disconnect(m_focusedArea, + &DockAreaWidget::viewToggled, + q, + &DockFocusController::onFocusedDockAreaViewToggled); + updateDockAreaFocusStyle(m_focusedArea, false); } - auto dockContainer = m_focusedDockWidget->dockContainer(); - FloatingDockContainer *newFloatingWidget = nullptr; + m_focusedArea = newFocusedDockArea; + updateDockAreaFocusStyle(m_focusedArea, true); + QObject::connect(m_focusedArea, + &DockAreaWidget::viewToggled, + q, + &DockFocusController::onFocusedDockAreaViewToggled); + } - if (dockContainer) - newFloatingWidget = dockContainer->floatingWidget(); + FloatingDockContainer *newFloatingWidget = nullptr; + dockContainer = m_focusedDockWidget->dockContainer(); + if (dockContainer) + newFloatingWidget = dockContainer->floatingWidget(); - if (newFloatingWidget) - newFloatingWidget->setProperty("FocusedDockWidget", QVariant::fromValue(dockWidget)); + if (newFloatingWidget) + newFloatingWidget->setProperty(g_focusedDockWidgetProperty, + QVariant::fromValue(QPointer(dockWidget))); -#ifdef Q_OS_LINUX - // This code is required for styling the floating widget titlebar for linux - // depending on the current focus state - if (m_floatingWidget == newFloatingWidget) - return; - - if (m_floatingWidget) +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // This code is required for styling the floating widget titlebar for linux + // depending on the current focus state + if (m_floatingWidget != newFloatingWidget) { + if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, false); - + } m_floatingWidget = newFloatingWidget; - if (m_floatingWidget) + if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, true); + } + } #endif - if (old != dockWidget) - emit m_dockManager->focusedDockWidgetChanged(old, dockWidget); + if (old == dockWidget && !m_forceFocusChangedSignal) + return; + + m_forceFocusChangedSignal = false; + if (dockWidget->isVisible()) { + emit m_dockManager->focusedDockWidgetChanged(old, dockWidget); + } else { + m_oldFocusedDockWidget = old; + QObject::connect(dockWidget, + &DockWidget::visibilityChanged, + q, + &DockFocusController::onDockWidgetVisibilityChanged); } +} - DockFocusController::DockFocusController(DockManager *dockManager) - : QObject(dockManager) - , d(new DockFocusControllerPrivate(this)) - { - d->m_dockManager = dockManager; - connect(qApp, &QApplication::focusChanged, - this, &DockFocusController::onApplicationFocusChanged); - connect(d->m_dockManager, &DockManager::stateRestored, - this, &DockFocusController::onStateRestored); - } +void DockFocusController::onDockWidgetVisibilityChanged(bool visible) +{ + auto dockWidget = qobject_cast(sender()); + QObject::disconnect(dockWidget, + &DockWidget::visibilityChanged, + this, + &DockFocusController::onDockWidgetVisibilityChanged); + if (dockWidget && visible) + emit d->m_dockManager->focusedDockWidgetChanged(d->m_oldFocusedDockWidget, dockWidget); +} - DockFocusController::~DockFocusController() - { - delete d; - } +DockFocusController::DockFocusController(DockManager *dockManager) + : QObject(dockManager) + , d(new DockFocusControllerPrivate(this)) +{ + d->m_dockManager = dockManager; + connect(qApp, + &QApplication::focusChanged, + this, + &DockFocusController::onApplicationFocusChanged); + connect(qApp, + &QApplication::focusWindowChanged, + this, + &DockFocusController::onFocusWindowChanged); + connect(d->m_dockManager, + &DockManager::stateRestored, + this, + &DockFocusController::onStateRestored); +} - void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow) - { - Q_UNUSED(focusedOld); +DockFocusController::~DockFocusController() +{ + delete d; +} - if (d->m_dockManager->isRestoringState()) - return; +void DockFocusController::onFocusWindowChanged(QWindow *focusWindow) +{ + if (!focusWindow) + return; - if (!focusedNow) - return; + auto dockWidgetVar = focusWindow->property(g_focusedDockWidgetProperty); + if (!dockWidgetVar.isValid()) + return; - DockWidget *dockWidget = nullptr; - auto dockWidgetTab = qobject_cast(focusedNow); - if (dockWidgetTab) - dockWidget = dockWidgetTab->dockWidget(); + auto dockWidget = dockWidgetVar.value>(); + if (!dockWidget) + return; - if (!dockWidget) - dockWidget = qobject_cast(focusedNow); + d->updateDockWidgetFocus(dockWidget); +} - bool focusActual = dockWidget && dockWidget->widget(); +void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow) +{ + Q_UNUSED(focusedOld); - if (!dockWidget) - dockWidget = internal::findParent(focusedNow); + // Ignore focus changes if we are restoring state, or if user clicked a tab which in turn + // caused the focus change. + if (d->m_dockManager->isRestoringState() || d->m_tabPressed) + return; - #ifdef Q_OS_LINUX - if (!dockWidget) - return; - #else - if (!dockWidget || dockWidget->tabWidget()->isHidden()) - return; - #endif + qCInfo(adsLog) << Q_FUNC_INFO << "old: " << focusedOld << "new:" << focusedNow; + if (!focusedNow) + return; + + DockWidget *dockWidget = qobject_cast(focusedNow); + if (!dockWidget) + dockWidget = internal::findParent(focusedNow); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!dockWidget) + return; +#else + if (!dockWidget || dockWidget->tabWidget()->isHidden()) + return; +#endif + + d->updateDockWidgetFocus(dockWidget); +} + +void DockFocusController::setDockWidgetTabFocused(DockWidgetTab *tab) +{ + auto dockWidget = tab->dockWidget(); + if (dockWidget) d->updateDockWidgetFocus(dockWidget); +} - if (focusActual) { - // Do this async to avoid issues when changing focus in middle of another focus handling - QMetaObject::invokeMethod(dockWidget->widget(), QOverload<>::of(&QWidget::setFocus), - Qt::QueuedConnection); - } +void DockFocusController::clearDockWidgetFocus(DockWidget *dockWidget) +{ + dockWidget->clearFocus(); + updateDockWidgetFocusStyle(dockWidget, false); +} + +void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow) +{ + d->updateDockWidgetFocus(focusedNow); +} + +void DockFocusController::onFocusedDockAreaViewToggled(bool open) +{ + if (d->m_dockManager->isRestoringState() || !d->m_focusedArea || open) + return; + + DockAreaWidget *dockArea = qobject_cast(sender()); + if (!dockArea || open) + return; + + auto container = dockArea->dockContainer(); + auto openedDockAreas = container->openedDockAreas(); + if (openedDockAreas.isEmpty()) + return; + + d->updateDockWidgetFocus(openedDockAreas[0]->currentDockWidget()); +} + +void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) +{ + if (d->m_dockManager->isRestoringState()) + return; + + DockWidget *dockWidget = qobject_cast(droppedWidget); + if (!dockWidget) { + DockAreaWidget *dockArea = qobject_cast(droppedWidget); + if (dockArea) + dockWidget = dockArea->currentDockWidget(); } - void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow) - { - d->updateDockWidgetFocus(focusedNow); - } + if (!dockWidget) + return; - void DockFocusController::onFocusedDockAreaViewToggled(bool open) - { - if (d->m_dockManager->isRestoringState() || !d->m_focusedArea || open) - return; + d->m_forceFocusChangedSignal = true; + DockManager::setWidgetFocus(dockWidget); +} - auto container = d->m_focusedArea->dockContainer(); - auto openedDockAreas = container->openedDockAreas(); - if (openedDockAreas.isEmpty()) - return; +void DockFocusController::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) +{ + if (!floatingWidget || d->m_dockManager->isRestoringState()) + return; - DockManager::setWidgetFocus(openedDockAreas[0]->currentDockWidget()->tabWidget()); - } + auto dockWidgetVar = floatingWidget->property(g_focusedDockWidgetProperty); + if (!dockWidgetVar.isValid()) + return; - void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) - { - if (d->m_dockManager->isRestoringState()) - return; - - DockWidget *dockWidget = qobject_cast(droppedWidget); - if (dockWidget) - { - DockManager::setWidgetFocus(dockWidget->tabWidget()); - return; - } - - DockAreaWidget* dockArea = qobject_cast(droppedWidget); - if (!dockArea) - return; - - dockWidget = dockArea->currentDockWidget(); + auto dockWidget = dockWidgetVar.value(); + if (dockWidget) { + dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget); DockManager::setWidgetFocus(dockWidget->tabWidget()); } +} - void DockFocusController::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) - { - if (!floatingWidget || d->m_dockManager->isRestoringState()) - return; +void DockFocusController::onStateRestored() +{ + if (d->m_focusedDockWidget) + updateDockWidgetFocusStyle(d->m_focusedDockWidget, false); +} - auto vDockWidget = floatingWidget->property("FocusedDockWidget"); - if (!vDockWidget.isValid()) - return; +DockWidget *DockFocusController::focusedDockWidget() const +{ + return d->m_focusedDockWidget.data(); +} - auto dockWidget = vDockWidget.value(); - if (dockWidget) { - dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget); - DockManager::setWidgetFocus(dockWidget->tabWidget()); - } - } - - void DockFocusController::onStateRestored() - { - if (d->m_focusedDockWidget) - updateDockWidgetFocusStyle(d->m_focusedDockWidget, false); - } +void DockFocusController::setDockWidgetTabPressed(bool value) +{ + d->m_tabPressed = value; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockfocuscontroller.h b/src/libs/advanceddockingsystem/dockfocuscontroller.h index c108d0d5591..a9e11e13662 100644 --- a/src/libs/advanceddockingsystem/dockfocuscontroller.h +++ b/src/libs/advanceddockingsystem/dockfocuscontroller.h @@ -27,8 +27,10 @@ private: private: void onApplicationFocusChanged(QWidget *old, QWidget *now); + void onFocusWindowChanged(QWindow *focusWindow); void onFocusedDockAreaViewToggled(bool open); void onStateRestored(); + void onDockWidgetVisibilityChanged(bool visible); public: /** @@ -41,19 +43,6 @@ public: */ ~DockFocusController() override; - /** - * Helper function to set focus depending on the configuration of the - * FocusStyling flag - */ - template - static void setWidgetFocus(QWidgetPtr widget) - { - if (!DockManager::testConfigFlag(DockManager::FocusHighlighting)) - return; - - widget->setFocus(Qt::OtherFocusReason); - } - /** * A container needs to call this function if a widget has been dropped * into it @@ -68,6 +57,28 @@ public: */ void notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget); + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + */ + DockWidget *focusedDockWidget() const; + + /** + * Notifies the dock focus controller, that a the mouse is pressed or released. + */ + void setDockWidgetTabPressed(bool value); + + /** + * Request focus highlighting for the given dock widget assigned to the tab + * given in Tab parameter + */ + void setDockWidgetTabFocused(DockWidgetTab *tab); + + /* + * Request clear focus for a dock widget + */ + void clearDockWidgetFocus(DockWidget *dockWidget); + /** * Request a focus change to the given dock widget */ diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 60a5aa35c8c..20b9a71a730 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -4,12 +4,14 @@ #include "dockmanager.h" #include "ads_globals.h" -#include "advanceddockingsystemtr.h" #include "ads_globals_p.h" +#include "advanceddockingsystemtr.h" +#include "autohidedockcontainer.h" #include "dockareawidget.h" #include "dockfocuscontroller.h" #include "dockingstatereader.h" #include "dockoverlay.h" +#include "docksplitter.h" #include "dockwidget.h" #include "floatingdockcontainer.h" #include "iconprovider.h" @@ -40,8 +42,13 @@ #include #include #include +#include #include +#if !(defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) +#include +#endif + Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); using namespace Utils; @@ -57,6 +64,9 @@ enum eStateFileVersion { }; static DockManager::ConfigFlags g_staticConfigFlags = DockManager::DefaultNonOpaqueConfig; +static DockManager::AutoHideFlags g_staticAutoHideConfigFlags; // auto hide is disabled by default + +static QString g_floatingContainersTitle; /** * Private data class of DockManager class (pimpl) @@ -66,6 +76,7 @@ class DockManagerPrivate public: DockManager *q; QList> m_floatingWidgets; + QList> m_hiddenFloatingWidgets; QList m_containers; DockOverlay *m_containerOverlay = nullptr; DockOverlay *m_dockAreaOverlay = nullptr; @@ -73,6 +84,8 @@ public: bool m_restoringState = false; QVector m_uninitializedFloatingWidgets; DockFocusController *m_focusController = nullptr; + DockWidget *m_centralWidget = nullptr; + bool m_isLeavingMinimized = false; QString m_workspacePresetsPath; QList m_workspaces; @@ -114,7 +127,7 @@ public: void markDockWidgetsDirty() { for (const auto &dockWidget : std::as_const(m_dockWidgetsMap)) - dockWidget->setProperty("dirty", true); + dockWidget->setProperty(internal::g_dirtyProperty, true); } /** @@ -180,11 +193,27 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versio return false; } + qCInfo(adsLog) << stateReader.attributes().value("containers").toInt(); + + if (m_centralWidget) { + const auto centralWidgetAttribute = stateReader.attributes().value("centralWidget"); + // If we have a central widget, but a state without central widget, then something is wrong. + if (centralWidgetAttribute.isEmpty()) { + qWarning() + << "DockManager has central widget, but saved state does not have central widget."; + return false; + } + + // If the object name of the central widget does not match the name of the saved central + // widget, something is wrong. + if (m_centralWidget->objectName() != centralWidgetAttribute.toString()) { + qWarning() << "Object name of central widget does not match name of central widget in " + "saved state."; + return false; + } + } + bool result = true; -#ifdef ADS_DEBUG_PRINT - int dockContainers = stateReader.attributes().value("containers").toInt(); - qCInfo(adsLog) << dockContainers; -#endif int dockContainerCount = 0; while (stateReader.readNextStartElement()) { if (stateReader.name() == QLatin1String("container")) { @@ -199,10 +228,10 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versio if (!testing) { // Delete remaining empty floating widgets int floatingWidgetIndex = dockContainerCount - 1; - int deleteCount = m_floatingWidgets.count() - floatingWidgetIndex; - for (int i = 0; i < deleteCount; ++i) { - m_floatingWidgets[floatingWidgetIndex + i]->deleteLater(); - q->removeDockContainer(m_floatingWidgets[floatingWidgetIndex + i]->dockContainer()); + for (int i = floatingWidgetIndex; i < m_floatingWidgets.count(); ++i) { + QPointer floatingWidget = m_floatingWidgets[i]; + q->removeDockContainer(floatingWidget->dockContainer()); + floatingWidget->deleteLater(); } } @@ -215,11 +244,17 @@ void DockManagerPrivate::restoreDockWidgetsOpenState() // invisible to the user now and have no assigned dock area. They do not belong to any dock // container, until the user toggles the toggle view action the next time. for (auto dockWidget : std::as_const(m_dockWidgetsMap)) { - if (dockWidget->property(internal::dirtyProperty).toBool()) { + if (dockWidget->property(internal::g_dirtyProperty).toBool()) { + // If the DockWidget is an auto hide widget that is not assigned yet, + // then we need to delete the auto hide container now + if (dockWidget->isAutoHide()) + dockWidget->autoHideDockContainer()->cleanupAndDelete(); + dockWidget->flagAsUnassigned(); emit dockWidget->viewToggled(false); } else { - dockWidget->toggleViewInternal(!dockWidget->property(internal::closedProperty).toBool()); + dockWidget->toggleViewInternal( + !dockWidget->property(internal::g_closedProperty).toBool()); } } } @@ -251,7 +286,7 @@ void DockManagerPrivate::restoreDockAreasIndices() void DockManagerPrivate::emitTopLevelEvents() { - // Finally we need to send the topLevelChanged() signals for all dock widgets if top + // Finally we need to send the topLevelChanged() signal for all dock widgets if top // level changed. for (auto dockContainer : std::as_const(m_containers)) { DockWidget *topLevelDockWidget = dockContainer->topLevelDockWidget(); @@ -288,6 +323,7 @@ bool DockManagerPrivate::restoreState(const QByteArray &state, int version) restoreDockWidgetsOpenState(); restoreDockAreasIndices(); emitTopLevelEvents(); + q->dumpLayout(); return true; } @@ -301,6 +337,7 @@ DockManager::DockManager(QWidget *parent) }); createRootSplitter(); + createSideTabBarWidgets(); QMainWindow *mainWindow = qobject_cast(parent); if (mainWindow) mainWindow->setCentralWidget(this); @@ -311,6 +348,17 @@ DockManager::DockManager(QWidget *parent) if (DockManager::configFlags().testFlag(DockManager::FocusHighlighting)) d->m_focusController = new DockFocusController(this); + + window()->installEventFilter(this); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + connect(qApp, &QApplication::focusWindowChanged, this, [](QWindow *focusWindow) { + // Bring modal dialogs to foreground to ensure that they are in front of any + // floating dock widget. + if (focusWindow && focusWindow->isModal()) + focusWindow->raise(); + }); +#endif } DockManager::~DockManager() @@ -319,6 +367,18 @@ DockManager::~DockManager() save(); saveStartupWorkspace(); + // Fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307 + std::vector areas; + for (int i = 0; i != dockAreaCount(); ++i) + areas.push_back(dockArea(i)); + + for (auto area : areas) { + for (auto widget : area->dockWidgets()) + delete widget; + + delete area; + } + // Using a temporary vector since the destructor of FloatingDockWidgetContainer // alters d->m_floatingWidgets. const auto copy = d->m_floatingWidgets; @@ -334,21 +394,41 @@ DockManager::ConfigFlags DockManager::configFlags() return g_staticConfigFlags; } +DockManager::AutoHideFlags DockManager::autoHideConfigFlags() +{ + return g_staticAutoHideConfigFlags; +} + void DockManager::setConfigFlags(const ConfigFlags flags) { g_staticConfigFlags = flags; } +void DockManager::setAutoHideConfigFlags(const AutoHideFlags flags) +{ + g_staticAutoHideConfigFlags = flags; +} + void DockManager::setConfigFlag(eConfigFlag flag, bool on) { internal::setFlag(g_staticConfigFlags, flag, on); } +void DockManager::setAutoHideConfigFlag(eAutoHideFlag flag, bool on) +{ + internal::setFlag(g_staticAutoHideConfigFlags, flag, on); +} + bool DockManager::testConfigFlag(eConfigFlag flag) { return configFlags().testFlag(flag); } +bool DockManager::testAutoHideConfigFlag(eAutoHideFlag flag) +{ + return autoHideConfigFlags().testFlag(flag); +} + IconProvider &DockManager::iconProvider() { static IconProvider instance; @@ -372,10 +452,9 @@ void DockManager::setWorkspacePresetsPath(const QString &path) void DockManager::initialize() { - qCInfo(adsLog) << "Initialize DockManager"; + qCInfo(adsLog) << Q_FUNC_INFO; - // Can't continue if settings are not set yet - QTC_ASSERT(d->m_settings, return); + QTC_ASSERT(d->m_settings, return); // Can't continue if settings are not set yet syncWorkspacePresets(); prepareWorkspaces(); @@ -414,10 +493,40 @@ void DockManager::initialize() DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) + DockAreaWidget *dockAreaWidget, + int index) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); - return DockContainerWidget::addDockWidget(area, dockWidget, dockAreaWidget); + auto container = dockAreaWidget ? dockAreaWidget->dockContainer() : this; + auto dockArea = container->addDockWidget(area, dockWidget, dockAreaWidget, index); + emit dockWidgetAdded(dockWidget); + return dockArea; +} + +DockAreaWidget *DockManager::addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget, + DockContainerWidget *dockContainerWidget) +{ + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + auto dockArea = dockContainerWidget->addDockWidget(area, dockWidget); + emit dockWidgetAdded(dockWidget); + return dockArea; +} + +AutoHideDockContainer *DockManager::addAutoHideDockWidget(SideBarLocation location, + DockWidget *dockWidget) +{ + return addAutoHideDockWidgetToContainer(location, dockWidget, this); +} + +AutoHideDockContainer *DockManager::addAutoHideDockWidgetToContainer( + SideBarLocation location, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget) +{ + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + auto container = dockContainerWidget->createAndSetupAutoHideContainer(location, dockWidget); + container->collapseView(true); + emit dockWidgetAdded(dockWidget); + return container; } DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget) @@ -426,15 +535,16 @@ DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *d if (areaWidget) return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, areaWidget); else if (!openedDockAreas().isEmpty()) - return addDockWidget(area, dockWidget, openedDockAreas().constLast()); + return addDockWidget(area, dockWidget, openedDockAreas().constLast()); // TODO else return addDockWidget(area, dockWidget, nullptr); } DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) + DockAreaWidget *dockAreaWidget, + int index) { - return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget); + return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget, index); } FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget) @@ -452,6 +562,7 @@ FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget else d->m_uninitializedFloatingWidgets.append(floatingWidget); + emit dockWidgetAdded(dockWidget); return floatingWidget; } @@ -465,6 +576,7 @@ void DockManager::removeDockWidget(DockWidget *dockWidget) emit dockWidgetAboutToBeRemoved(dockWidget); d->m_dockWidgetsMap.remove(dockWidget->objectName()); DockContainerWidget::removeDockWidget(dockWidget); + dockWidget->setDockManager(nullptr); emit dockWidgetRemoved(dockWidget); } @@ -503,6 +615,10 @@ QByteArray DockManager::saveState(const QString &displayName, int version) const stream.writeAttribute("userVersion", QString::number(version)); stream.writeAttribute("containers", QString::number(d->m_containers.count())); stream.writeAttribute(workspaceDisplayNameAttribute.toString(), displayName); + + if (d->m_centralWidget) + stream.writeAttribute("centralWidget", d->m_centralWidget->objectName()); + for (auto container : std::as_const(d->m_containers)) container->saveState(stream); @@ -544,12 +660,185 @@ bool DockManager::isRestoringState() const return d->m_restoringState; } +bool DockManager::isLeavingMinimizedState() const +{ + return d->m_isLeavingMinimized; +} + +bool DockManager::eventFilter(QObject *obj, QEvent *event) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Emulate Qt:Tool behavior. + /* + // Window always on top of the MainWindow. + if (event->type() == QEvent::WindowActivate) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible() || window()->isMinimized()) + continue; + + // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires + // a show call. This then leads to flickering and a nasty endless loop (also buggy + // behavior on Ubuntu). So we just do it ourself. + floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, true); + } + } else if (event->type() == QEvent::WindowDeactivate) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible() || window()->isMinimized()) + continue; + + floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, false); + + floatingWidget->raise(); + } + } +*/ + // Sync minimize with MainWindow + if (event->type() == QEvent::WindowStateChange) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible()) + continue; + + if (window()->isMinimized()) + floatingWidget->showMinimized(); + else + floatingWidget->setWindowState(floatingWidget->windowState() + & (~Qt::WindowMinimized)); + } + if (!window()->isMinimized()) + QApplication::setActiveWindow(window()); + } + return Super::eventFilter(obj, event); + +#else + if (event->type() == QEvent::WindowStateChange) { + QWindowStateChangeEvent *ev = static_cast(event); + if (ev->oldState().testFlag(Qt::WindowMinimized)) { + d->m_isLeavingMinimized = true; + QMetaObject::invokeMethod(this, "endLeavingMinimizedState", Qt::QueuedConnection); + } + } + return Super::eventFilter(obj, event); +#endif +} + +DockWidget *DockManager::focusedDockWidget() const +{ + if (!d->m_focusController) + return nullptr; + else + return d->m_focusController->focusedDockWidget(); +} + +QList DockManager::splitterSizes(DockAreaWidget *containedArea) const +{ + if (containedArea) { + auto splitter = internal::findParent(containedArea); + if (splitter) + return splitter->sizes(); + } + + return QList(); +} + +void DockManager::setSplitterSizes(DockAreaWidget *containedArea, const QList &sizes) +{ + if (!containedArea) + return; + + auto splitter = internal::findParent(containedArea); + if (splitter && splitter->count() == sizes.count()) + splitter->setSizes(sizes); +} + +void DockManager::setFloatingContainersTitle(const QString &title) +{ + g_floatingContainersTitle = title; +} + +QString DockManager::floatingContainersTitle() +{ + if (g_floatingContainersTitle.isEmpty()) + return qApp->applicationDisplayName(); + + return g_floatingContainersTitle; +} + +DockWidget *DockManager::centralWidget() const +{ + return d->m_centralWidget; +} + +DockAreaWidget *DockManager::setCentralWidget(DockWidget *widget) +{ + if (!widget) { + d->m_centralWidget = nullptr; + return nullptr; + } + + // Setting a new central widget is now allowed if there is already a central widget or + // if there are already other dock widgets. + if (d->m_centralWidget) { + qWarning( + "Setting a central widget not possible because there is already a central widget."); + return nullptr; + } + + // Setting a central widget is now allowed if there are already other dock widgets. + if (!d->m_dockWidgetsMap.isEmpty()) { + qWarning("Setting a central widget not possible - the central widget need to be the first " + "dock widget that is added to the dock manager."); + return nullptr; + } + + widget->setFeature(DockWidget::DockWidgetClosable, false); + widget->setFeature(DockWidget::DockWidgetMovable, false); + widget->setFeature(DockWidget::DockWidgetFloatable, false); + widget->setFeature(DockWidget::DockWidgetPinnable, false); + d->m_centralWidget = widget; + DockAreaWidget *centralArea = addDockWidget(CenterDockWidgetArea, widget); + centralArea->setDockAreaFlag(DockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true); + return centralArea; +} + void DockManager::setDockWidgetFocused(DockWidget *dockWidget) { if (d->m_focusController) d->m_focusController->setDockWidgetFocused(dockWidget); } +void DockManager::hideManagerAndFloatingWidgets() +{ + hide(); + + d->m_hiddenFloatingWidgets.clear(); + // Hide updates of floating widgets from user. + for (auto &floatingWidget : d->m_floatingWidgets) { + if (floatingWidget->isVisible()) { + QList visibleWidgets; + for (auto dockWidget : floatingWidget->dockWidgets()) { + if (dockWidget->toggleViewAction()->isChecked()) + visibleWidgets.push_back(dockWidget); + } + + // Save as floating widget to be shown when DockManager will be shown back. + d->m_hiddenFloatingWidgets.push_back(floatingWidget); + floatingWidget->hide(); + + // Hidding floating widget automatically marked contained DockWidgets as hidden + // but they must remain marked as visible as we want them to be restored visible + // when DockManager will be shown back. + for (auto dockWidget : visibleWidgets) + dockWidget->toggleViewAction()->setChecked(true); + } + } +} + +void DockManager::endLeavingMinimizedState() +{ + d->m_isLeavingMinimized = false; + this->activateWindow(); +} + void DockManager::registerFloatingWidget(FloatingDockContainer *floatingWidget) { d->m_floatingWidgets.append(floatingWidget); @@ -602,15 +891,53 @@ void DockManager::notifyDockAreaRelocation(DockAreaWidget *, DockContainerWidget void DockManager::showEvent(QShowEvent *event) { Super::showEvent(event); + // Fix issue #380 + restoreHiddenFloatingWidgets(); + if (d->m_uninitializedFloatingWidgets.empty()) return; - for (auto floatingWidget : std::as_const(d->m_uninitializedFloatingWidgets)) - floatingWidget->show(); + for (auto floatingWidget : std::as_const(d->m_uninitializedFloatingWidgets)) { + // Check, if someone closed a floating dock widget before the dock manager is shown. + if (floatingWidget->dockContainer()->hasOpenDockAreas()) + floatingWidget->show(); + } d->m_uninitializedFloatingWidgets.clear(); } +DockFocusController *DockManager::dockFocusController() const +{ + return d->m_focusController; +} + +void DockManager::restoreHiddenFloatingWidgets() +{ + if (d->m_hiddenFloatingWidgets.isEmpty()) + return; + + // Restore floating widgets that were hidden upon hideManagerAndFloatingWidgets + for (auto &floatingWidget : d->m_hiddenFloatingWidgets) { + bool hasDockWidgetVisible = false; + + // Needed to prevent FloatingDockContainer being shown empty + // Could make sense to move this to FloatingDockContainer::showEvent(QShowEvent *event) + // if experiencing FloatingDockContainer being shown empty in other situations, but let's keep + // it here for now to make sure changes to fix Issue #380 does not impact existing behaviours + for (auto dockWidget : floatingWidget->dockWidgets()) { + if (dockWidget->toggleViewAction()->isChecked()) { + dockWidget->toggleView(true); + hasDockWidgetVisible = true; + } + } + + if (hasDockWidgetVisible) + floatingWidget->show(); + } + + d->m_hiddenFloatingWidgets.clear(); +} + Workspace *DockManager::activeWorkspace() const { return &d->m_workspace; diff --git a/src/libs/advanceddockingsystem/dockmanager.h b/src/libs/advanceddockingsystem/dockmanager.h index 62148d1e234..f0e9a8d0e1e 100644 --- a/src/libs/advanceddockingsystem/dockmanager.h +++ b/src/libs/advanceddockingsystem/dockmanager.h @@ -46,6 +46,8 @@ class DockWidgetTab; class DockWidgetTabPrivate; struct DockAreaWidgetPrivate; class IconProvider; +class DockFocusController; +class AutoHideSideBar; inline constexpr QStringView workspaceFolderName{u"workspaces"}; inline constexpr QStringView workspaceFileExtension{u"wrk"}; @@ -80,6 +82,8 @@ private: friend class FloatingDragPreview; friend class FloatingDragPreviewPrivate; friend class DockAreaTitleBar; + friend class AutoHideDockContainer; + friend AutoHideSideBar; public: using Super = DockContainerWidget; @@ -105,8 +109,6 @@ public: = 0x0080, //!< if this flag is set, then all tabs that are closable show a close button RetainTabSizeWhenCloseButtonHidden = 0x0100, //!< if this flag is set, the space for the close button is reserved even if the close button is not visible - OpaqueUndocking - = 0x0200, ///< If enabled, the widgets are immediately undocked into floating widgets, if disabled, only a draw preview is undocked and the real undocking is deferred until the mouse is released DragPreviewIsDynamic = 0x0400, ///< If opaque undocking is disabled, this flag defines the behavior of the drag preview window, if this flag is enabled, the preview will be adjusted dynamically to the drop area DragPreviewShowsContentPixmap @@ -117,34 +119,38 @@ public: = 0x2000, ///< If this option is enabled, the tab of a dock widget is always displayed - even if it is the only visible dock widget in a floating widget. DockAreaHasUndockButton = 0x4000, //!< If the flag is set each dock area has an undock button DockAreaHasTabsMenuButton - = 0x8000, //!< If the flag is set each dock area has a tabs menu button + = 0x8000, //!< If the flag is set each dock area has a tabs menu button DockAreaHideDisabledButtons - = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the tollbar at all (enabling them will bring them back) + = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the toolbar at all (enabling them will bring them back) DockAreaDynamicTabsMenuButtonVisibility = 0x20000, //!< If the flag is set, the tabs menu button will be shown only when it is required - that means, if the tabs are elided. If the tabs are not elided, it is hidden FloatingContainerHasWidgetTitle - = 0x40000, //!< If set, the Floating Widget window title reflects the title of the current dock widget otherwise it displays application name as window title + = 0x40000, //!< If set, the Floating Widget window title reflects the title of the current dock widget otherwise it displays the title set with `CDockManager::setFloatingContainersTitle` or application name as window title FloatingContainerHasWidgetIcon = 0x80000, //!< If set, the Floating Widget icon reflects the icon of the current dock widget otherwise it displays application icon HideSingleCentralWidgetTitleBar = 0x100000, //!< If there is only one single visible dock widget in the main dock container (the dock manager) and if this flag is set, then the titlebar of this dock widget will be hidden //!< this only makes sense for non draggable and non floatable widgets and enables the creation of some kind of "central" widget + FocusHighlighting = 0x200000, //!< enables styling of focused dock widget tabs or floating widget titlebar EqualSplitOnInsertion - = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + + MiddleMouseButtonClosesTab + = 0x2000000, //! If the flag is set, the user can use the mouse middle button to close the tab under the mouse DefaultDockAreaButtons = DockAreaHasCloseButton | DockAreaHasUndockButton | DockAreaHasTabsMenuButton, ///< default configuration of dock area title bar buttons - DefaultBaseConfig = DefaultDockAreaButtons | ActiveTabHasCloseButton - | XmlAutoFormattingEnabled - | FloatingContainerHasWidgetTitle, ///< default base configuration settings + DefaultBaseConfig + = DefaultDockAreaButtons | ActiveTabHasCloseButton | XmlAutoFormattingEnabled + | FloatingContainerHasWidgetTitle, ///< default base configuration settings DefaultOpaqueConfig = DefaultBaseConfig | OpaqueSplitterResize - | OpaqueUndocking, ///< the default configuration with opaque operations - this may cause issues if ActiveX or Qt 3D windows are involved + | DragPreviewShowsContentPixmap, ///< the default configuration for non opaque operations DefaultNonOpaqueConfig = DefaultBaseConfig @@ -156,6 +162,31 @@ public: }; Q_DECLARE_FLAGS(ConfigFlags, eConfigFlag) + /** + * These global configuration flags configure some dock manager auto hide settings. + * Set the dock manager flags, before you create the dock manager instance. + */ + enum eAutoHideFlag { + AutoHideFeatureEnabled = 0x01, //!< enables / disables auto hide feature + DockAreaHasAutoHideButton + = 0x02, //!< If the flag is set each dock area has a auto hide menu button + AutoHideButtonTogglesArea + = 0x04, //!< If the flag is set, the auto hide button enables auto hiding for all dock widgets in an area, if disabled, only the current dock widget will be toggled + AutoHideButtonCheckable + = 0x08, //!< If the flag is set, the auto hide button will be checked and unchecked depending on the auto hide state. Mainly for styling purposes. + AutoHideSideBarsIconOnly + = 0x10, ///< show only icons in auto hide side tab - if a tab has no icon, then the text will be shown + AutoHideShowOnMouseOver + = 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container + AutoHideCloseButtonCollapsesDock + = 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely + + DefaultAutoHideConfig + = AutoHideFeatureEnabled + | DockAreaHasAutoHideButton ///< the default configuration for left and right side bars + }; + Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag) + /** * Default Constructor. * If the given parent is a QMainWindow, the dock manager sets itself as the central widget. @@ -174,23 +205,45 @@ public: */ static ConfigFlags configFlags(); + /** + * This function returns the auto hide configuration flags. + */ + static AutoHideFlags autoHideConfigFlags(); + /** * Sets the global configuration flags for the whole docking system. Call this function before * you create the dock manager and before your create the first dock widget. */ static void setConfigFlags(const ConfigFlags flags); + /** + * Sets the global configuration flags for the whole docking system. Call this function before + * you create the dock manager and before your create the first dock widget. + */ + static void setAutoHideConfigFlags(const AutoHideFlags flags); + /** * Set a certain config flag. * \see setConfigFlags() */ static void setConfigFlag(eConfigFlag flag, bool on = true); + /** + * Set a certain overlay config flag. + * \see setConfigFlags() + */ + static void setAutoHideConfigFlag(eAutoHideFlag flag, bool on = true); + /** * Returns true if the given config flag is set. */ static bool testConfigFlag(eConfigFlag flag); + /** + * Returns true if the given overlay config flag is set + */ + static bool testAutoHideConfigFlag(eAutoHideFlag flag); + /** * Returns the global icon provider. * The icon provider enables the use of custom icons in case using styleheets for icons is not @@ -242,7 +295,33 @@ public: */ DockAreaWidget *addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget = nullptr); + DockAreaWidget *dockAreaWidget = nullptr, + int index = -1); + + /** + * Adds dockwidget into the given container. + * This allows you to place the dock widget into a container, even if that + * container does not yet contain a DockAreaWidget. + * \return Returns the dock area widget that contains the new DockWidget + */ + DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget, + DockContainerWidget *dockContainerWidget); + + /** + * Adds an Auto-Hide widget to the dock manager container pinned to + * the given side bar location. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + AutoHideDockContainer *addAutoHideDockWidget(SideBarLocation location, DockWidget *dockWidget); + + /** + * Adds an Auto-Hide widget to the given DockContainerWidget pinned to + * the given side bar location in this container. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + AutoHideDockContainer *addAutoHideDockWidgetToContainer( + SideBarLocation location, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget); /** * This function will add the given Dockwidget to the given dock area as a new tab. If no dock @@ -253,7 +332,9 @@ public: /** * This function will add the given Dockwidget to the given DockAreaWidget as a new tab. */ - DockAreaWidget *addDockWidgetTabToArea(DockWidget *dockWidget, DockAreaWidget *dockAreaWidget); + DockAreaWidget *addDockWidgetTabToArea(DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget, + int index = -1); /** * Adds the given DockWidget floating and returns the created FloatingDockContainer instance. @@ -322,11 +403,90 @@ public: bool isRestoringState() const; /** - * Request a focus change to the given dock widget. - * This function only has an effect, if the flag CDockManager::FocusStyling is enabled. + * This function returns true, if the DockManager window is restoring from minimized state. + * The DockManager is in this state starting from the QWindowStateChangeEvent that signals the + * state change from minimized to normal until endLeavingMinimizedState() function is called. + */ + bool isLeavingMinimizedState() const; + + bool eventFilter(QObject *obj, QEvent *e) override; + + /** + * Returns the dock widget that has focus style in the ui or a nullptr if not dock widget is + * painted focused. If the flag FocusHighlighting is disabled, this function always returns + * nullptr. + */ + DockWidget *focusedDockWidget() const; + + /** + * Returns the sizes of the splitter that contains the dock area. If there is no splitter that + * contains the area, an empty list will be returned. + */ + QList splitterSizes(DockAreaWidget *containedArea) const; + + /** + * Update the sizes of a splitter + * Programmatically updates the sizes of a given splitter by calling QSplitter::setSizes(). The + * splitter will be the splitter that contains the supplied dock area widget. If there is not + * splitter that contains the dock area, or the sizes supplied does not match the number of + * children of the splitter, this method will have no effect. + */ + void setSplitterSizes(DockAreaWidget *containedArea, const QList &sizes); + + /** + * Set a custom title for all FloatingContainer that does not reflect the title of the current + * dock widget. + */ + static void setFloatingContainersTitle(const QString &title); + + /** + * Returns the title used by all FloatingContainer that does not reflect the title of the + * current dock widget. If not title was set with setFloatingContainersTitle(), it returns + * QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle(); + + /** + * This function returns managers central widget or nullptr if no central widget is set. + */ + DockWidget *centralWidget() const; + + /** + * Adds dockwidget widget into the central area and marks it as central widget. + * If central widget is set, it will be the only dock widget + * that will resize with the dock container. A central widget if not + * movable, floatable or closable and the titlebar of the central + * dock area is not visible. + * If the given widget could be set as central widget, the function returns + * the created dock area. If the widget could not be set, because there + * is already a central widget, this function returns a nullptr. + * To clear the central widget, pass a nullptr to the function. + * \note Setting a central widget is only possible if no other dock widgets + * have been registered before. That means, this function should be the + * first function that you call before you add other dock widgets. + * \retval != 0 The dock area that contains the central widget + * \retval nullptr Indicates that the given widget can not be set as central + * widget because there is already a central widget. + */ + DockAreaWidget *setCentralWidget(DockWidget *widget); + + /** + * Request a focus change to the given dock widget. This function only has an effect, if the + * flag DockManager::FocusStyling is enabled. */ void setDockWidgetFocused(DockWidget *dockWidget); + /** + * Hide CDockManager and all floating widgets (See Issue #380). Calling regular QWidget::hide() + * hides the DockManager but not the floating widgets. + */ + void hideManagerAndFloatingWidgets(); + + /** + * Ends the isRestoringFromMinimizedState + */ + void endLeavingMinimizedState(); + signals: /** * This signal is emitted if the list of workspaces changed. @@ -376,6 +536,12 @@ signals: */ void dockAreaCreated(ADS::DockAreaWidget *dockArea); + /** + * This signal is emitted if a dock widget has been added to this + * dock manager instance. + */ + void dockWidgetAdded(ADS::DockWidget *dockWidget); + /** * This signal is emitted just before removal of the given DockWidget. */ @@ -454,6 +620,17 @@ protected: */ void showEvent(QShowEvent *event) override; + /** + * Access for the internal dock focus controller. + * This function only returns a valid object, if the FocusHighlighting flag is set. + */ + DockFocusController *dockFocusController() const; + + /** + * Restore floating widgets hidden by an earlier call to hideManagerAndFloatingWidgets. + */ + void restoreHiddenFloatingWidgets(); + public: // Workspace state Workspace *activeWorkspace() const; diff --git a/src/libs/advanceddockingsystem/dockoverlay.cpp b/src/libs/advanceddockingsystem/dockoverlay.cpp index 3a3e9b5b0b0..8ca418b645f 100644 --- a/src/libs/advanceddockingsystem/dockoverlay.cpp +++ b/src/libs/advanceddockingsystem/dockoverlay.cpp @@ -3,8 +3,8 @@ #include "dockoverlay.h" -#include "dockareawidget.h" #include "dockareatitlebar.h" +#include "dockareawidget.h" #include @@ -25,724 +25,718 @@ namespace ADS { - /** - * Private data class of DockOverlay - */ - class DockOverlayPrivate - { - public: - DockOverlay *q; - DockWidgetAreas m_allowedAreas = InvalidDockWidgetArea; - DockOverlayCross *m_cross = nullptr; - QPointer m_targetWidget; - DockWidgetArea m_lastLocation = InvalidDockWidgetArea; - bool m_dropPreviewEnabled = true; - DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; - QRect m_dropAreaRect; - - /** - * Private data constructor - */ - DockOverlayPrivate(DockOverlay *parent) - : q(parent) - {} - }; +/** + * Private data class of DockOverlay + */ +class DockOverlayPrivate +{ +public: + DockOverlay *q; + DockWidgetAreas m_allowedAreas = InvalidDockWidgetArea; + DockOverlayCross *m_cross = nullptr; + QPointer m_targetWidget; + DockWidgetArea m_lastLocation = InvalidDockWidgetArea; + bool m_dropPreviewEnabled = true; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + QRect m_dropAreaRect; /** - * Private data of DockOverlayCross class + * Private data constructor */ - class DockOverlayCrossPrivate + DockOverlayPrivate(DockOverlay *parent) + : q(parent) + {} +}; + +/** + * Private data of DockOverlayCross class + */ +class DockOverlayCrossPrivate +{ +public: + DockOverlayCross *q; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + DockOverlay *m_dockOverlay = nullptr; + QHash m_dropIndicatorWidgets; + QGridLayout *m_gridLayout = nullptr; + QColor m_iconColors[5]; + bool m_updateRequired = false; + double m_lastDevicePixelRatio = 0.1; + + /** + * Private data constructor + */ + DockOverlayCrossPrivate(DockOverlayCross *parent) + : q(parent) + {} + + /** + * @param area + * @return + */ + QPoint areaGridPosition(const DockWidgetArea area); + + /** + * Palette based default icon colors + */ + QColor defaultIconColor(DockOverlayCross::eIconColor colorIndex) { - public: - DockOverlayCross *q; - DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; - DockOverlay *m_dockOverlay = nullptr; - QHash m_dropIndicatorWidgets; - QGridLayout *m_gridLayout = nullptr; - QColor m_iconColors[5]; - bool m_updateRequired = false; - double m_lastDevicePixelRatio = 0.1; - - /** - * Private data constructor - */ - DockOverlayCrossPrivate(DockOverlayCross *parent) - : q(parent) - {} - - /** - * @param area - * @return - */ - QPoint areaGridPosition(const DockWidgetArea area); - - /** - * Palette based default icon colors - */ - QColor defaultIconColor(DockOverlayCross::eIconColor colorIndex) - { - QPalette palette = q->palette(); - switch (colorIndex) { - case DockOverlayCross::FrameColor: - return palette.color(QPalette::Active, QPalette::Highlight); - case DockOverlayCross::WindowBackgroundColor: - return palette.color(QPalette::Active, QPalette::Base); - case DockOverlayCross::OverlayColor: { - QColor color = palette.color(QPalette::Active, QPalette::Highlight); - color.setAlpha(64); - return color; - } - case DockOverlayCross::ArrowColor: - return palette.color(QPalette::Active, QPalette::Base); - case DockOverlayCross::ShadowColor: - return QColor(0, 0, 0, 64); - } - - return QColor(); - } - - /** - * Stylehseet based icon colors - */ - QColor iconColor(DockOverlayCross::eIconColor colorIndex) - { - QColor color = m_iconColors[colorIndex]; - if (!color.isValid()) { - color = defaultIconColor(colorIndex); - m_iconColors[colorIndex] = color; - } + QPalette palette = q->palette(); + switch (colorIndex) { + case DockOverlayCross::FrameColor: + return palette.color(QPalette::Active, QPalette::Highlight); + case DockOverlayCross::WindowBackgroundColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::OverlayColor: { + QColor color = palette.color(QPalette::Active, QPalette::Highlight); + color.setAlpha(64); return color; } - - /** - * Helper function that returns the drop indicator width depending on the - * operating system - */ - qreal dropIndicatiorWidth(QLabel *label) const - { - if (Utils::HostOsInfo::isLinuxHost()) - return 40; - else - return static_cast(label->fontMetrics().height()) * 3.f; + case DockOverlayCross::ArrowColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::ShadowColor: + return QColor(0, 0, 0, 64); } - QWidget *createDropIndicatorWidget(DockWidgetArea dockWidgetArea, DockOverlay::eMode mode) - { - QLabel *label = new QLabel(); - label->setObjectName("DockWidgetAreaLabel"); + return QColor(); + } - const qreal metric = dropIndicatiorWidth(label); - const QSizeF size(metric, metric); - - label->setPixmap(createHighDpiDropIndicatorPixmap(size, dockWidgetArea, mode)); - label->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - label->setAttribute(Qt::WA_TranslucentBackground); - label->setProperty("dockWidgetArea", dockWidgetArea); - return label; - } - - void updateDropIndicatorIcon(QWidget *dropIndicatorWidget) - { - QLabel *label = qobject_cast(dropIndicatorWidget); - const qreal metric = dropIndicatiorWidth(label); - const QSizeF size(metric, metric); - - int area = label->property("dockWidgetArea").toInt(); - label->setPixmap(createHighDpiDropIndicatorPixmap(size, - static_cast(area), - m_mode)); // TODO - } - - QPixmap createHighDpiDropIndicatorPixmap(const QSizeF &size, - DockWidgetArea dockWidgetArea, - DockOverlay::eMode mode) - { - const QColor borderColor = iconColor(DockOverlayCross::FrameColor); - const QColor backgroundColor = iconColor(DockOverlayCross::WindowBackgroundColor); - const double devicePixelRatio = q->window()->devicePixelRatioF(); - const QSizeF pixmapSize = size * devicePixelRatio; - QPixmap pixmap(pixmapSize.toSize()); - pixmap.fill(QColor(0, 0, 0, 0)); - - QPainter painter(&pixmap); - QPen pen = painter.pen(); - QRectF shadowRect(pixmap.rect()); - QRectF baseRect; - baseRect.setSize(shadowRect.size() * 0.7); - baseRect.moveCenter(shadowRect.center()); - - // Fill - QColor shadowColor = iconColor(DockOverlayCross::ShadowColor); - if (shadowColor.alpha() == 255) { - shadowColor.setAlpha(64); - } - painter.fillRect(shadowRect, shadowColor); - - // Drop area rect. - painter.save(); - QRectF areaRect; - QLineF areaLine; - QRectF nonAreaRect; - switch (dockWidgetArea) { - case TopDockWidgetArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); - nonAreaRect = QRectF(baseRect.x(), - shadowRect.height() * 0.5, - baseRect.width(), - baseRect.height() * 0.5); - areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); - break; - case RightDockWidgetArea: - areaRect = QRectF(shadowRect.width() * 0.5, - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - nonAreaRect = QRectF(baseRect.x(), - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); - break; - case BottomDockWidgetArea: - areaRect = QRectF(baseRect.x(), - shadowRect.height() * 0.5, - baseRect.width(), - baseRect.height() * 0.5); - nonAreaRect = QRectF(baseRect.x(), - baseRect.y(), - baseRect.width(), - baseRect.height() * 0.5); - areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); - break; - case LeftDockWidgetArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); - nonAreaRect = QRectF(shadowRect.width() * 0.5, - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); - break; - default: - break; - } - - const QSizeF baseSize = baseRect.size(); - if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { - baseRect = areaRect; - } - - painter.fillRect(baseRect, backgroundColor); - if (areaRect.isValid()) { - pen = painter.pen(); - pen.setColor(borderColor); - QColor color = iconColor(DockOverlayCross::OverlayColor); - if (color.alpha() == 255) { - color.setAlpha(64); - } - painter.setBrush(color); - painter.setPen(Qt::NoPen); - painter.drawRect(areaRect); - - pen = painter.pen(); - pen.setWidth(1); - pen.setColor(borderColor); - pen.setStyle(Qt::DashLine); - painter.setPen(pen); - painter.drawLine(areaLine); - } - painter.restore(); - - painter.save(); - // Draw outer border - pen = painter.pen(); - pen.setColor(borderColor); - pen.setWidth(1); - painter.setBrush(Qt::NoBrush); - painter.setPen(pen); - painter.drawRect(baseRect); - - // draw window title bar - painter.setBrush(borderColor); - const QRectF frameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); - painter.drawRect(frameRect); - painter.restore(); - - // Draw arrow for outer container drop indicators - if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { - QRectF arrowRect; - arrowRect.setSize(baseSize); - arrowRect.setWidth(arrowRect.width() / 4.6); - arrowRect.setHeight(arrowRect.height() / 2); - arrowRect.moveCenter(QPointF(0, 0)); - QPolygonF arrow; - arrow << arrowRect.topLeft() << QPointF(arrowRect.right(), arrowRect.center().y()) - << arrowRect.bottomLeft(); - painter.setPen(Qt::NoPen); - painter.setBrush(iconColor(DockOverlayCross::ArrowColor)); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); - - switch (dockWidgetArea) { - case TopDockWidgetArea: - painter.rotate(-90); - break; - case RightDockWidgetArea: - break; - case BottomDockWidgetArea: - painter.rotate(90); - break; - case LeftDockWidgetArea: - painter.rotate(180); - break; - default: - break; - } - - painter.drawPolygon(arrow); - } - - pixmap.setDevicePixelRatio(devicePixelRatio); - return pixmap; - } - }; // class DockOverlayCrossPrivate - - DockOverlay::DockOverlay(QWidget *parent, eMode mode) - : QFrame(parent) - , d(new DockOverlayPrivate(this)) + /** + * Stylehseet based icon colors + */ + QColor iconColor(DockOverlayCross::eIconColor colorIndex) { - d->m_mode = mode; - d->m_cross = new DockOverlayCross(this); + QColor color = m_iconColors[colorIndex]; + if (!color.isValid()) { + color = defaultIconColor(colorIndex); + m_iconColors[colorIndex] = color; + } + return color; + } - if (Utils::HostOsInfo::isLinuxHost()) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint - | Qt::X11BypassWindowManagerHint); + /** + * Helper function that returns the drop indicator width depending on the + * operating system + */ + qreal dropIndicatorWidth(QLabel *label) const + { + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + return 40; else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - - setWindowOpacity(1); - setWindowTitle("DockOverlay"); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); - - d->m_cross->setVisible(false); - setVisible(false); + return static_cast(label->fontMetrics().height()) * 3.f; } - DockOverlay::~DockOverlay() + QWidget *createDropIndicatorWidget(DockWidgetArea dockWidgetArea, DockOverlay::eMode mode) { - delete d; + QLabel *label = new QLabel(); + label->setObjectName("DockWidgetAreaLabel"); + + const qreal metric = dropIndicatorWidth(label); + const QSizeF size(metric, metric); + + label->setPixmap(createHighDpiDropIndicatorPixmap(size, dockWidgetArea, mode)); + label->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + label->setAttribute(Qt::WA_TranslucentBackground); + label->setProperty("dockWidgetArea", dockWidgetArea); + return label; } - void DockOverlay::setAllowedAreas(DockWidgetAreas areas) + void updateDropIndicatorIcon(QWidget *dropIndicatorWidget) { - if (areas == d->m_allowedAreas) - return; + QLabel *label = qobject_cast(dropIndicatorWidget); + const qreal metric = dropIndicatorWidth(label); + const QSizeF size(metric, metric); - d->m_allowedAreas = areas; - d->m_cross->reset(); + int area = label->property("dockWidgetArea").toInt(); + label->setPixmap(createHighDpiDropIndicatorPixmap(size, + static_cast(area), + m_mode)); // TODO } - DockWidgetAreas DockOverlay::allowedAreas() const + QPixmap createHighDpiDropIndicatorPixmap(const QSizeF &size, + DockWidgetArea dockWidgetArea, + DockOverlay::eMode mode) { - return d->m_allowedAreas; - } + const QColor borderColor = iconColor(DockOverlayCross::FrameColor); + const QColor backgroundColor = iconColor(DockOverlayCross::WindowBackgroundColor); + const double devicePixelRatio = q->window()->devicePixelRatioF(); + const QSizeF pixmapSize = size * devicePixelRatio; + QPixmap pixmap(pixmapSize.toSize()); + pixmap.fill(QColor(0, 0, 0, 0)); - DockWidgetArea DockOverlay::dropAreaUnderCursor() const - { - DockWidgetArea result = d->m_cross->cursorLocation(); - if (result != InvalidDockWidgetArea) - return result; + QPainter painter(&pixmap); + QPen pen = painter.pen(); + QRectF shadowRect(pixmap.rect()); + QRectF baseRect; + baseRect.setSize(shadowRect.size() * 0.7); + baseRect.moveCenter(shadowRect.center()); - DockAreaWidget *dockArea = qobject_cast(d->m_targetWidget.data()); - if (!dockArea) - return result; - - if (dockArea->allowedAreas().testFlag(CenterDockWidgetArea) - && !dockArea->titleBar()->isHidden() - && dockArea->titleBarGeometry().contains(dockArea->mapFromGlobal(QCursor::pos()))) - return CenterDockWidgetArea; - - return result; - } - - DockWidgetArea DockOverlay::visibleDropAreaUnderCursor() const - { - if (isHidden() || !d->m_dropPreviewEnabled) - return InvalidDockWidgetArea; - else - return dropAreaUnderCursor(); - } - - DockWidgetArea DockOverlay::showOverlay(QWidget *target) - { - if (d->m_targetWidget == target) { - // Hint: We could update geometry of overlay here. - DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); - if (dockWidgetArea != d->m_lastLocation) { - repaint(); - d->m_lastLocation = dockWidgetArea; - } - return dockWidgetArea; + // Fill + QColor shadowColor = iconColor(DockOverlayCross::ShadowColor); + if (shadowColor.alpha() == 255) { + shadowColor.setAlpha(64); } + painter.fillRect(shadowRect, shadowColor); - d->m_targetWidget = target; - d->m_lastLocation = InvalidDockWidgetArea; - - // Move it over the target. - hide(); - resize(target->size()); - QPoint topLeft = target->mapToGlobal(target->rect().topLeft()); - move(topLeft); - show(); - d->m_cross->updatePosition(); - d->m_cross->updateOverlayIcons(); - return dropAreaUnderCursor(); - } - - void DockOverlay::hideOverlay() - { - hide(); - d->m_targetWidget.clear(); - d->m_lastLocation = InvalidDockWidgetArea; - d->m_dropAreaRect = QRect(); - } - - void DockOverlay::enableDropPreview(bool enable) - { - d->m_dropPreviewEnabled = enable; - update(); - } - - bool DockOverlay::dropPreviewEnabled() const - { - return d->m_dropPreviewEnabled; - } - - void DockOverlay::paintEvent(QPaintEvent *event) - { - Q_UNUSED(event) - // Draw rect based on location - if (!d->m_dropPreviewEnabled) { - d->m_dropAreaRect = QRect(); - return; - } - - QRect rectangle = rect(); - const DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); - double factor = (DockOverlay::ModeContainerOverlay == d->m_mode) ? 3 : 2; - + // Drop area rect. + painter.save(); + QRectF areaRect; + QLineF areaLine; + QRectF nonAreaRect; switch (dockWidgetArea) { case TopDockWidgetArea: - rectangle.setHeight(static_cast(rectangle.height() / factor)); + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); + nonAreaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); break; case RightDockWidgetArea: - rectangle.setX(static_cast(rectangle.width() * (1 - 1 / factor))); + areaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + nonAreaRect + = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); + areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); break; case BottomDockWidgetArea: - rectangle.setY(static_cast(rectangle.height() * (1 - 1 / factor))); + areaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + nonAreaRect + = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); + areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); break; case LeftDockWidgetArea: - rectangle.setWidth(static_cast(rectangle.width() / factor)); - break; - case CenterDockWidgetArea: - rectangle = rect(); + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); + nonAreaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); break; default: - return; + break; } - QPainter painter(this); - QColor color = palette().color(QPalette::Active, QPalette::Highlight); - QPen pen = painter.pen(); - pen.setColor(color.darker(120)); - pen.setStyle(Qt::SolidLine); + + const QSizeF baseSize = baseRect.size(); + if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { + baseRect = areaRect; + } + + painter.fillRect(baseRect, backgroundColor); + if (areaRect.isValid()) { + pen = painter.pen(); + pen.setColor(borderColor); + QColor color = iconColor(DockOverlayCross::OverlayColor); + if (color.alpha() == 255) { + color.setAlpha(64); + } + painter.setBrush(color); + painter.setPen(Qt::NoPen); + painter.drawRect(areaRect); + + pen = painter.pen(); + pen.setWidth(1); + pen.setColor(borderColor); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawLine(areaLine); + } + painter.restore(); + + painter.save(); + // Draw outer border + pen = painter.pen(); + pen.setColor(borderColor); pen.setWidth(1); - pen.setCosmetic(true); + painter.setBrush(Qt::NoBrush); painter.setPen(pen); - color = color.lighter(130); - color.setAlpha(64); - painter.setBrush(color); - painter.drawRect(rectangle.adjusted(0, 0, -1, -1)); - d->m_dropAreaRect = rectangle; + painter.drawRect(baseRect); + + // draw window title bar + painter.setBrush(borderColor); + const QRectF frameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); + painter.drawRect(frameRect); + painter.restore(); + + // Draw arrow for outer container drop indicators + if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { + QRectF arrowRect; + arrowRect.setSize(baseSize); + arrowRect.setWidth(arrowRect.width() / 4.6); + arrowRect.setHeight(arrowRect.height() / 2); + arrowRect.moveCenter(QPointF(0, 0)); + QPolygonF arrow; + arrow << arrowRect.topLeft() << QPointF(arrowRect.right(), arrowRect.center().y()) + << arrowRect.bottomLeft(); + painter.setPen(Qt::NoPen); + painter.setBrush(iconColor(DockOverlayCross::ArrowColor)); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); + + switch (dockWidgetArea) { + case TopDockWidgetArea: + painter.rotate(-90); + break; + case RightDockWidgetArea: + break; + case BottomDockWidgetArea: + painter.rotate(90); + break; + case LeftDockWidgetArea: + painter.rotate(180); + break; + default: + break; + } + + painter.drawPolygon(arrow); + } + + pixmap.setDevicePixelRatio(devicePixelRatio); + return pixmap; } +}; // class DockOverlayCrossPrivate - QRect DockOverlay::dropOverlayRect() const - { - return d->m_dropAreaRect; - } +DockOverlay::DockOverlay(QWidget *parent, eMode mode) + : QFrame(parent) + , d(new DockOverlayPrivate(this)) +{ + d->m_mode = mode; + d->m_cross = new DockOverlayCross(this); - void DockOverlay::showEvent(QShowEvent *event) - { - d->m_cross->show(); - QFrame::showEvent(event); - } + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - void DockOverlay::hideEvent(QHideEvent *event) - { - d->m_cross->hide(); - QFrame::hideEvent(event); - } + setWindowOpacity(1); + setWindowTitle("DockOverlay"); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); - bool DockOverlay::event(QEvent *event) - { - bool result = Super::event(event); - if (event->type() == QEvent::Polish) - d->m_cross->setupOverlayCross(d->m_mode); + d->m_cross->setVisible(false); + setVisible(false); +} +DockOverlay::~DockOverlay() +{ + delete d; +} + +void DockOverlay::setAllowedAreas(DockWidgetAreas areas) +{ + if (areas == d->m_allowedAreas) + return; + + d->m_allowedAreas = areas; + d->m_cross->reset(); +} + +DockWidgetAreas DockOverlay::allowedAreas() const +{ + return d->m_allowedAreas; +} + +DockWidgetArea DockOverlay::dropAreaUnderCursor() const +{ + DockWidgetArea result = d->m_cross->cursorLocation(); + if (result != InvalidDockWidgetArea) return result; + + DockAreaWidget *dockArea = qobject_cast(d->m_targetWidget.data()); + if (!dockArea) + return result; + + if (dockArea->allowedAreas().testFlag(CenterDockWidgetArea) && !dockArea->titleBar()->isHidden() + && dockArea->titleBarGeometry().contains(dockArea->mapFromGlobal(QCursor::pos()))) + return CenterDockWidgetArea; + + return result; +} + +DockWidgetArea DockOverlay::visibleDropAreaUnderCursor() const +{ + if (isHidden() || !d->m_dropPreviewEnabled) + return InvalidDockWidgetArea; + else + return dropAreaUnderCursor(); +} + +DockWidgetArea DockOverlay::showOverlay(QWidget *target) +{ + if (d->m_targetWidget == target) { + // Hint: We could update geometry of overlay here. + DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + if (dockWidgetArea != d->m_lastLocation) { + repaint(); + d->m_lastLocation = dockWidgetArea; + } + return dockWidgetArea; } - static int areaAlignment(const DockWidgetArea area) - { + d->m_targetWidget = target; + d->m_lastLocation = InvalidDockWidgetArea; + + // Move it over the target. + hide(); + resize(target->size()); + QPoint topLeft = target->mapToGlobal(target->rect().topLeft()); + move(topLeft); + show(); + d->m_cross->updatePosition(); + d->m_cross->updateOverlayIcons(); + return dropAreaUnderCursor(); +} + +void DockOverlay::hideOverlay() +{ + hide(); + d->m_targetWidget.clear(); + d->m_lastLocation = InvalidDockWidgetArea; + d->m_dropAreaRect = QRect(); +} + +void DockOverlay::enableDropPreview(bool enable) +{ + d->m_dropPreviewEnabled = enable; + update(); +} + +bool DockOverlay::dropPreviewEnabled() const +{ + return d->m_dropPreviewEnabled; +} + +void DockOverlay::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + // Draw rect based on location + if (!d->m_dropPreviewEnabled) { + d->m_dropAreaRect = QRect(); + return; + } + + QRect rectangle = rect(); + const DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + double factor = (DockOverlay::ModeContainerOverlay == d->m_mode) ? 3 : 2; + + switch (dockWidgetArea) { + case TopDockWidgetArea: + rectangle.setHeight(static_cast(rectangle.height() / factor)); + break; + case RightDockWidgetArea: + rectangle.setX(static_cast(rectangle.width() * (1 - 1 / factor))); + break; + case BottomDockWidgetArea: + rectangle.setY(static_cast(rectangle.height() * (1 - 1 / factor))); + break; + case LeftDockWidgetArea: + rectangle.setWidth(static_cast(rectangle.width() / factor)); + break; + case CenterDockWidgetArea: + rectangle = rect(); + break; + default: + return; + } + QPainter painter(this); + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rectangle.adjusted(0, 0, -1, -1)); + d->m_dropAreaRect = rectangle; +} + +QRect DockOverlay::dropOverlayRect() const +{ + return d->m_dropAreaRect; +} + +void DockOverlay::showEvent(QShowEvent *event) +{ + d->m_cross->show(); + QFrame::showEvent(event); +} + +void DockOverlay::hideEvent(QHideEvent *event) +{ + d->m_cross->hide(); + QFrame::hideEvent(event); +} + +bool DockOverlay::event(QEvent *event) +{ + bool result = Super::event(event); + if (event->type() == QEvent::Polish) + d->m_cross->setupOverlayCross(d->m_mode); + + return result; +} + +static int areaAlignment(const DockWidgetArea area) +{ + switch (area) { + case TopDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignBottom; + case RightDockWidgetArea: + return Qt::AlignLeft | Qt::AlignVCenter; + case BottomDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignTop; + case LeftDockWidgetArea: + return Qt::AlignRight | Qt::AlignVCenter; + case CenterDockWidgetArea: + return Qt::AlignCenter; + default: + return Qt::AlignCenter; + } +} + +// DockOverlayCrossPrivate +QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) +{ + if (DockOverlay::ModeDockAreaOverlay == m_mode) { switch (area) { case TopDockWidgetArea: - return Qt::AlignHCenter | Qt::AlignBottom; + return QPoint(1, 2); case RightDockWidgetArea: - return Qt::AlignLeft | Qt::AlignVCenter; + return QPoint(2, 3); case BottomDockWidgetArea: - return Qt::AlignHCenter | Qt::AlignTop; + return QPoint(3, 2); case LeftDockWidgetArea: - return Qt::AlignRight | Qt::AlignVCenter; + return QPoint(2, 1); case CenterDockWidgetArea: - return Qt::AlignCenter; + return QPoint(2, 2); default: - return Qt::AlignCenter; + return QPoint(); + } + } else { + switch (area) { + case TopDockWidgetArea: + return QPoint(0, 2); + case RightDockWidgetArea: + return QPoint(2, 4); + case BottomDockWidgetArea: + return QPoint(4, 2); + case LeftDockWidgetArea: + return QPoint(2, 0); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } +} + +DockOverlayCross::DockOverlayCross(DockOverlay *overlay) + : QWidget(overlay->parentWidget()) + , d(new DockOverlayCrossPrivate(this)) +{ + d->m_dockOverlay = overlay; + + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + + setWindowTitle("DockOverlayCross"); + setAttribute(Qt::WA_TranslucentBackground); + + d->m_gridLayout = new QGridLayout(); + d->m_gridLayout->setSpacing(0); + setLayout(d->m_gridLayout); +} + +DockOverlayCross::~DockOverlayCross() +{ + delete d; +} + +void DockOverlayCross::setupOverlayCross(DockOverlay::eMode mode) +{ + d->m_mode = mode; + + QHash areaWidgets; + areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, mode)); + areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, mode)); + areaWidgets.insert(BottomDockWidgetArea, + d->createDropIndicatorWidget(BottomDockWidgetArea, mode)); + areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, mode)); + areaWidgets.insert(CenterDockWidgetArea, + d->createDropIndicatorWidget(CenterDockWidgetArea, mode)); + d->m_lastDevicePixelRatio = devicePixelRatioF(); + setAreaWidgets(areaWidgets); + d->m_updateRequired = false; +} + +void DockOverlayCross::updateOverlayIcons() +{ + if (windowHandle()->devicePixelRatio() == d->m_lastDevicePixelRatio) // TODO + return; + + for (auto widget : std::as_const(d->m_dropIndicatorWidgets)) + d->updateDropIndicatorIcon(widget); + + d->m_lastDevicePixelRatio = devicePixelRatioF(); +} + +void DockOverlayCross::setIconColor(eIconColor colorIndex, const QColor &color) +{ + d->m_iconColors[colorIndex] = color; + d->m_updateRequired = true; +} + +QColor DockOverlayCross::iconColor(eIconColor colorIndex) const +{ + return d->m_iconColors[colorIndex]; +} + +void DockOverlayCross::setAreaWidgets(const QHash &widgets) +{ + // Delete old widgets. + const auto values = d->m_dropIndicatorWidgets.values(); + for (auto widget : values) { + d->m_gridLayout->removeWidget(widget); + delete widget; + } + d->m_dropIndicatorWidgets.clear(); + + // Insert new widgets into grid. + d->m_dropIndicatorWidgets = widgets; + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { + const DockWidgetArea area = constIt.key(); + QWidget *widget = constIt.value(); + const QPoint position = d->areaGridPosition(area); + d->m_gridLayout->addWidget(widget, + position.x(), + position.y(), + static_cast(areaAlignment(area))); + } + + if (DockOverlay::ModeDockAreaOverlay == d->m_mode) { + d->m_gridLayout->setContentsMargins(0, 0, 0, 0); + d->m_gridLayout->setRowStretch(0, 1); + d->m_gridLayout->setRowStretch(1, 0); + d->m_gridLayout->setRowStretch(2, 0); + d->m_gridLayout->setRowStretch(3, 0); + d->m_gridLayout->setRowStretch(4, 1); + + d->m_gridLayout->setColumnStretch(0, 1); + d->m_gridLayout->setColumnStretch(1, 0); + d->m_gridLayout->setColumnStretch(2, 0); + d->m_gridLayout->setColumnStretch(3, 0); + d->m_gridLayout->setColumnStretch(4, 1); + } else { + d->m_gridLayout->setContentsMargins(4, 4, 4, 4); + d->m_gridLayout->setRowStretch(0, 0); + d->m_gridLayout->setRowStretch(1, 1); + d->m_gridLayout->setRowStretch(2, 1); + d->m_gridLayout->setRowStretch(3, 1); + d->m_gridLayout->setRowStretch(4, 0); + + d->m_gridLayout->setColumnStretch(0, 0); + d->m_gridLayout->setColumnStretch(1, 1); + d->m_gridLayout->setColumnStretch(2, 1); + d->m_gridLayout->setColumnStretch(3, 1); + d->m_gridLayout->setColumnStretch(4, 0); + } + reset(); +} + +DockWidgetArea DockOverlayCross::cursorLocation() const +{ + const QPoint position = mapFromGlobal(QCursor::pos()); + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { + if (d->m_dockOverlay->allowedAreas().testFlag(constIt.key()) && constIt.value() + && constIt.value()->isVisible() && constIt.value()->geometry().contains(position)) { + return constIt.key(); } } - // DockOverlayCrossPrivate - QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) - { - if (DockOverlay::ModeDockAreaOverlay == m_mode) { - switch (area) { - case TopDockWidgetArea: - return QPoint(1, 2); - case RightDockWidgetArea: - return QPoint(2, 3); - case BottomDockWidgetArea: - return QPoint(3, 2); - case LeftDockWidgetArea: - return QPoint(2, 1); - case CenterDockWidgetArea: - return QPoint(2, 2); - default: - return QPoint(); - } - } else { - switch (area) { - case TopDockWidgetArea: - return QPoint(0, 2); - case RightDockWidgetArea: - return QPoint(2, 4); - case BottomDockWidgetArea: - return QPoint(4, 2); - case LeftDockWidgetArea: - return QPoint(2, 0); - case CenterDockWidgetArea: - return QPoint(2, 2); - default: - return QPoint(); - } - } + return InvalidDockWidgetArea; +} + +void DockOverlayCross::showEvent(QShowEvent *) +{ + if (d->m_updateRequired) + setupOverlayCross(d->m_mode); + + this->updatePosition(); +} + +void DockOverlayCross::updatePosition() +{ + resize(d->m_dockOverlay->size()); + QPoint topLeft = d->m_dockOverlay->pos(); + QPoint offest((this->width() - d->m_dockOverlay->width()) / 2, + (this->height() - d->m_dockOverlay->height()) / 2); + QPoint crossTopLeft = topLeft - offest; + move(crossTopLeft); +} + +void DockOverlayCross::reset() +{ + const QList allAreas{TopDockWidgetArea, + RightDockWidgetArea, + BottomDockWidgetArea, + LeftDockWidgetArea, + CenterDockWidgetArea}; + const DockWidgetAreas allowedAreas = d->m_dockOverlay->allowedAreas(); + + // Update visibility of area widgets based on allowedAreas. + for (auto area : allAreas) { + const QPoint position = d->areaGridPosition(area); + QLayoutItem *item = d->m_gridLayout->itemAtPosition(position.x(), position.y()); + QWidget *widget = nullptr; + if (item && (widget = item->widget()) != nullptr) + widget->setVisible(allowedAreas.testFlag(area)); + } +} + +void DockOverlayCross::setIconColors(const QString &colors) +{ + static const QMap + colorCompenentStringMap{{"Frame", DockOverlayCross::FrameColor}, + {"Background", DockOverlayCross::WindowBackgroundColor}, + {"Overlay", DockOverlayCross::OverlayColor}, + {"Arrow", DockOverlayCross::ArrowColor}, + {"Shadow", DockOverlayCross::ShadowColor}}; + + auto colorList = colors.split(' ', Qt::SkipEmptyParts); + for (const auto &colorListEntry : colorList) { + auto componentColor = colorListEntry.split('=', Qt::SkipEmptyParts); + int component = colorCompenentStringMap.value(componentColor[0], -1); + if (component < 0) + continue; + + d->m_iconColors[component] = QColor(componentColor[1]); } - DockOverlayCross::DockOverlayCross(DockOverlay *overlay) - : QWidget(overlay->parentWidget()) - , d(new DockOverlayCrossPrivate(this)) - { - d->m_dockOverlay = overlay; + d->m_updateRequired = true; +} - if (Utils::HostOsInfo::isLinuxHost()) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint - | Qt::X11BypassWindowManagerHint); - else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - - setWindowTitle("DockOverlayCross"); - setAttribute(Qt::WA_TranslucentBackground); - - d->m_gridLayout = new QGridLayout(); - d->m_gridLayout->setSpacing(0); - setLayout(d->m_gridLayout); - } - - DockOverlayCross::~DockOverlayCross() - { - delete d; - } - - void DockOverlayCross::setupOverlayCross(DockOverlay::eMode mode) - { - d->m_mode = mode; - - QHash areaWidgets; - areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, mode)); - areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, mode)); - areaWidgets.insert(BottomDockWidgetArea, - d->createDropIndicatorWidget(BottomDockWidgetArea, mode)); - areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, mode)); - areaWidgets.insert(CenterDockWidgetArea, - d->createDropIndicatorWidget(CenterDockWidgetArea, mode)); - d->m_lastDevicePixelRatio = devicePixelRatioF(); - setAreaWidgets(areaWidgets); - d->m_updateRequired = false; - } - - void DockOverlayCross::updateOverlayIcons() - { - if (windowHandle()->devicePixelRatio() == d->m_lastDevicePixelRatio) // TODO - return; - - for (auto widget : std::as_const(d->m_dropIndicatorWidgets)) - d->updateDropIndicatorIcon(widget); - - d->m_lastDevicePixelRatio = devicePixelRatioF(); - } - - void DockOverlayCross::setIconColor(eIconColor colorIndex, const QColor &color) - { - d->m_iconColors[colorIndex] = color; - d->m_updateRequired = true; - } - - QColor DockOverlayCross::iconColor(eIconColor colorIndex) const - { - return d->m_iconColors[colorIndex]; - } - - void DockOverlayCross::setAreaWidgets(const QHash &widgets) - { - // Delete old widgets. - const auto values = d->m_dropIndicatorWidgets.values(); - for (auto widget : values) { - d->m_gridLayout->removeWidget(widget); - delete widget; - } - d->m_dropIndicatorWidgets.clear(); - - // Insert new widgets into grid. - d->m_dropIndicatorWidgets = widgets; - - const QHash areas = d->m_dropIndicatorWidgets; - QHash::const_iterator constIt; - for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { - const DockWidgetArea area = constIt.key(); - QWidget *widget = constIt.value(); - const QPoint position = d->areaGridPosition(area); - d->m_gridLayout->addWidget(widget, - position.x(), - position.y(), - static_cast(areaAlignment(area))); - } - - if (DockOverlay::ModeDockAreaOverlay == d->m_mode) { - d->m_gridLayout->setContentsMargins(0, 0, 0, 0); - d->m_gridLayout->setRowStretch(0, 1); - d->m_gridLayout->setRowStretch(1, 0); - d->m_gridLayout->setRowStretch(2, 0); - d->m_gridLayout->setRowStretch(3, 0); - d->m_gridLayout->setRowStretch(4, 1); - - d->m_gridLayout->setColumnStretch(0, 1); - d->m_gridLayout->setColumnStretch(1, 0); - d->m_gridLayout->setColumnStretch(2, 0); - d->m_gridLayout->setColumnStretch(3, 0); - d->m_gridLayout->setColumnStretch(4, 1); - } else { - d->m_gridLayout->setContentsMargins(4, 4, 4, 4); - d->m_gridLayout->setRowStretch(0, 0); - d->m_gridLayout->setRowStretch(1, 1); - d->m_gridLayout->setRowStretch(2, 1); - d->m_gridLayout->setRowStretch(3, 1); - d->m_gridLayout->setRowStretch(4, 0); - - d->m_gridLayout->setColumnStretch(0, 0); - d->m_gridLayout->setColumnStretch(1, 1); - d->m_gridLayout->setColumnStretch(2, 1); - d->m_gridLayout->setColumnStretch(3, 1); - d->m_gridLayout->setColumnStretch(4, 0); - } - reset(); - } - - DockWidgetArea DockOverlayCross::cursorLocation() const - { - const QPoint position = mapFromGlobal(QCursor::pos()); - - const QHash areas = d->m_dropIndicatorWidgets; - QHash::const_iterator constIt; - for (constIt = areas.begin(); constIt != areas.end(); ++constIt) - { - if (d->m_dockOverlay->allowedAreas().testFlag(constIt.key()) && constIt.value() - && constIt.value()->isVisible() && constIt.value()->geometry().contains(position)) { - return constIt.key(); - } - } - - return InvalidDockWidgetArea; - } - - void DockOverlayCross::showEvent(QShowEvent *) - { - if (d->m_updateRequired) - setupOverlayCross(d->m_mode); - - this->updatePosition(); - } - - void DockOverlayCross::updatePosition() - { - resize(d->m_dockOverlay->size()); - QPoint topLeft = d->m_dockOverlay->pos(); - QPoint offest((this->width() - d->m_dockOverlay->width()) / 2, - (this->height() - d->m_dockOverlay->height()) / 2); - QPoint crossTopLeft = topLeft - offest; - move(crossTopLeft); - } - - void DockOverlayCross::reset() - { - const QList allAreas{TopDockWidgetArea, - RightDockWidgetArea, - BottomDockWidgetArea, - LeftDockWidgetArea, - CenterDockWidgetArea}; - const DockWidgetAreas allowedAreas = d->m_dockOverlay->allowedAreas(); - - // Update visibility of area widgets based on allowedAreas. - for (auto area : allAreas) { - const QPoint position = d->areaGridPosition(area); - QLayoutItem *item = d->m_gridLayout->itemAtPosition(position.x(), position.y()); - QWidget *widget = nullptr; - if (item && (widget = item->widget()) != nullptr) - widget->setVisible(allowedAreas.testFlag(area)); - } - } - - void DockOverlayCross::setIconColors(const QString &colors) - { - static const QMap - colorCompenentStringMap{{"Frame", DockOverlayCross::FrameColor}, - {"Background", DockOverlayCross::WindowBackgroundColor}, - {"Overlay", DockOverlayCross::OverlayColor}, - {"Arrow", DockOverlayCross::ArrowColor}, - {"Shadow", DockOverlayCross::ShadowColor}}; - - auto colorList = colors.split(' ', Qt::SkipEmptyParts); - for (const auto &colorListEntry : colorList) { - auto componentColor = colorListEntry.split('=', Qt::SkipEmptyParts); - int component = colorCompenentStringMap.value(componentColor[0], -1); - if (component < 0) - continue; - - d->m_iconColors[component] = QColor(componentColor[1]); - } - - d->m_updateRequired = true; - } - - QString DockOverlayCross::iconColors() const - { - return QString(); - } +QString DockOverlayCross::iconColors() const +{ + return QString(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index a10161da596..a9207e0c08d 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -12,61 +12,70 @@ #include #include -namespace ADS +namespace ADS { +/** + * Private dock splitter data + */ +struct DockSplitterPrivate { - /** - * Private dock splitter data - */ - struct DockSplitterPrivate - { - DockSplitter *q; - int m_visibleContentCount = 0; + DockSplitter *q; + int m_visibleContentCount = 0; - DockSplitterPrivate(DockSplitter *parent) - : q(parent) - {} - }; - - DockSplitter::DockSplitter(QWidget *parent) - : QSplitter(parent) - , d(new DockSplitterPrivate(this)) - { - //setProperty("ads-splitter", true); // TODO - setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); - setChildrenCollapsible(false); - } - - DockSplitter::DockSplitter(Qt::Orientation orientation, QWidget *parent) - : QSplitter(orientation, parent) - , d(new DockSplitterPrivate(this)) + DockSplitterPrivate(DockSplitter *parent) + : q(parent) {} +}; - DockSplitter::~DockSplitter() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } +DockSplitter::DockSplitter(QWidget *parent) + : QSplitter(parent) + , d(new DockSplitterPrivate(this)) +{ + //setProperty("ads-splitter", true); // TODO + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); + setChildrenCollapsible(false); +} - bool DockSplitter::hasVisibleContent() const - { - // TODO Cache or precalculate this to speed up - for (int i = 0; i < count(); ++i) { - if (!widget(i)->isHidden()) { - return true; - } +DockSplitter::DockSplitter(Qt::Orientation orientation, QWidget *parent) + : QSplitter(orientation, parent) + , d(new DockSplitterPrivate(this)) +{} + +DockSplitter::~DockSplitter() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +bool DockSplitter::hasVisibleContent() const +{ + // TODO Cache or precalculate this to speed up + for (int i = 0; i < count(); ++i) { + if (!widget(i)->isHidden()) { + return true; } - - return false; } - QWidget *DockSplitter::firstWidget() const - { - return (count() > 0) ? widget(0) : nullptr; + return false; +} + +QWidget *DockSplitter::firstWidget() const +{ + return (count() > 0) ? widget(0) : nullptr; +} + +QWidget *DockSplitter::lastWidget() const +{ + return (count() > 0) ? widget(count() - 1) : nullptr; +} + +bool DockSplitter::isResizingWithContainer() const +{ + for (auto area : findChildren()) { + if (area->isCentralWidgetArea()) + return true; } - QWidget *DockSplitter::lastWidget() const - { - return (count() > 0) ? widget(count() - 1) : nullptr; - } + return false; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/docksplitter.h b/src/libs/advanceddockingsystem/docksplitter.h index 8dffc234ad4..c78bfd0b45f 100644 --- a/src/libs/advanceddockingsystem/docksplitter.h +++ b/src/libs/advanceddockingsystem/docksplitter.h @@ -45,6 +45,11 @@ public: * Returns last widget of nullptr is splitter is empty */ QWidget *lastWidget() const; + + /** + * Returns true if the splitter contains central widget of dock manager. + */ + bool isResizingWithContainer() const; }; // class DockSplitter } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index ea680149661..d0127cb8087 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -5,11 +5,13 @@ #include "ads_globals.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "autohidetab.h" #include "dockareawidget.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" #include "dockmanager.h" -#include "docksplitter.h" #include "dockwidgettab.h" #include "floatingdockcontainer.h" @@ -23,643 +25,864 @@ #include #include #include -#include #include +#include -namespace ADS +namespace ADS { +/** + * Private data class of DockWidget class (pimpl) + */ +class DockWidgetPrivate { +public: + struct WidgetFactory + { + DockWidget::FactoryFunc createWidget; + DockWidget::eInsertMode insertMode; + }; + + DockWidget *q = nullptr; + QBoxLayout *m_layout = nullptr; + QWidget *m_widget = nullptr; + DockWidgetTab *m_tabWidget = nullptr; + DockWidget::DockWidgetFeatures m_features = DockWidget::DefaultDockWidgetFeatures; + DockManager *m_dockManager = nullptr; + DockAreaWidget *m_dockArea = nullptr; + QAction *m_toggleViewAction = nullptr; + bool m_closed = false; + QScrollArea *m_scrollArea = nullptr; + QToolBar *m_toolBar = nullptr; + Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize m_toolBarIconSizeDocked = QSize(16, 16); + QSize m_toolBarIconSizeFloating = QSize(24, 24); + bool m_isFloatingTopLevel = false; + QList m_titleBarActions; + DockWidget::eMinimumSizeHintMode m_minimumSizeHintMode + = DockWidget::MinimumSizeHintFromDockWidget; + WidgetFactory *m_factory = nullptr; + QPointer m_sideTabWidget; + /** - * Private data class of DockWidget class (pimpl) + * Private data constructor */ - class DockWidgetPrivate - { - public: - DockWidget *q = nullptr; - QBoxLayout *m_layout = nullptr; - QWidget *m_widget = nullptr; - DockWidgetTab *m_tabWidget = nullptr; - DockWidget::DockWidgetFeatures m_features = DockWidget::DefaultDockWidgetFeatures; - DockManager *m_dockManager = nullptr; - DockAreaWidget *m_dockArea = nullptr; - QAction *m_toggleViewAction = nullptr; - bool m_closed = false; - QScrollArea *m_scrollArea = nullptr; - QToolBar *m_toolBar = nullptr; - Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; - Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; - QSize m_toolBarIconSizeDocked = QSize(16, 16); - QSize m_toolBarIconSizeFloating = QSize(24, 24); - bool m_isFloatingTopLevel = false; - QList m_titleBarActions; - DockWidget::eMinimumSizeHintMode m_minimumSizeHintMode = DockWidget::MinimumSizeHintFromDockWidget; + DockWidgetPrivate(DockWidget *parent); - /** - * Private data constructor - */ - DockWidgetPrivate(DockWidget *parent); + /** + * Show dock widget + */ + void showDockWidget(); - /** - * Show dock widget - */ - void showDockWidget(); + /** + * Hide dock widget. + */ + void hideDockWidget(); - /** - * Hide dock widget. - */ - void hideDockWidget(); + /** + * Hides a dock area if all dock widgets in the area are closed. This function updates the + * current selected tab and hides the parent dock area if it is empty. + */ + void updateParentDockArea(); - /** - * Hides a dock area if all dock widgets in the area are closed. - * This function updates the current selected tab and hides the parent - * dock area if it is empty - */ - void updateParentDockArea(); + /** + * Closes all auto hide dock widgets if there are no more opened dock areas. This prevents the + * auto hide dock widgets from being pinned to an empty dock area. + */ + void closeAutoHideDockWidgetsIfNeeded(); - /** - * Setup the top tool bar - */ - void setupToolBar(); + /** + * Setup the top tool bar + */ + void setupToolBar(); - /** - * Setup the main scroll area - */ - void setupScrollArea(); - }; // class DockWidgetPrivate + /** + * Setup the main scroll area + */ + void setupScrollArea(); - DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) - : q(parent) - {} + /** + * Creates the content widget with the registered widget factory and returns true on success. + */ + bool createWidgetFromFactory(); +}; // class DockWidgetPrivate - void DockWidgetPrivate::showDockWidget() - { - if (!m_dockArea) { - FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); - floatingWidget->resize(q->size()); - m_tabWidget->show(); +DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) + : q(parent) +{} + +void DockWidgetPrivate::showDockWidget() +{ + if (!m_widget) { + if (!createWidgetFromFactory()) { + Q_ASSERT(!m_features.testFlag(DockWidget::DeleteContentOnClose) + && "DeleteContentOnClose flag was set, but the widget " + "factory is missing or it doesn't return a valid QWidget."); + return; + } + } + + if (!m_dockArea) { + FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); + // We use the size hint of the content widget to provide a good initial size + floatingWidget->resize(m_widget ? m_widget->sizeHint() : q->size()); + m_tabWidget->show(); + floatingWidget->show(); + } else { + m_dockArea->setCurrentDockWidget(q); + m_dockArea->toggleView(true); + m_tabWidget->show(); + QSplitter *splitter = internal::findParent(m_dockArea); + while (splitter && !splitter->isVisible() && !m_dockArea->isAutoHide()) { + splitter->show(); + splitter = internal::findParent(splitter); + } + + DockContainerWidget *container = m_dockArea->dockContainer(); + if (container->isFloating()) { + FloatingDockContainer *floatingWidget = internal::findParent( + container); floatingWidget->show(); - } else { - m_dockArea->setCurrentDockWidget(q); - m_dockArea->toggleView(true); - m_tabWidget->show(); - QSplitter *splitter = internal::findParent(m_dockArea); - while (splitter && !splitter->isVisible()) { - splitter->show(); - splitter = internal::findParent(splitter); - } - - DockContainerWidget *container = m_dockArea->dockContainer(); - if (container->isFloating()) { - FloatingDockContainer *floatingWidget - = internal::findParent(container); - floatingWidget->show(); - } } + + // If this widget is pinned and there are no opened dock widgets, unpin the auto hide widget + // by moving it's contents to parent container While restoring state, opened dock widgets + // are not valid + if (container->openedDockWidgets().count() == 0 && m_dockArea->isAutoHide() + && !m_dockManager->isRestoringState()) + m_dockArea->autoHideDockContainer()->moveContentsToParent(); + } +} + +void DockWidgetPrivate::hideDockWidget() +{ + m_tabWidget->hide(); + updateParentDockArea(); + + closeAutoHideDockWidgetsIfNeeded(); + + if (m_features.testFlag(DockWidget::DeleteContentOnClose)) { + m_widget->deleteLater(); + m_widget = nullptr; + } +} + +void DockWidgetPrivate::updateParentDockArea() +{ + if (!m_dockArea) + return; + + // We don't need to change the current tab if the current DockWidget is not the one being closed + if (m_dockArea->currentDockWidget() != q) + return; + + auto nextDockWidget = m_dockArea->nextOpenDockWidget(q); + if (nextDockWidget) + m_dockArea->setCurrentDockWidget(nextDockWidget); + else + m_dockArea->hideAreaWithNoVisibleContent(); +} + +void DockWidgetPrivate::closeAutoHideDockWidgetsIfNeeded() +{ + auto dockContainer = q->dockContainer(); + if (!dockContainer) + return; + + if (q->dockManager()->isRestoringState()) + return; + + if (!dockContainer->openedDockWidgets().isEmpty()) + return; + + for (auto autoHideWidget : dockContainer->autoHideWidgets()) { + auto dockWidget = autoHideWidget->dockWidget(); + if (dockWidget == q) + continue; + + dockWidget->toggleView(false); + } +} + +void DockWidgetPrivate::setupToolBar() +{ + m_toolBar = new QToolBar(q); + m_toolBar->setObjectName("dockWidgetToolBar"); + m_layout->insertWidget(0, m_toolBar); + m_toolBar->setIconSize(QSize(16, 16)); + m_toolBar->toggleViewAction()->setEnabled(false); + m_toolBar->toggleViewAction()->setVisible(false); + QObject::connect(q, &DockWidget::topLevelChanged, q, &DockWidget::setToolbarFloatingStyle); +} + +void DockWidgetPrivate::setupScrollArea() +{ + m_scrollArea = new QScrollArea(q); + m_scrollArea->setObjectName("dockWidgetScrollArea"); + m_scrollArea->setWidgetResizable(true); + m_layout->addWidget(m_scrollArea); +} + +bool DockWidgetPrivate::createWidgetFromFactory() +{ + if (!m_features.testFlag(DockWidget::DeleteContentOnClose)) + return false; + + if (!m_factory) + return false; + + QWidget *w = m_factory->createWidget(q); + if (!w) + return false; + + q->setWidget(w, m_factory->insertMode); + return true; +} + +DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetPrivate(this)) +{ + d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setWindowTitle(uniqueId); // temporarily use unique id as title + setObjectName(uniqueId); + + d->m_tabWidget = componentsFactory()->createDockWidgetTab(this); + d->m_toggleViewAction = new QAction(uniqueId, this); + d->m_toggleViewAction->setCheckable(true); + connect(d->m_toggleViewAction, &QAction::triggered, this, [this](bool open) { + // If the toggle view action mode is ActionModeShow (== m_toggleViewAction isn't + // checkable, see setToggleViewActionMode()), then open is always true + toggleView(open || !d->m_toggleViewAction->isCheckable()); + }); + setToolbarFloatingStyle(false); + + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + setFocusPolicy(Qt::ClickFocus); +} + +DockWidget::~DockWidget() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void DockWidget::setToggleViewActionChecked(bool checked) +{ + d->m_toggleViewAction->setChecked(checked); +} + +void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) +{ + if (d->m_widget) + takeWidget(); + + auto scrollAreaWidget = qobject_cast(widget); + if (scrollAreaWidget || ForceNoScrollArea == insertMode) { + d->m_layout->addWidget(widget); + if (scrollAreaWidget && scrollAreaWidget->viewport()) + scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + } else { + d->setupScrollArea(); + d->m_scrollArea->setWidget(widget); } - void DockWidgetPrivate::hideDockWidget() - { - m_tabWidget->hide(); - updateParentDockArea(); + d->m_widget = widget; + d->m_widget->setProperty("dockWidgetContent", true); +} + +void DockWidget::setWidgetFactory(FactoryFunc createWidget, eInsertMode insertMode) +{ + if (d->m_factory) + delete d->m_factory; + + d->m_factory = new DockWidgetPrivate::WidgetFactory{createWidget, insertMode}; +} + +QWidget *DockWidget::takeWidget() +{ + QWidget *w = nullptr; + if (d->m_scrollArea) { + d->m_layout->removeWidget(d->m_scrollArea); + w = d->m_scrollArea->takeWidget(); + delete d->m_scrollArea; + d->m_scrollArea = nullptr; + d->m_widget = nullptr; + } else if (d->m_widget) { + d->m_layout->removeWidget(d->m_widget); + w = d->m_widget; + d->m_widget = nullptr; } - void DockWidgetPrivate::updateParentDockArea() - { - if (!m_dockArea) - return; + if (w) + w->setParent(nullptr); - // we don't need to change the current tab if the current DockWidget is not the one being closed - if (m_dockArea->currentDockWidget() != q) - return; + return w; +} - auto nextDockWidget = m_dockArea->nextOpenDockWidget(q); - if (nextDockWidget) - m_dockArea->setCurrentDockWidget(nextDockWidget); - else - m_dockArea->hideAreaWithNoVisibleContent(); - } +QWidget *DockWidget::widget() const +{ + return d->m_widget; +} - void DockWidgetPrivate::setupToolBar() - { - m_toolBar = new QToolBar(q); - m_toolBar->setObjectName("dockWidgetToolBar"); - m_layout->insertWidget(0, m_toolBar); - m_toolBar->setIconSize(QSize(16, 16)); - m_toolBar->toggleViewAction()->setEnabled(false); - m_toolBar->toggleViewAction()->setVisible(false); - QObject::connect(q, &DockWidget::topLevelChanged, q, &DockWidget::setToolbarFloatingStyle); - } +DockWidgetTab *DockWidget::tabWidget() const +{ + return d->m_tabWidget; +} - void DockWidgetPrivate::setupScrollArea() - { - m_scrollArea = new QScrollArea(q); - m_scrollArea->setObjectName("dockWidgetScrollArea"); - m_scrollArea->setWidgetResizable(true); - m_layout->addWidget(m_scrollArea); - } +AutoHideDockContainer *DockWidget::autoHideDockContainer() const +{ + if (!d->m_dockArea) + return nullptr; - DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) - : QFrame(parent) - , d(new DockWidgetPrivate(this)) - { - d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); - d->m_layout->setContentsMargins(0, 0, 0, 0); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - setWindowTitle(uniqueId); // temporarily use unique id as title - setObjectName(uniqueId); + return d->m_dockArea->autoHideDockContainer(); +} - d->m_tabWidget = componentsFactory()->createDockWidgetTab(this); - d->m_toggleViewAction = new QAction(uniqueId, this); +void DockWidget::setFeatures(DockWidgetFeatures features) +{ + if (d->m_features == features) + return; + + d->m_features = features; + emit featuresChanged(d->m_features); + d->m_tabWidget->onDockWidgetFeaturesChanged(); + + if (DockAreaWidget *dockArea = dockAreaWidget()) + dockArea->onDockWidgetFeaturesChanged(); +} + +void DockWidget::setFeature(DockWidgetFeature flag, bool on) +{ + auto currentFeatures = features(); + internal::setFlag(currentFeatures, flag, on); + setFeatures(currentFeatures); +} + +DockWidget::DockWidgetFeatures DockWidget::features() const +{ + return d->m_features; +} + +DockManager *DockWidget::dockManager() const +{ + return d->m_dockManager; +} + +void DockWidget::setDockManager(DockManager *dockManager) +{ + d->m_dockManager = dockManager; +} + +DockContainerWidget *DockWidget::dockContainer() const +{ + if (d->m_dockArea) + return d->m_dockArea->dockContainer(); + else + return nullptr; +} + +FloatingDockContainer *DockWidget::floatingDockContainer() const +{ + auto container = dockContainer(); + return container ? container->floatingWidget() : nullptr; +} + +DockAreaWidget *DockWidget::dockAreaWidget() const +{ + return d->m_dockArea; +} + +AutoHideTab *DockWidget::sideTabWidget() const +{ + return d->m_sideTabWidget; +} + +void DockWidget::setSideTabWidget(AutoHideTab *sideTab) const +{ + d->m_sideTabWidget = sideTab; +} + +bool DockWidget::isAutoHide() const +{ + return !d->m_sideTabWidget.isNull(); +} + +bool DockWidget::isFloating() const +{ + if (!isInFloatingContainer()) + return false; + + return dockContainer()->topLevelDockWidget() == this; +} + +bool DockWidget::isInFloatingContainer() const +{ + auto container = dockContainer(); + if (!container) + return false; + + if (!container->isFloating()) + return false; + + return true; +} + +bool DockWidget::isClosed() const +{ + return d->m_closed; +} + +QAction *DockWidget::toggleViewAction() const +{ + return d->m_toggleViewAction; +} + +void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) +{ + if (ActionModeToggle == mode) { d->m_toggleViewAction->setCheckable(true); - connect(d->m_toggleViewAction, &QAction::triggered, this, [this](bool open) { - // If the toggle view action mode is ActionModeShow (== m_toggleViewAction isn't - // checkable, see setToggleViewActionMode()), then open is always true - toggleView(open || !d->m_toggleViewAction->isCheckable()); - }); - setToolbarFloatingStyle(false); - - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - setFocusPolicy(Qt::ClickFocus); + d->m_toggleViewAction->setIcon(QIcon()); + } else { + d->m_toggleViewAction->setCheckable(false); + d->m_toggleViewAction->setIcon(d->m_tabWidget->icon()); } +} - DockWidget::~DockWidget() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } +void DockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode mode) +{ + d->m_minimumSizeHintMode = mode; +} - void DockWidget::setToggleViewActionChecked(bool checked) - { - d->m_toggleViewAction->setChecked(checked); - } +DockWidget::eMinimumSizeHintMode DockWidget::minimumSizeHintMode() const +{ + return d->m_minimumSizeHintMode; +} - void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) - { - if (d->m_widget) - takeWidget(); +bool DockWidget::isCentralWidget() const +{ + return dockManager()->centralWidget() == this; +} - auto scrollAreaWidget = qobject_cast(widget); - if (scrollAreaWidget || ForceNoScrollArea == insertMode) { - d->m_layout->addWidget(widget); - if (scrollAreaWidget && scrollAreaWidget->viewport()) - scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); - } else { - d->setupScrollArea(); - d->m_scrollArea->setWidget(widget); - } +void DockWidget::toggleView(bool open) +{ + // If the dock widget state is different, then we really need to toggle the state. If we are + // in the right state, then we simply make this dock widget the current dock widget. + auto autoHideContainer = autoHideDockContainer(); + if (d->m_closed != !open) + toggleViewInternal(open); + else if (open && d->m_dockArea && !autoHideContainer) + raise(); - d->m_widget = widget; - d->m_widget->setProperty("dockWidgetContent", true); - } + if (open && autoHideContainer) + autoHideContainer->collapseView(false); +} - QWidget *DockWidget::takeWidget() - { - QWidget *w = nullptr; - if (d->m_scrollArea) { - d->m_layout->removeWidget(d->m_scrollArea); - w = d->m_scrollArea->takeWidget(); - delete d->m_scrollArea; - d->m_scrollArea = nullptr; - d->m_widget = nullptr; - } else if (d->m_widget) { - d->m_layout->removeWidget(d->m_widget); - w = d->m_widget; - d->m_widget = nullptr; - } +void DockWidget::toggleViewInternal(bool open) +{ + const DockContainerWidget *const beforeDockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetBefore = beforeDockContainerWidget + ? beforeDockContainerWidget->topLevelDockWidget() + : nullptr; - if (w) - w->setParent(nullptr); + d->m_closed = !open; - return w; - } + if (open) + d->showDockWidget(); + else + d->hideDockWidget(); - QWidget *DockWidget::widget() const { return d->m_widget; } + //d->m_toggleViewAction->blockSignals(true); + d->m_toggleViewAction->setChecked(open); + //d->m_toggleViewAction->blockSignals(false); + if (d->m_dockArea) + d->m_dockArea->toggleDockWidgetView(this, open); - DockWidgetTab *DockWidget::tabWidget() const { return d->m_tabWidget; } + if (d->m_dockArea->isAutoHide()) + d->m_dockArea->autoHideDockContainer()->toggleView(open); - void DockWidget::setFeatures(DockWidgetFeatures features) - { - if (d->m_features == features) - return; + if (open && topLevelDockWidgetBefore) + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); - d->m_features = features; - emit featuresChanged(d->m_features); - d->m_tabWidget->onDockWidgetFeaturesChanged(); - } - - void DockWidget::setFeature(DockWidgetFeature flag, bool on) - { - auto currentFeatures = features(); - internal::setFlag(currentFeatures, flag, on); - setFeatures(currentFeatures); - } - - DockWidget::DockWidgetFeatures DockWidget::features() const { return d->m_features; } - - DockManager *DockWidget::dockManager() const { return d->m_dockManager; } - - void DockWidget::setDockManager(DockManager *dockManager) { d->m_dockManager = dockManager; } - - DockContainerWidget *DockWidget::dockContainer() const - { - if (d->m_dockArea) - return d->m_dockArea->dockContainer(); - else - return nullptr; - } - - DockAreaWidget *DockWidget::dockAreaWidget() const { return d->m_dockArea; } - - bool DockWidget::isFloating() const - { - if (!isInFloatingContainer()) - return false; - - return dockContainer()->topLevelDockWidget() == this; - } - - bool DockWidget::isInFloatingContainer() const - { - auto container = dockContainer(); - if (!container) - return false; - - if (!container->isFloating()) - return false; - - return true; - } - - bool DockWidget::isClosed() const { return d->m_closed; } - - QAction *DockWidget::toggleViewAction() const { return d->m_toggleViewAction; } - - void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) - { - if (ActionModeToggle == mode) { - d->m_toggleViewAction->setCheckable(true); - d->m_toggleViewAction->setIcon(QIcon()); - } else { - d->m_toggleViewAction->setCheckable(false); - d->m_toggleViewAction->setIcon(d->m_tabWidget->icon()); - } - } - - void DockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode mode) - { - d->m_minimumSizeHintMode = mode; - } - - void DockWidget::toggleView(bool open) - { - // If the dock widget state is different, then we really need to toggle - // the state. If we are in the right state, then we simply make this - // dock widget the current dock widget - if (d->m_closed != !open) - toggleViewInternal(open); - else if (open && d->m_dockArea) - d->m_dockArea->setCurrentDockWidget(this); - } - - void DockWidget::toggleViewInternal(bool open) - { - const DockContainerWidget *const beforeDockContainerWidget = dockContainer(); - DockWidget *topLevelDockWidgetBefore = beforeDockContainerWidget - ? beforeDockContainerWidget->topLevelDockWidget() + // Here we need to call the dockContainer() function again, because if + // this dock widget was unassigned before the call to showDockWidget() then + // it has a dock container now + const DockContainerWidget *const dockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetAfter = dockContainerWidget + ? dockContainerWidget->topLevelDockWidget() + : nullptr; + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetAfter, true); + FloatingDockContainer *floatingContainer = dockContainerWidget + ? dockContainerWidget->floatingWidget() : nullptr; + if (floatingContainer) + floatingContainer->updateWindowTitle(); - if (open) - d->showDockWidget(); - else - d->hideDockWidget(); + if (!open) + emit closed(); - d->m_closed = !open; - //d->m_toggleViewAction->blockSignals(true); - d->m_toggleViewAction->setChecked(open); - //d->m_toggleViewAction->blockSignals(false); - if (d->m_dockArea) - d->m_dockArea->toggleDockWidgetView(this, open); + emit viewToggled(open); +} - if (open && topLevelDockWidgetBefore) - DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); +void DockWidget::setDockArea(DockAreaWidget *dockArea) +{ + d->m_dockArea = dockArea; + d->m_toggleViewAction->setChecked(dockArea != nullptr && !isClosed()); + setParent(dockArea); +} - // Here we need to call the dockContainer() function again, because if - // this dock widget was unassigned before the call to showDockWidget() then - // it has a dock container now - const DockContainerWidget *const dockContainerWidget = dockContainer(); - DockWidget *topLevelDockWidgetAfter = dockContainerWidget - ? dockContainerWidget->topLevelDockWidget() - : nullptr; - DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetAfter, true); - FloatingDockContainer *floatingContainer = dockContainerWidget - ? dockContainerWidget->floatingWidget() - : nullptr; - if (floatingContainer) - floatingContainer->updateWindowTitle(); +void DockWidget::saveState(QXmlStreamWriter &stream) const +{ + stream.writeStartElement("widget"); + stream.writeAttribute("name", objectName()); + stream.writeAttribute("closed", QVariant::fromValue(d->m_closed).toString()); + stream.writeEndElement(); +} - if (!open) - emit closed(); +void DockWidget::flagAsUnassigned() +{ + d->m_closed = true; + setParent(d->m_dockManager); + setVisible(false); + setDockArea(nullptr); + tabWidget()->setParent(this); +} - emit viewToggled(open); - } +bool DockWidget::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Hide: + emit visibilityChanged(false); + break; - void DockWidget::setDockArea(DockAreaWidget *dockArea) - { - d->m_dockArea = dockArea; - d->m_toggleViewAction->setChecked(dockArea != nullptr && !this->isClosed()); - } + case QEvent::Show: + emit visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); + break; - void DockWidget::saveState(QXmlStreamWriter &stream) const - { - stream.writeStartElement("widget"); - stream.writeAttribute("name", objectName()); - stream.writeAttribute("closed", QVariant::fromValue(d->m_closed).toString()); - stream.writeEndElement(); - } + case QEvent::WindowTitleChange: { + const auto title = windowTitle(); + if (d->m_tabWidget) { + d->m_tabWidget->setText(title); + } + if (d->m_sideTabWidget) + d->m_sideTabWidget->setText(title); - void DockWidget::flagAsUnassigned() - { - d->m_closed = true; - setParent(d->m_dockManager); - setVisible(false); - setDockArea(nullptr); - tabWidget()->setParent(this); - } - - bool DockWidget::event(QEvent *event) - { - switch (event->type()) { - case QEvent::Hide: - emit visibilityChanged(false); - break; - - case QEvent::Show: - emit visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); - break; - - case QEvent::WindowTitleChange : - { - const auto title = windowTitle(); - if (d->m_tabWidget) { - d->m_tabWidget->setText(title); - } - if (d->m_toggleViewAction) { - d->m_toggleViewAction->setText(title); - } - if (d->m_dockArea) { - d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu - } - emit titleChanged(title); - } - break; - - default: - break; + if (d->m_toggleViewAction) { + d->m_toggleViewAction->setText(title); + } + if (d->m_dockArea) { + d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu } - return Super::event(event); + auto floatingWidget = floatingDockContainer(); + if (floatingWidget) + floatingWidget->updateWindowTitle(); + + emit titleChanged(title); + } break; + + default: + break; } + return Super::event(event); +} + #ifndef QT_NO_TOOLTIP - void DockWidget::setTabToolTip(const QString &text) - { - if (d->m_tabWidget) - d->m_tabWidget->setToolTip(text); +void DockWidget::setTabToolTip(const QString &text) +{ + if (d->m_tabWidget) + d->m_tabWidget->setToolTip(text); - if (d->m_toggleViewAction) - d->m_toggleViewAction->setToolTip(text); + if (d->m_toggleViewAction) + d->m_toggleViewAction->setToolTip(text); - if (d->m_dockArea) - d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu - } + if (d->m_dockArea) + d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu +} #endif - void DockWidget::setIcon(const QIcon &icon) - { - d->m_tabWidget->setIcon(icon); - if (!d->m_toggleViewAction->isCheckable()) - d->m_toggleViewAction->setIcon(icon); +void DockWidget::setIcon(const QIcon &icon) +{ + d->m_tabWidget->setIcon(icon); + + if (d->m_sideTabWidget) + d->m_sideTabWidget->setIcon(icon); + + if (!d->m_toggleViewAction->isCheckable()) + d->m_toggleViewAction->setIcon(icon); +} + +QIcon DockWidget::icon() const +{ + return d->m_tabWidget->icon(); +} + +QToolBar *DockWidget::toolBar() const +{ + return d->m_toolBar; +} + +QToolBar *DockWidget::createDefaultToolBar() +{ + if (!d->m_toolBar) + d->setupToolBar(); + + return d->m_toolBar; +} + +void DockWidget::setToolBar(QToolBar *toolBar) +{ + if (d->m_toolBar) + delete d->m_toolBar; + + d->m_toolBar = toolBar; + d->m_layout->insertWidget(0, d->m_toolBar); + connect(this, &DockWidget::topLevelChanged, this, &DockWidget::setToolbarFloatingStyle); + setToolbarFloatingStyle(isFloating()); +} + +void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) +{ + if (StateFloating == state) + d->m_toolBarStyleFloating = style; + else + d->m_toolBarStyleDocked = style; + + setToolbarFloatingStyle(isFloating()); +} + +Qt::ToolButtonStyle DockWidget::toolBarStyle(eState state) const +{ + if (StateFloating == state) + return d->m_toolBarStyleFloating; + else + return d->m_toolBarStyleDocked; +} + +void DockWidget::setToolBarIconSize(const QSize &iconSize, eState state) +{ + if (StateFloating == state) + d->m_toolBarIconSizeFloating = iconSize; + else + d->m_toolBarIconSizeDocked = iconSize; + + setToolbarFloatingStyle(isFloating()); +} + +QSize DockWidget::toolBarIconSize(eState state) const +{ + if (StateFloating == state) + return d->m_toolBarIconSizeFloating; + else + return d->m_toolBarIconSizeDocked; +} + +void DockWidget::setToolbarFloatingStyle(bool floating) +{ + if (!d->m_toolBar) + return; + + auto iconSize = floating ? d->m_toolBarIconSizeFloating : d->m_toolBarIconSizeDocked; + if (iconSize != d->m_toolBar->iconSize()) + d->m_toolBar->setIconSize(iconSize); + + auto buttonStyle = floating ? d->m_toolBarStyleFloating : d->m_toolBarStyleDocked; + if (buttonStyle != d->m_toolBar->toolButtonStyle()) + d->m_toolBar->setToolButtonStyle(buttonStyle); +} + +void DockWidget::emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating) +{ + if (topLevelDockWidget) { + topLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); + topLevelDockWidget->emitTopLevelChanged(floating); + } +} + +void DockWidget::emitTopLevelChanged(bool floating) +{ + if (floating != d->m_isFloatingTopLevel) { + d->m_isFloatingTopLevel = floating; + emit topLevelChanged(d->m_isFloatingTopLevel); + } +} + +void DockWidget::setClosedState(bool closed) +{ + d->m_closed = closed; +} + +QSize DockWidget::minimumSizeHint() const +{ + if (!d->m_widget) + return QSize(60, 40); + + // TODO + DockContainerWidget *container = this->dockContainer(); + if (!container || container->isFloating()) { + const QSize sh = d->m_widget->minimumSizeHint(); + const QSize s = d->m_widget->minimumSize(); + return {std::max(s.width(), sh.width()), std::max(s.height(), sh.height())}; } - QIcon DockWidget::icon() const { return d->m_tabWidget->icon(); } - - QToolBar *DockWidget::toolBar() const { return d->m_toolBar; } - - QToolBar *DockWidget::createDefaultToolBar() - { - if (!d->m_toolBar) - d->setupToolBar(); - - return d->m_toolBar; + switch (d->m_minimumSizeHintMode) { + case MinimumSizeHintFromDockWidget: + return QSize(60, 40); + case MinimumSizeHintFromContent: + return d->m_widget->minimumSizeHint(); + case MinimumSizeHintFromDockWidgetMinimumSize: + return minimumSize(); + case MinimumSizeHintFromContentMinimumSize: + return d->m_widget->minimumSize(); } - void DockWidget::setToolBar(QToolBar *toolBar) - { - if (d->m_toolBar) - delete d->m_toolBar; + return d->m_widget->minimumSizeHint(); +} - d->m_toolBar = toolBar; - d->m_layout->insertWidget(0, d->m_toolBar); - connect(this, &DockWidget::topLevelChanged, this, &DockWidget::setToolbarFloatingStyle); - setToolbarFloatingStyle(isFloating()); - } +void DockWidget::setFloating() +{ + if (isClosed()) + return; - void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) - { - if (StateFloating == state) - d->m_toolBarStyleFloating = style; - else - d->m_toolBarStyleDocked = style; + d->m_tabWidget->detachDockWidget(); +} - setToolbarFloatingStyle(isFloating()); - } +void DockWidget::deleteDockWidget() +{ + auto manager = dockManager(); + if (manager) + manager->removeDockWidget(this); - Qt::ToolButtonStyle DockWidget::toolBarStyle(eState state) const - { - if (StateFloating == state) - return d->m_toolBarStyleFloating; - else - return d->m_toolBarStyleDocked; - } + deleteLater(); + d->m_closed = true; +} - void DockWidget::setToolBarIconSize(const QSize &iconSize, eState state) - { - if (StateFloating == state) - d->m_toolBarIconSizeFloating = iconSize; - else - d->m_toolBarIconSizeDocked = iconSize; +void DockWidget::closeDockWidget() +{ + closeDockWidgetInternal(true); +} - setToolbarFloatingStyle(isFloating()); - } +bool DockWidget::closeDockWidgetInternal(bool forceClose) +{ + if (!forceClose) + emit closeRequested(); - QSize DockWidget::toolBarIconSize(eState state) const - { - if (StateFloating == state) - return d->m_toolBarIconSizeFloating; - else - return d->m_toolBarIconSizeDocked; - } + if (!forceClose && features().testFlag(DockWidget::CustomCloseHandling)) + return false; - void DockWidget::setToolbarFloatingStyle(bool floating) - { - if (!d->m_toolBar) - return; - - auto iconSize = floating ? d->m_toolBarIconSizeFloating : d->m_toolBarIconSizeDocked; - if (iconSize != d->m_toolBar->iconSize()) - d->m_toolBar->setIconSize(iconSize); - - auto buttonStyle = floating ? d->m_toolBarStyleFloating : d->m_toolBarStyleDocked; - if (buttonStyle != d->m_toolBar->toolButtonStyle()) - d->m_toolBar->setToolButtonStyle(buttonStyle); - } - - void DockWidget::emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating) - { - if (topLevelDockWidget) { - topLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); - topLevelDockWidget->emitTopLevelChanged(floating); - } - } - - void DockWidget::emitTopLevelChanged(bool floating) - { - if (floating != d->m_isFloatingTopLevel) { - d->m_isFloatingTopLevel = floating; - emit topLevelChanged(d->m_isFloatingTopLevel); - } - } - - void DockWidget::setClosedState(bool closed) { d->m_closed = closed; } - - QSize DockWidget::minimumSizeHint() const - { - if (!d->m_widget) - return QSize(60, 40); - - DockContainerWidget *container = this->dockContainer(); - if (!container || container->isFloating()) { - const QSize sh = d->m_widget->minimumSizeHint(); - const QSize s = d->m_widget->minimumSize(); - return {std::max(s.width(), sh.width()), std::max(s.height(), sh.height())}; + if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + // If the dock widget is floating, then we check if we also need to + // delete the floating widget + if (isFloating()) { + FloatingDockContainer *floatingWidget = internal::findParent( + this); + if (floatingWidget->dockWidgets().count() == 1) + floatingWidget->deleteLater(); + else + floatingWidget->hide(); } - if (d->m_minimumSizeHintMode == DockWidget::MinimumSizeHintFromDockWidget) - return QSize(60, 40); - else - return d->m_widget->minimumSizeHint(); + if (d->m_dockArea && d->m_dockArea->isAutoHide()) + d->m_dockArea->autoHideDockContainer()->cleanupAndDelete(); + + deleteDockWidget(); + emit closed(); + } else { + toggleView(false); } - void DockWidget::setFloating() - { - if (isClosed()) - return; + return true; +} - d->m_tabWidget->detachDockWidget(); +void DockWidget::setTitleBarActions(QList actions) +{ + d->m_titleBarActions = actions; +} + +QList DockWidget::titleBarActions() const +{ + return d->m_titleBarActions; +} + +void DockWidget::showFullScreen() +{ + if (isFloating()) + dockContainer()->floatingWidget()->showFullScreen(); + else + Super::showFullScreen(); +} + +void DockWidget::showNormal() +{ + if (isFloating()) + dockContainer()->floatingWidget()->showNormal(); + else + Super::showNormal(); +} + +bool DockWidget::isFullScreen() const +{ + if (isFloating()) + return dockContainer()->floatingWidget()->isFullScreen(); + else + return Super::isFullScreen(); +} + +void DockWidget::setAsCurrentTab() +{ + if (d->m_dockArea && !isClosed()) + d->m_dockArea->setCurrentDockWidget(this); +} + +bool DockWidget::isTabbed() const +{ + return d->m_dockArea && (d->m_dockArea->openDockWidgetsCount() > 1); +} + +bool DockWidget::isCurrentTab() const +{ + return d->m_dockArea && (d->m_dockArea->currentDockWidget() == this); +} + +void DockWidget::raise() +{ + if (isClosed()) + return; + + setAsCurrentTab(); + if (isInFloatingContainer()) { + auto floatingWindow = window(); + floatingWindow->raise(); + floatingWindow->activateWindow(); } +} - void DockWidget::deleteDockWidget() - { - dockManager()->removeDockWidget(this); - deleteLater(); - d->m_closed = true; +void DockWidget::setAutoHide(bool enable, SideBarLocation location) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; + + // Do nothing if nothing changes + if (enable == isAutoHide()) + return; + + auto dockArea = dockAreaWidget(); + if (!enable) { + dockArea->setAutoHide(false); + } else { + auto area = (SideBarNone == location) ? dockArea->calculateSideTabBarArea() : location; + dockContainer()->createAndSetupAutoHideContainer(area, this); } +} - void DockWidget::closeDockWidget() - { - closeDockWidgetInternal(true); - } +void DockWidget::toggleAutoHide(SideBarLocation location) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; - bool DockWidget::closeDockWidgetInternal(bool forceClose) - { - if (!forceClose) - emit closeRequested(); - - if (!forceClose && features().testFlag(DockWidget::CustomCloseHandling)) - return false; - - if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - // If the dock widget is floating, then we check if we also need to - // delete the floating widget - if (isFloating()) { - FloatingDockContainer* floatingWidget = internal::findParent< - FloatingDockContainer *>(this); - if (floatingWidget->dockWidgets().count() == 1) - floatingWidget->deleteLater(); - else - floatingWidget->hide(); - } - deleteDockWidget(); - emit closed(); - } else { - toggleView(false); - } - - return true; - } - - void DockWidget::setTitleBarActions(QList actions) - { - d->m_titleBarActions = actions; - } - - QList DockWidget::titleBarActions() const - { - return d->m_titleBarActions; - } - - void DockWidget::showFullScreen() - { - if (isFloating()) - dockContainer()->floatingWidget()->showFullScreen(); - else - Super::showFullScreen(); - } - - void DockWidget::showNormal() - { - if (isFloating()) - dockContainer()->floatingWidget()->showNormal(); - else - Super::showNormal(); - } - - bool DockWidget::isFullScreen() const - { - if (isFloating()) - return dockContainer()->floatingWidget()->isFullScreen(); - else - return Super::isFullScreen(); - } - - void DockWidget::setAsCurrentTab() - { - if (d->m_dockArea && !isClosed()) - d->m_dockArea->setCurrentDockWidget(this); - } - - bool DockWidget::isTabbed() const - { - return d->m_dockArea && (d->m_dockArea->openDockWidgetsCount() > 1); - } - - bool DockWidget::isCurrentTab() const - { - return d->m_dockArea && (d->m_dockArea->currentDockWidget() == this); - } - - void DockWidget::raise() - { - if (isClosed()) - return; - - setAsCurrentTab(); - if (isInFloatingContainer()) - { - auto floatingWindow = window(); - floatingWindow->raise(); - floatingWindow->activateWindow(); - } - } + setAutoHide(!isAutoHide(), location); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.h b/src/libs/advanceddockingsystem/dockwidget.h index 7820b88d17d..30a1e190794 100644 --- a/src/libs/advanceddockingsystem/dockwidget.h +++ b/src/libs/advanceddockingsystem/dockwidget.h @@ -21,6 +21,9 @@ class DockContainerWidget; class DockAreaWidget; class DockContainerWidgetPrivate; class FloatingDockContainer; +class AutoHideTab; +class AutoHideDockContainer; +class AutoHideSideBar; /** * The QDockWidget class provides a widget that can be docked inside a @@ -49,6 +52,8 @@ protected: friend class DockWidgetTab; friend class DockWidgetTabPrivate; friend class DockAreaTitleBarPrivate; + friend class AutoHideDockContainer; + friend AutoHideSideBar; /** * Assigns the dock manager that manages this dock widget @@ -120,14 +125,28 @@ public: using Super = QFrame; enum DockWidgetFeature { - DockWidgetClosable = 0x01,///< dock widget has a close button - DockWidgetMovable = 0x02,///< dock widget is movable and can be moved to a new position in the current dock container - DockWidgetFloatable = 0x04, - DockWidgetDeleteOnClose = 0x08, ///< deletes the dock widget when it is closed - CustomCloseHandling = 0x10, - DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable, - AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling, - NoDockWidgetFeatures = 0x00 + DockWidgetClosable = 0x001, ///< dock widget has a close button + DockWidgetMovable + = 0x002, ///< dock widget is movable and can be moved to a new position in the current dock container + DockWidgetFloatable = 0x004, ///< dock widget can be dragged into a floating window + DockWidgetDeleteOnClose = 0x008, ///< deletes the dock widget when it is closed + CustomCloseHandling + = 0x010, ///< clicking the close button will not close the dock widget but emits the closeRequested() signal instead + DockWidgetFocusable = 0x020, ///< if this is enabled, a dock widget can get focus highlighting + DockWidgetForceCloseWithArea + = 0x040, ///< dock widget will be closed when the dock area hosting it is closed + NoTab = 0x080, ///< dock widget tab will never be shown if this flag is set + DeleteContentOnClose + = 0x100, ///< deletes only the contained widget on close, keeping the dock widget intact + ///< and in place. Attempts to rebuild the contents widget on show if there is a widget factory set. + DockWidgetPinnable + = 0x200, ///< dock widget can be pinned and added to an auto hide dock container + DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable + | DockWidgetFocusable | DockWidgetPinnable, + AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose + | CustomCloseHandling, + DockWidgetAlwaysCloseAndDelete = DockWidgetForceCloseWithArea | DockWidgetDeleteOnClose, + NoDockWidgetFeatures = 0x000 }; Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) @@ -158,10 +177,18 @@ public: * To ensure, that a dock widget does not block resizing, the dock widget * reimplements minimumSizeHint() function to return a very small minimum * size hint. If you would like to adhere the minimumSizeHint() from the - * content widget, the set the minimumSizeHintMode() to - * MinimumSizeHintFromContent. + * content widget, then set the minimumSizeHintMode() to + * MinimumSizeHintFromContent. If you would like to use the minimumSize() + * value of the content widget or the dock widget, then you can use the + * MinimumSizeHintFromDockWidgetMinimumSize or + * MinimumSizeHintFromContentMinimumSize modes. */ - enum eMinimumSizeHintMode { MinimumSizeHintFromDockWidget, MinimumSizeHintFromContent }; + enum eMinimumSizeHintMode { + MinimumSizeHintFromDockWidget, + MinimumSizeHintFromContent, + MinimumSizeHintFromDockWidgetMinimumSize, + MinimumSizeHintFromContentMinimumSize, + }; /** * This mode configures the behavior of the toggle view action. @@ -205,7 +232,7 @@ public: /** * Sets the widget for the dock widget to widget. * The InsertMode defines how the widget is inserted into the dock widget. - * The content of a dock widget should be resizable do a very small size to + * The content of a dock widget should be resizable to a very small size to * prevent the dock widget from blocking the resizing. To ensure, that a * dock widget can be resized very well, it is better to insert the content+ * widget into a scroll area or to provide a widget that is already a scroll @@ -221,6 +248,18 @@ public: */ void setWidget(QWidget *widget, eInsertMode insertMode = AutoScrollArea); + /** + * Only used when the feature flag DeleteContentOnClose is set. + * Using the flag and setting a widget factory allows to free the resources + * of the widget of your application while retaining the position the next + * time you want to show your widget, unlike the flag DockWidgetDeleteOnClose + * which deletes the dock widget itself. Since we keep the dock widget, all + * regular features of ADS should work as normal, including saving and + * restoring the state of the docking system and using perspectives. + */ + using FactoryFunc = std::function; + void setWidgetFactory(FactoryFunc createWidget, eInsertMode insertMode = AutoScrollArea); + /** * Remove the widget from the dock and give ownership back to the caller */ @@ -269,12 +308,41 @@ public: */ DockContainerWidget *dockContainer() const; + /** + * This function return the floating DockContainer if is isFloating() is true + * and a nullptr if this dock widget is not floating. + */ + FloatingDockContainer *floatingDockContainer() const; + /** * Returns the dock area widget this dock widget belongs to or 0 * if this dock widget has not been docked yet */ DockAreaWidget *dockAreaWidget() const; + /** + * Returns the side tab widget for this dock, if this dock widget is in + * a auto hide container. If it is not in a auto hide container, then this + * function returns a nullptr, + */ + AutoHideTab *sideTabWidget() const; + + /** + * Assign a side tab widget if this dock widget is an auto hide container + */ + void setSideTabWidget(AutoHideTab *sideTab) const; + + /** + * Returns true, if this dock widget is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Returns the auto hide dock container of this dock widget + * or 0 if there is none + */ + AutoHideDockContainer *autoHideDockContainer() const; + /** * This property holds whether the dock widget is floating. * A dock widget is only floating, if it is the one and only widget inside @@ -314,6 +382,16 @@ public: */ void setMinimumSizeHintMode(eMinimumSizeHintMode mode); + /** + * Get the minimum size hint mode configured by setMinimumSizeHintMode + */ + eMinimumSizeHintMode minimumSizeHintMode() const; + + /** + * Returns true if the dock widget is set as central widget of it's dock manager + */ + bool isCentralWidget() const; + /** * Sets the dock widget icon that is shown in tabs and in toggle view * actions @@ -431,7 +509,7 @@ public: // reimplements QFrame /** * This property controls whether the dock widget is open or closed. - * The toogleViewAction triggers this slot + * The toggleViewAction triggers this slot */ void toggleView(bool open = true); @@ -488,6 +566,18 @@ public: // reimplements QFrame */ void showNormal(); + /** + * Sets the dock widget into auto hide mode if this feature is enabled + * via CDockManager::setAutoHideFlags(CDockManager::AutoHideFeatureEnabled) + */ + void setAutoHide(bool enable, SideBarLocation location = SideBarNone); + + /** + * Switches the dock widget to auto hide mode or vice versa depending on its + * current state. + */ + void toggleAutoHide(SideBarLocation location = SideBarNone); + signals: /** * This signal is emitted if the dock widget is opened or closed diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index 1775da373f4..091bfe6509f 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -7,13 +7,13 @@ #include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareawidget.h" +#include "dockfocuscontroller.h" #include "dockmanager.h" #include "dockoverlay.h" #include "dockwidget.h" #include "elidinglabel.h" #include "floatingdockcontainer.h" #include "floatingdragpreview.h" -#include "iconprovider.h" #include @@ -23,549 +23,697 @@ #include #include #include +#include #include #include #include #include -#include -#include #include +#include #include -namespace ADS +namespace ADS { + +static const char *const g_locationProperty = "Location"; +using TabLabelType = ElidingLabel; + +/** + * Private data class of DockWidgetTab class (pimpl) + */ +class DockWidgetTabPrivate { - using TabLabelType = ElidingLabel; +public: + DockWidgetTab *q; + DockWidget *m_dockWidget = nullptr; + QLabel *m_iconLabel = nullptr; + TabLabelType *m_titleLabel = nullptr; + QPoint m_globalDragStartMousePosition; + QPoint m_dragStartMousePosition; + bool m_isActiveTab = false; + DockAreaWidget *m_dockArea = nullptr; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + QIcon m_icon; + TabButton *m_closeButton = nullptr; + QPoint m_tabDragStartPosition; + QSize m_iconSize; + bool m_mousePressed = false; /** - * Private data class of DockWidgetTab class (pimpl) + * Private data constructor */ - class DockWidgetTabPrivate + DockWidgetTabPrivate(DockWidgetTab *parent); + + /** + * Creates the complete layout including all controls + */ + void createLayout(); + + /** + * Moves the tab depending on the position in the given mouse event + */ + void moveTab(QMouseEvent *event); + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(eDragState draggingState = DraggingFloatingWidget); + + /** + * Returns true if the given config flag is set + */ + bool testConfigFlag(DockManager::eConfigFlag flag) const { - public: - DockWidgetTab *q; - DockWidget *m_dockWidget = nullptr; - QLabel *m_iconLabel = nullptr; - TabLabelType *m_titleLabel = nullptr; - QPoint m_globalDragStartMousePosition; - QPoint m_dragStartMousePosition; - bool m_isActiveTab = false; - DockAreaWidget *m_dockArea = nullptr; - eDragState m_dragState = DraggingInactive; - AbstractFloatingWidget *m_floatingWidget = nullptr; - QIcon m_icon; - TabButton *m_closeButton = nullptr; - QPoint m_tabDragStartPosition; - - /** - * Private data constructor - */ - DockWidgetTabPrivate(DockWidgetTab *parent); - - /** - * Creates the complete layout including all controls - */ - void createLayout(); - - /** - * Moves the tab depending on the position in the given mouse event - */ - void moveTab(QMouseEvent *event); - - /** - * Test function for current drag state - */ - bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } - - /** - * Starts floating of the dock widget that belongs to this title bar - * Returns true, if floating has been started and false if floating - * is not possible for any reason - */ - bool startFloating(eDragState draggingState = DraggingFloatingWidget); - - /** - * Returns true if the given config flag is set - */ - bool testConfigFlag(DockManager::eConfigFlag flag) const - { - return DockManager::testConfigFlag(flag); - } - - /** - * Creates the close button as QPushButton or as QToolButton - */ - TabButton *createCloseButton() const - { - /* - if (testConfigFlag(DockManager::TabCloseButtonIsToolButton)) { - auto button = new QToolButton(); - button->setAutoRaise(true); - return button; - } else { - return new QPushButton(); - } - */ - return new TabButton(); - } - - template - AbstractFloatingWidget *createFloatingWidget(T *widget, bool opaqueUndocking) - { - if (opaqueUndocking) { - return new FloatingDockContainer(widget); - } else { - auto w = new FloatingDragPreview(widget); - QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [=]() { - m_dragState = DraggingInactive; - }); - return w; - } - } - - /** - * Saves the drag start position in global and local coordinates - */ - void saveDragStartMousePosition(const QPoint &globalPos) - { - m_globalDragStartMousePosition = globalPos; - m_dragStartMousePosition = q->mapFromGlobal(globalPos); - } - }; // class DockWidgetTabPrivate - - DockWidgetTabPrivate::DockWidgetTabPrivate(DockWidgetTab *parent) - : q(parent) - {} - - void DockWidgetTabPrivate::createLayout() - { - m_titleLabel = new TabLabelType(); - m_titleLabel->setElideMode(Qt::ElideRight); - m_titleLabel->setText(m_dockWidget->windowTitle()); - m_titleLabel->setObjectName("dockWidgetTabLabel"); - m_titleLabel->setAlignment(Qt::AlignCenter); - QObject::connect(m_titleLabel, - &ElidingLabel::elidedChanged, - q, - &DockWidgetTab::elidedChanged); - - m_closeButton = createCloseButton(); - m_closeButton->setObjectName("tabCloseButton"); - internal::setButtonIcon(m_closeButton, - QStyle::SP_TitleBarCloseButton, - TabCloseIcon); - m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_closeButton->setIconSize(QSize(11, 11)); - m_closeButton->setFixedSize(QSize(17, 17)); - q->onDockWidgetFeaturesChanged(); - internal::setToolTip(m_closeButton, Tr::tr("Close Tab")); - QObject::connect(m_closeButton, - &QAbstractButton::clicked, - q, - &DockWidgetTab::closeRequested); - - QFontMetrics fontMetrics(m_titleLabel->font()); - int spacing = qRound(fontMetrics.height() / 4.0); - - // Fill the layout - QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); - boxLayout->setContentsMargins(2 * spacing, 0, 0, 0); - boxLayout->setSpacing(0); - q->setLayout(boxLayout); - boxLayout->addWidget(m_titleLabel, 1, Qt::AlignVCenter); - boxLayout->addSpacing(qRound(spacing * 4.0 / 3.0)); - boxLayout->addWidget(m_closeButton, 0, Qt::AlignVCenter); - boxLayout->addSpacing(1); - boxLayout->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); - - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - m_closeButton->setCheckable(true); - - m_titleLabel->setVisible(true); + return DockManager::testConfigFlag(flag); } - void DockWidgetTabPrivate::moveTab(QMouseEvent *event) + /** + * Creates the close button as QPushButton or as QToolButton + */ + TabButton *createCloseButton() const { return new TabButton(); } + + template + AbstractFloatingWidget *createFloatingWidget(T *widget, bool opaqueUndocking) { - event->accept(); - QPoint distance = event->globalPosition().toPoint() - m_globalDragStartMousePosition; - distance.setY(0); - auto targetPos = distance + m_tabDragStartPosition; - targetPos.rx() = qMax(targetPos.x(), 0); - targetPos.rx() = qMin(q->parentWidget()->rect().right() - q->width() + 1, targetPos.rx()); - q->move(targetPos); - q->raise(); - } - - bool DockWidgetTabPrivate::startFloating(eDragState draggingState) - { - auto dockContainer = m_dockWidget->dockContainer(); - qCInfo(adsLog) << "isFloating " << dockContainer->isFloating(); - qCInfo(adsLog) << "areaCount " << dockContainer->dockAreaCount(); - qCInfo(adsLog) << "widgetCount " << m_dockWidget->dockAreaWidget()->dockWidgetsCount(); - // if this is the last dock widget inside of this floating widget, - // then it does not make any sense, to make it floating because - // it is already floating - if (dockContainer->isFloating() && (dockContainer->visibleDockAreaCount() == 1) - && (m_dockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) { - return false; - } - - qCInfo(adsLog) << "startFloating"; - m_dragState = draggingState; - AbstractFloatingWidget *floatingWidget = nullptr; - bool opaqueUndocking = DockManager::testConfigFlag(DockManager::OpaqueUndocking) - || (DraggingFloatingWidget != draggingState); - - // If section widget has multiple tabs, we take only one tab - // If it has only one single tab, we can move the complete - // dock area into floating widget - QSize size; - if (m_dockArea->dockWidgetsCount() > 1) { - floatingWidget = createFloatingWidget(m_dockWidget, opaqueUndocking); - size = m_dockWidget->size(); + if (opaqueUndocking) { + return new FloatingDockContainer(widget); } else { - floatingWidget = createFloatingWidget(m_dockArea, opaqueUndocking); - size = m_dockArea->size(); + auto w = new FloatingDragPreview(widget); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [=]() { + m_dragState = DraggingInactive; + }); + return w; } - - if (DraggingFloatingWidget == draggingState) { - floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingFloatingWidget, q); - auto Overlay = m_dockWidget->dockManager()->containerOverlay(); - Overlay->setAllowedAreas(OuterDockAreas); - this->m_floatingWidget = floatingWidget; - } else { - floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingInactive, nullptr); - } - - return true; } - - TabButton::TabButton(QWidget *parent) - : TabButtonType(parent) - , m_active(false) - , m_focus(false) - {} - - void TabButton::setActive(bool value) { m_active = value; } - void TabButton::setFocus(bool value) { m_focus = value; } - - void TabButton::paintEvent(QPaintEvent *event) + /** + * Update the close button visibility from current feature/config + */ + void updateCloseButtonVisibility(bool active) { - Q_UNUSED(event) - - QStylePainter p(this); - QStyleOptionToolButton opt; - initStyleOption(&opt); - opt.icon = QIcon(); // set to null icon otherwise it is drawn twice - p.drawComplexControl(QStyle::CC_ToolButton, opt); - - QIcon::Mode mode = QIcon::Mode::Normal; - if (m_active) - mode = QIcon::Mode::Active; - if (m_focus) - mode = QIcon::Mode::Selected; - - const QPoint iconPosition = rect().center() - QPoint(iconSize().width() * 0.5, - iconSize().height() * 0.5); - - p.drawPixmap(iconPosition, icon().pixmap(iconSize(), mode)); - } - - - DockWidgetTab::DockWidgetTab(DockWidget *dockWidget, QWidget *parent) - : QFrame(parent) - , d(new DockWidgetTabPrivate(this)) - { - setAttribute(Qt::WA_NoMousePropagation, true); - d->m_dockWidget = dockWidget; - d->createLayout(); - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - setFocusPolicy(Qt::ClickFocus); - } - - DockWidgetTab::~DockWidgetTab() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } - - void DockWidgetTab::mousePressEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - event->accept(); - d->saveDragStartMousePosition(event->globalPosition().toPoint()); - d->m_dragState = DraggingMousePressed; - emit clicked(); - return; - } - Super::mousePressEvent(event); - } - - void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - auto currentDragState = d->m_dragState; - d->m_globalDragStartMousePosition = QPoint(); - d->m_dragStartMousePosition = QPoint(); - d->m_dragState = DraggingInactive; - - switch (currentDragState) { - case DraggingTab: - // End of tab moving, emit signal - if (d->m_dockArea) { - emit moved(event->globalPosition().toPoint()); - } - break; - - case DraggingFloatingWidget: - d->m_floatingWidget->finishDragging(); - break; - - default:; // do nothing - } - } - - Super::mouseReleaseEvent(event); - } - - void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) - { - if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { - d->m_dragState = DraggingInactive; - Super::mouseMoveEvent(event); - return; - } - - // move floating window - if (d->isDraggingState(DraggingFloatingWidget)) { - d->m_floatingWidget->moveFloating(); - Super::mouseMoveEvent(event); - return; - } - - // move tab - if (d->isDraggingState(DraggingTab)) { - // Moving the tab is always allowed because it does not mean moving the - // dock widget around - d->moveTab(event); - } - - auto mappedPos = mapToParent(event->pos()); - bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); - // Maybe a fixed drag distance is better here ? - int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() - event->globalPosition().toPoint().y()); - if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { - // If this is the last dock area in a dock container with only - // one single dock widget it does not make sense to move it to a new - // floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->openDockWidgetsCount() == 1 - && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { - return; - } - - // Floating is only allowed for widgets that are floatable - // If we do non opaque undocking, then can create the drag preview - // if the widget is movable. - auto features = d->m_dockWidget->features(); - if (features.testFlag(DockWidget::DockWidgetFloatable) - || (features.testFlag(DockWidget::DockWidgetMovable) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { - // If we undock, we need to restore the initial position of this - // tab because it looks strange if it remains on its dragged position - if (d->isDraggingState(DraggingTab) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking)) - parentWidget()->layout()->update(); - - d->startFloating(); - } - return; - } else if (d->m_dockArea->openDockWidgetsCount() > 1 - && (event->globalPosition().toPoint() - d->m_globalDragStartMousePosition).manhattanLength() - >= QApplication::startDragDistance()) // Wait a few pixels before start moving - { - // If we start dragging the tab, we save its initial position to - // restore it later - if (DraggingTab != d->m_dragState) - d->m_tabDragStartPosition = this->pos(); - - d->m_dragState = DraggingTab; - return; - } - - Super::mouseMoveEvent(event); - } - - void DockWidgetTab::contextMenuEvent(QContextMenuEvent *event) - { - event->accept(); - if (d->isDraggingState(DraggingFloatingWidget)) - return; - - d->saveDragStartMousePosition(event->globalPos()); - QMenu menu(this); - - const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); - const bool isNotOnlyTabInContainer = !d->m_dockArea->dockContainer()->hasTopLevelDockWidget(); - const bool isDetachable = isFloatable && isNotOnlyTabInContainer; - - auto action = menu.addAction(Tr::tr("Detach"), this, &DockWidgetTab::detachDockWidget); - action->setEnabled(isDetachable); - menu.addSeparator(); - action = menu.addAction(Tr::tr("Close"), this, &DockWidgetTab::closeRequested); - action->setEnabled(isClosable()); - menu.addAction(Tr::tr("Close Others"), this, &DockWidgetTab::closeOtherTabsRequested); - menu.exec(event->globalPos()); - } - - bool DockWidgetTab::isActiveTab() const { return d->m_isActiveTab; } - - void DockWidgetTab::setActiveTab(bool active) - { - bool dockWidgetClosable = d->m_dockWidget->features().testFlag( - DockWidget::DockWidgetClosable); - bool activeTabHasCloseButton = d->testConfigFlag(DockManager::ActiveTabHasCloseButton); - bool allTabsHaveCloseButton = d->testConfigFlag(DockManager::AllTabsHaveCloseButton); + bool dockWidgetClosable = m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); + bool activeTabHasCloseButton = testConfigFlag(DockManager::ActiveTabHasCloseButton); + bool allTabsHaveCloseButton = testConfigFlag(DockManager::AllTabsHaveCloseButton); bool tabHasCloseButton = (activeTabHasCloseButton && active) | allTabsHaveCloseButton; - d->m_closeButton->setVisible(dockWidgetClosable && tabHasCloseButton); - d->m_closeButton->setActive(active); - - // Focus related stuff - if (DockManager::testConfigFlag(DockManager::FocusHighlighting) - && !d->m_dockWidget->dockManager()->isRestoringState()) { - bool updateFocusStyle = false; - if (active && !hasFocus()) { - setFocus(Qt::OtherFocusReason); - updateFocusStyle = true; - } - if (d->m_isActiveTab == active) { - if (updateFocusStyle) - updateStyle(); - return; - } - } else if (d->m_isActiveTab == active) { - return; - } - - d->m_isActiveTab = active; - updateStyle(); - update(); - updateGeometry(); - - emit activeTabChanged(); + m_closeButton->setVisible(dockWidgetClosable && tabHasCloseButton); } - DockWidget *DockWidgetTab::dockWidget() const { return d->m_dockWidget; } - - void DockWidgetTab::setDockAreaWidget(DockAreaWidget *dockArea) { d->m_dockArea = dockArea; } - - DockAreaWidget *DockWidgetTab::dockAreaWidget() const { return d->m_dockArea; } - - void DockWidgetTab::setIcon(const QIcon &icon) + /** + * Update the size policy of the close button depending on the + * RetainTabSizeWhenCloseButtonHidden feature + */ + void updateCloseButtonSizePolicy() { - QBoxLayout *boxLayout = qobject_cast(layout()); - if (!d->m_iconLabel && icon.isNull()) + auto features = m_dockWidget->features(); + auto sizePolicy = m_closeButton->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden( + features.testFlag(DockWidget::DockWidgetClosable) + && testConfigFlag(DockManager::RetainTabSizeWhenCloseButtonHidden)); + m_closeButton->setSizePolicy(sizePolicy); + } + + /** + * Saves the drag start position in global and local coordinates + */ + void saveDragStartMousePosition(const QPoint &globalPos) + { + m_globalDragStartMousePosition = globalPos; + m_dragStartMousePosition = q->mapFromGlobal(globalPos); + } + + /** + * Update the icon in case the icon size changed + */ + void updateIcon() + { + if (!m_iconLabel || m_icon.isNull()) return; - if (!d->m_iconLabel) { - d->m_iconLabel = new QLabel(); - d->m_iconLabel->setAlignment(Qt::AlignVCenter); - d->m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - internal::setToolTip(d->m_iconLabel, d->m_titleLabel->toolTip()); - boxLayout->insertWidget(0, d->m_iconLabel, Qt::AlignVCenter); - boxLayout->insertSpacing(1, qRound(1.5 * boxLayout->contentsMargins().left() / 2.0)); - } else if (icon.isNull()) { - // Remove icon label and spacer item - boxLayout->removeWidget(d->m_iconLabel); - boxLayout->removeItem(boxLayout->itemAt(0)); - delete d->m_iconLabel; - d->m_iconLabel = nullptr; - } + if (m_iconSize.isValid()) + m_iconLabel->setPixmap(m_icon.pixmap(m_iconSize)); + else + m_iconLabel->setPixmap( + m_icon.pixmap(q->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, q))); - d->m_icon = icon; - if (d->m_iconLabel) { - d->m_iconLabel->setPixmap( - icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this))); - d->m_iconLabel->setVisible(true); + m_iconLabel->setVisible(true); + } + + /** + * Convenience function for access to the dock manager dock focus controller + */ + DockFocusController *focusController() const + { + return m_dockWidget->dockManager()->dockFocusController(); + } + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction *createAutoHideToAction(const QString &title, SideBarLocation location, QMenu *menu) + { + auto action = menu->addAction(title); + action->setProperty("Location", location); + QObject::connect(action, &QAction::triggered, q, &DockWidgetTab::onAutoHideToActionClicked); + return action; + } + +}; // class DockWidgetTabPrivate + +DockWidgetTabPrivate::DockWidgetTabPrivate(DockWidgetTab *parent) + : q(parent) +{} + +void DockWidgetTabPrivate::createLayout() +{ + m_titleLabel = new TabLabelType(); + m_titleLabel->setElideMode(Qt::ElideRight); + m_titleLabel->setText(m_dockWidget->windowTitle()); + m_titleLabel->setObjectName("dockWidgetTabLabel"); + m_titleLabel->setAlignment(Qt::AlignCenter); + QObject::connect(m_titleLabel, &ElidingLabel::elidedChanged, q, &DockWidgetTab::elidedChanged); + + m_closeButton = createCloseButton(); + m_closeButton->setObjectName("tabCloseButton"); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon); + m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_closeButton->setIconSize(QSize(11, 11)); + m_closeButton->setFixedSize(QSize(17, 17)); + m_closeButton->setFocusPolicy(Qt::NoFocus); + updateCloseButtonSizePolicy(); + internal::setToolTip(m_closeButton, Tr::tr("Close Tab")); + QObject::connect(m_closeButton, &QAbstractButton::clicked, q, &DockWidgetTab::closeRequested); + + QFontMetrics fontMetrics(m_titleLabel->font()); + int spacing = qRound(fontMetrics.height() / 4.0); + + // Fill the layout + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); + boxLayout->setContentsMargins(2 * spacing, 0, 0, 0); + boxLayout->setSpacing(0); + q->setLayout(boxLayout); + boxLayout->addWidget(m_titleLabel, 1, Qt::AlignVCenter); + boxLayout->addSpacing(qRound(spacing * 4.0 / 3.0)); + boxLayout->addWidget(m_closeButton, 0, Qt::AlignVCenter); + boxLayout->addSpacing(1); + boxLayout->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); + + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + m_closeButton->setCheckable(true); + + m_titleLabel->setVisible(true); +} + +void DockWidgetTabPrivate::moveTab(QMouseEvent *event) +{ + event->accept(); + QPoint distance = event->globalPosition().toPoint() - m_globalDragStartMousePosition; + distance.setY(0); + auto targetPos = distance + m_tabDragStartPosition; + targetPos.rx() = qMax(targetPos.x(), 0); + targetPos.rx() = qMin(q->parentWidget()->rect().right() - q->width() + 1, targetPos.rx()); + q->move(targetPos); + q->raise(); +} + +bool DockWidgetTabPrivate::startFloating(eDragState draggingState) +{ + auto dockContainer = m_dockWidget->dockContainer(); + // If this is the last dock widget inside of this floating widget, then it does not make any + // sense, to make it floating because it is already floating. + if (dockContainer->isFloating() && (dockContainer->visibleDockAreaCount() == 1) + && (m_dockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) + return false; + + m_dragState = draggingState; + AbstractFloatingWidget *floatingWidget = nullptr; + bool createContainer = (DraggingFloatingWidget != draggingState); + + // If section widget has multiple tabs, we take only one tab. If it has only one single tab, + // we can move the complete dock area into floating widget. + QSize size; + if (m_dockArea->dockWidgetsCount() > 1) { + floatingWidget = createFloatingWidget(m_dockWidget, createContainer); + size = m_dockWidget->size(); + } else { + floatingWidget = createFloatingWidget(m_dockArea, createContainer); + size = m_dockArea->size(); + } + + if (DraggingFloatingWidget == draggingState) { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingFloatingWidget, q); + auto overlay = m_dockWidget->dockManager()->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + m_floatingWidget = floatingWidget; + qApp->postEvent(m_dockWidget, + new QEvent((QEvent::Type) internal::g_dockedWidgetDragStartEvent)); + } else { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingInactive, nullptr); + } + + return true; +} + +TabButton::TabButton(QWidget *parent) + : TabButtonType(parent) + , m_active(false) + , m_focus(false) +{} + +void TabButton::setActive(bool value) +{ + m_active = value; +} +void TabButton::setFocus(bool value) +{ + m_focus = value; +} + +void TabButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.icon = QIcon(); // set to null icon otherwise it is drawn twice + p.drawComplexControl(QStyle::CC_ToolButton, opt); + + QIcon::Mode mode = QIcon::Mode::Normal; + if (m_active) + mode = QIcon::Mode::Active; + if (m_focus) + mode = QIcon::Mode::Selected; + + const QPoint iconPosition = rect().center() + - QPoint(iconSize().width() * 0.5, iconSize().height() * 0.5); + + p.drawPixmap(iconPosition, icon().pixmap(iconSize(), mode)); +} + +DockWidgetTab::DockWidgetTab(DockWidget *dockWidget, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation, true); + d->m_dockWidget = dockWidget; + d->createLayout(); + setFocusPolicy(Qt::NoFocus); +} + +DockWidgetTab::~DockWidgetTab() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void DockWidgetTab::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + d->m_mousePressed = true; + d->saveDragStartMousePosition(event->globalPosition().toPoint()); + d->m_dragState = DraggingMousePressed; + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) { + d->focusController()->setDockWidgetTabPressed(true); + d->focusController()->setDockWidgetTabFocused(this); + } + emit clicked(); + return; + } + Super::mousePressEvent(event); +} + +void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->m_mousePressed = false; + auto currentDragState = d->m_dragState; + d->m_globalDragStartMousePosition = QPoint(); + d->m_dragStartMousePosition = QPoint(); + d->m_dragState = DraggingInactive; + + switch (currentDragState) { + case DraggingTab: + // End of tab moving, emit signal + if (d->m_dockArea) { + event->accept(); + emit moved(event->globalPosition().toPoint()); + } + break; + + case DraggingFloatingWidget: + event->accept(); + d->m_floatingWidget->finishDragging(); + break; + + default: + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->focusController()->setDockWidgetTabPressed(false); + break; + } + } else if (event->button() == Qt::MiddleButton) { + if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab) + && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) { + // Only attempt to close if the mouse is still + // on top of the widget, to allow the user to cancel. + if (rect().contains(mapFromGlobal(QCursor::pos()))) { + event->accept(); + emit closeRequested(); + } } } - const QIcon &DockWidgetTab::icon() const { return d->m_icon; } + Super::mouseReleaseEvent(event); +} - QString DockWidgetTab::text() const { return d->m_titleLabel->text(); } +void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + Super::mouseMoveEvent(event); + return; + } - void DockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) + // Move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + Super::mouseMoveEvent(event); + return; + } + + // Move tab + if (d->isDraggingState(DraggingTab)) { + // Moving the tab is always allowed because it does not mean moving the dock widget around + d->moveTab(event); + } + + auto mappedPos = mapToParent(event->pos()); + bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() + - event->globalPosition().toPoint().y()); + if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { + // If this is the last dock area in a dock container with only + // one single dock widget it does not make sense to move it to a new + // floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->openDockWidgetsCount() == 1 + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { + return; + } + + // Floating is only allowed for widgets that are floatable + // We can create the drag preview if the widget is movable. + auto features = d->m_dockWidget->features(); + if (features.testFlag(DockWidget::DockWidgetFloatable) + || (features.testFlag(DockWidget::DockWidgetMovable))) { + // If we undock, we need to restore the initial position of this + // tab because it looks strange if it remains on its dragged position + if (d->isDraggingState(DraggingTab)) + parentWidget()->layout()->update(); + + d->startFloating(); + } + return; + } else if (d->m_dockArea->openDockWidgetsCount() > 1 + && (event->globalPosition().toPoint() - d->m_globalDragStartMousePosition) + .manhattanLength() + >= QApplication::startDragDistance()) // Wait a few pixels before start moving { + // If we start dragging the tab, we save its initial position to restore it later + if (DraggingTab != d->m_dragState) + d->m_tabDragStartPosition = this->pos(); + + d->m_dragState = DraggingTab; + return; + } + + Super::mouseMoveEvent(event); +} + +void DockWidgetTab::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + return; + + d->saveDragStartMousePosition(event->globalPos()); + + const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); + const bool isNotOnlyTabInContainer = !d->m_dockArea->dockContainer()->hasTopLevelDockWidget(); + const bool isTopLevelArea = d->m_dockArea->isTopLevelArea(); + const bool isDetachable = isFloatable && isNotOnlyTabInContainer; + + QMenu menu(this); + + if (!isTopLevelArea) { + QAction *detachAction = menu.addAction(tr("Detach")); + detachAction->connect(detachAction, + &QAction::triggered, + this, + &DockWidgetTab::detachDockWidget); + detachAction->setEnabled(isDetachable); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + QAction *pinAction = menu.addAction(tr("Pin")); + pinAction->connect(pinAction, + &QAction::triggered, + this, + &DockWidgetTab::autoHideDockWidget); + + auto isPinnable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetPinnable); + pinAction->setEnabled(isPinnable); + + auto subMenu = menu.addMenu(tr("Pin To...")); + subMenu->setEnabled(isPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, subMenu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, subMenu); + d->createAutoHideToAction(tr("Right"), SideBarRight, subMenu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, subMenu); + } + } + + menu.addSeparator(); + + QAction *closeAction = menu.addAction(tr("Close")); + closeAction->connect(closeAction, &QAction::triggered, this, &DockWidgetTab::closeRequested); + closeAction->setEnabled(isClosable()); + + if (d->m_dockArea->openDockWidgetsCount() > 1) { + QAction *closeOthersAction = menu.addAction(tr("Close Others")); + closeOthersAction->connect(closeOthersAction, + &QAction::triggered, + this, + &DockWidgetTab::closeOtherTabsRequested); + } + menu.exec(event->globalPos()); +} + +bool DockWidgetTab::isActiveTab() const +{ + return d->m_isActiveTab; +} + +void DockWidgetTab::setActiveTab(bool active) +{ + d->updateCloseButtonVisibility(active); + + d->m_closeButton->setActive(active); // TODO + + // Focus related stuff + if (DockManager::testConfigFlag(DockManager::FocusHighlighting) + && !d->m_dockWidget->dockManager()->isRestoringState()) { + bool updateFocusStyle = false; + if (active && !hasFocus()) { + d->focusController()->setDockWidgetTabFocused(this); + updateFocusStyle = true; + } + if (d->m_isActiveTab == active) { + if (updateFocusStyle) + updateStyle(); + return; + } + } else if (d->m_isActiveTab == active) { + return; + } + + d->m_isActiveTab = active; + updateStyle(); + update(); + updateGeometry(); + + emit activeTabChanged(); +} + +DockWidget *DockWidgetTab::dockWidget() const +{ + return d->m_dockWidget; +} + +void DockWidgetTab::setDockAreaWidget(DockAreaWidget *dockArea) +{ + d->m_dockArea = dockArea; +} + +DockAreaWidget *DockWidgetTab::dockAreaWidget() const +{ + return d->m_dockArea; +} + +void DockWidgetTab::setIcon(const QIcon &icon) +{ + QBoxLayout *boxLayout = qobject_cast(layout()); + if (!d->m_iconLabel && icon.isNull()) + return; + + if (!d->m_iconLabel) { + d->m_iconLabel = new QLabel(); + d->m_iconLabel->setAlignment(Qt::AlignVCenter); + d->m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + internal::setToolTip(d->m_iconLabel, d->m_titleLabel->toolTip()); + boxLayout->insertWidget(0, d->m_iconLabel, Qt::AlignVCenter); + boxLayout->insertSpacing(1, qRound(1.5 * boxLayout->contentsMargins().left() / 2.0)); + } else if (icon.isNull()) { + // Remove icon label and spacer item + boxLayout->removeWidget(d->m_iconLabel); + boxLayout->removeItem(boxLayout->itemAt(0)); + delete d->m_iconLabel; + d->m_iconLabel = nullptr; + } + + d->m_icon = icon; + if (d->m_iconLabel) { + d->m_iconLabel->setPixmap( + icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this))); + d->m_iconLabel->setVisible(true); + } +} + +const QIcon &DockWidgetTab::icon() const +{ + return d->m_icon; +} + +QString DockWidgetTab::text() const +{ + return d->m_titleLabel->text(); +} + +void DockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { // If this is the last dock area in a dock container it does not make // sense to move it to a new floating widget and leave this one empty if ((!d->m_dockArea->dockContainer()->isFloating() || d->m_dockArea->dockWidgetsCount() > 1) && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) { + event->accept(); d->saveDragStartMousePosition(event->globalPosition().toPoint()); d->startFloating(DraggingInactive); } - - Super::mouseDoubleClickEvent(event); } - void DockWidgetTab::setVisible(bool visible) - { - // Just here for debugging to insert debug output - Super::setVisible(visible); - } + Super::mouseDoubleClickEvent(event); +} - void DockWidgetTab::setText(const QString &title) { d->m_titleLabel->setText(title); } - bool DockWidgetTab::isTitleElided() const { return d->m_titleLabel->isElided(); } +void DockWidgetTab::setVisible(bool visible) +{ + visible &= !d->m_dockWidget->features().testFlag(DockWidget::NoTab); + Super::setVisible(visible); +} - bool DockWidgetTab::isClosable() const - { - return d->m_dockWidget - && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); - } +void DockWidgetTab::setText(const QString &title) +{ + d->m_titleLabel->setText(title); +} +bool DockWidgetTab::isTitleElided() const +{ + return d->m_titleLabel->isElided(); +} - void DockWidgetTab::detachDockWidget() - { - if (!d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) - return; +bool DockWidgetTab::isClosable() const +{ + return d->m_dockWidget && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); +} - d->saveDragStartMousePosition(QCursor::pos()); - d->startFloating(DraggingInactive); - } +void DockWidgetTab::detachDockWidget() +{ + if (!d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) + return; - bool DockWidgetTab::event(QEvent *event) - { + d->saveDragStartMousePosition(QCursor::pos()); + d->startFloating(DraggingInactive); +} + +void DockWidgetTab::autoHideDockWidget() +{ + d->m_dockWidget->setAutoHide(true); +} + +void DockWidgetTab::onAutoHideToActionClicked() +{ + int location = sender()->property(g_locationProperty).toInt(); + d->m_dockWidget->toggleAutoHide((SideBarLocation) location); +} + +bool DockWidgetTab::event(QEvent *event) +{ #ifndef QT_NO_TOOLTIP - if (event->type() == QEvent::ToolTipChange) { - const auto text = toolTip(); - d->m_titleLabel->setToolTip(text); - } + if (event->type() == QEvent::ToolTipChange) { + const auto text = toolTip(); + d->m_titleLabel->setToolTip(text); + if (d->m_iconLabel) + d->m_iconLabel->setToolTip(text); + } #endif - return Super::event(event); - } + if (event->type() == QEvent::StyleChange) + d->updateIcon(); - void DockWidgetTab::onDockWidgetFeaturesChanged() - { - auto features = d->m_dockWidget->features(); - auto sizePolicy = d->m_closeButton->sizePolicy(); - sizePolicy.setRetainSizeWhenHidden( - features.testFlag(DockWidget::DockWidgetClosable) - && d->testConfigFlag(DockManager::RetainTabSizeWhenCloseButtonHidden)); - d->m_closeButton->setSizePolicy(sizePolicy); - } + return Super::event(event); +} - void DockWidgetTab::setElideMode(Qt::TextElideMode mode) - { - d->m_titleLabel->setElideMode(mode); - } +void DockWidgetTab::onDockWidgetFeaturesChanged() +{ + d->updateCloseButtonSizePolicy(); + d->updateCloseButtonVisibility(isActiveTab()); +} - void DockWidgetTab::updateStyle() - { - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - d->m_closeButton->setFocus(property("focused").toBool()); +void DockWidgetTab::setElideMode(Qt::TextElideMode mode) +{ + d->m_titleLabel->setElideMode(mode); +} - internal::repolishStyle(this, internal::RepolishDirectChildren); - } +void DockWidgetTab::updateStyle() +{ + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->m_closeButton->setFocus(property("focused").toBool()); + + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + +QSize DockWidgetTab::iconSize() const +{ + return d->m_iconSize; +} + +void DockWidgetTab::setIconSize(const QSize &size) +{ + if (size == d->m_iconSize) + return; + + d->m_iconSize = size; + d->updateIcon(); + emit iconSizeChanged(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidgettab.h b/src/libs/advanceddockingsystem/dockwidgettab.h index 49909c6374b..e6d025fec78 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.h +++ b/src/libs/advanceddockingsystem/dockwidgettab.h @@ -35,24 +35,27 @@ private: bool m_focus; }; - /** * A dock widget tab that shows a title and an icon. - * The dock widget tab is shown in the dock area title bar to switch between - * tabbed dock widgets + * The dock widget tab is shown in the dock area title bar to switch between tabbed dock widgets. */ class ADS_EXPORT DockWidgetTab : public QFrame { Q_OBJECT Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged) private: DockWidgetTabPrivate *d; ///< private data (pimpl) friend class DockWidgetTabPrivate; friend class DockWidget; friend class DockManager; + friend class AutoHideDockContainer; + void onDockWidgetFeaturesChanged(); void detachDockWidget(); + void autoHideDockWidget(); + void onAutoHideToActionClicked(); protected: void mousePressEvent(QMouseEvent *event) override; @@ -95,8 +98,7 @@ public: DockWidget *dockWidget() const; /** - * Sets the dock area widget the dockWidget returned by dockWidget() - * function belongs to. + * Sets the dock area widget the dockWidget returned by dockWidget() function belongs to. */ void setDockAreaWidget(DockAreaWidget *dockArea); @@ -152,6 +154,24 @@ public: */ void updateStyle(); + /** + * Returns the icon size. + * If no explicit icon size has been set, the function returns an invalid QSize. + */ + QSize iconSize() const; + + /** + * Set an explicit icon size. + * If no icon size has been set explicitly, than the tab sets the icon size depending + * on the style. + */ + void setIconSize(const QSize &size); + + /** + * Returns true, if the tab has been clicked and the mouse is currently pressed. + */ + bool mousePressed() const; + void setVisible(bool visible) override; signals: @@ -161,6 +181,7 @@ signals: void closeOtherTabsRequested(); void moved(const QPoint &globalPosition); void elidedChanged(bool elided); + void iconSizeChanged(); }; // class DockWidgetTab } // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.cpp b/src/libs/advanceddockingsystem/elidinglabel.cpp index 1942b5b1878..ae5b7fd506e 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.cpp +++ b/src/libs/advanceddockingsystem/elidinglabel.cpp @@ -6,143 +6,145 @@ #include namespace ADS { - /** - * Private data of public ElidingLabel - */ - struct ElidingLabelPrivate - { - ElidingLabel *q; - Qt::TextElideMode m_elideMode = Qt::ElideNone; - QString m_text; - bool m_isElided = false; - ElidingLabelPrivate(ElidingLabel *parent) - : q(parent) - {} +/** + * Private data of public ElidingLabel + */ +class ElidingLabelPrivate +{ +public: + ElidingLabel *q; + Qt::TextElideMode m_elideMode = Qt::ElideNone; + QString m_text; + bool m_isElided = false; - void elideText(int width); - - /** - * Convenience function to check if the - */ - bool isModeElideNone() const { return Qt::ElideNone == m_elideMode; } - }; - - void ElidingLabelPrivate::elideText(int width) - { - if (isModeElideNone()) - return; - - QFontMetrics fm = q->fontMetrics(); - QString str = fm.elidedText(m_text, m_elideMode, width - q->margin() * 2 - q->indent()); - if (str == u'\u2026') - str = m_text.at(0); - - bool wasElided = m_isElided; - m_isElided = str != m_text; - if (m_isElided != wasElided) - emit q->elidedChanged(m_isElided); - - q->QLabel::setText(str); - } - - ElidingLabel::ElidingLabel(QWidget *parent, Qt::WindowFlags flags) - : QLabel(parent, flags) - , d(new ElidingLabelPrivate(this)) + ElidingLabelPrivate(ElidingLabel *parent) + : q(parent) {} - ElidingLabel::ElidingLabel(const QString &text, QWidget *parent, Qt::WindowFlags flags) - : QLabel(text, parent, flags) - , d(new ElidingLabelPrivate(this)) - { - d->m_text = text; + void elideText(int width); + + /** + * Convenience function to check if the + */ + bool isModeElideNone() const { return Qt::ElideNone == m_elideMode; } +}; + +void ElidingLabelPrivate::elideText(int width) +{ + if (isModeElideNone()) + return; + + QFontMetrics fm = q->fontMetrics(); + QString str = fm.elidedText(m_text, m_elideMode, width - q->margin() * 2 - q->indent()); + if (str == u'\u2026') + str = m_text.at(0); + + bool wasElided = m_isElided; + m_isElided = (str != m_text); + if (m_isElided != wasElided) + emit q->elidedChanged(m_isElided); + + q->QLabel::setText(str); +} + +ElidingLabel::ElidingLabel(QWidget *parent, Qt::WindowFlags flags) + : QLabel(parent, flags) + , d(new ElidingLabelPrivate(this)) +{} + +ElidingLabel::ElidingLabel(const QString &text, QWidget *parent, Qt::WindowFlags flags) + : QLabel(text, parent, flags) + , d(new ElidingLabelPrivate(this)) +{ + d->m_text = text; + internal::setToolTip(this, text); +} + +ElidingLabel::~ElidingLabel() +{ + delete d; +} + +Qt::TextElideMode ElidingLabel::elideMode() const +{ + return d->m_elideMode; +} + +void ElidingLabel::setElideMode(Qt::TextElideMode mode) +{ + d->m_elideMode = mode; + d->elideText(size().width()); +} + +bool ElidingLabel::isElided() const +{ + return d->m_isElided; +} + +void ElidingLabel::mouseReleaseEvent(QMouseEvent *event) +{ + Super::mouseReleaseEvent(event); + if (event->button() != Qt::LeftButton) + return; + + emit clicked(); +} + +void ElidingLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event) + emit doubleClicked(); + Super::mouseDoubleClickEvent(event); +} + +void ElidingLabel::resizeEvent(QResizeEvent *event) +{ + if (!d->isModeElideNone()) + d->elideText(event->size().width()); + + Super::resizeEvent(event); +} + +QSize ElidingLabel::minimumSizeHint() const +{ + if (hasPixmap() || d->isModeElideNone()) + return QLabel::minimumSizeHint(); + + const QFontMetrics &fm = fontMetrics(); + QSize size(fm.horizontalAdvance(d->m_text.left(2) + "…"), fm.height()); + return size; +} + +QSize ElidingLabel::sizeHint() const +{ + if (hasPixmap() || d->isModeElideNone()) + return QLabel::sizeHint(); + + const QFontMetrics &fm = fontMetrics(); + QSize size(fm.horizontalAdvance(d->m_text), QLabel::sizeHint().height()); + return size; +} + +void ElidingLabel::setText(const QString &text) +{ + d->m_text = text; + if (d->isModeElideNone()) { + Super::setText(text); + } else { internal::setToolTip(this, text); - } - - ElidingLabel::~ElidingLabel() - { - delete d; - } - - Qt::TextElideMode ElidingLabel::elideMode() const - { - return d->m_elideMode; - } - - void ElidingLabel::setElideMode(Qt::TextElideMode mode) - { - d->m_elideMode = mode; d->elideText(size().width()); } +} - bool ElidingLabel::isElided() const - { - return d->m_isElided; - } +QString ElidingLabel::text() const +{ + return d->m_text; +} - void ElidingLabel::mouseReleaseEvent(QMouseEvent *event) - { - Super::mouseReleaseEvent(event); - if (event->button() != Qt::LeftButton) - return; - - emit clicked(); - } - - void ElidingLabel::mouseDoubleClickEvent(QMouseEvent *event) - { - Q_UNUSED(event) - emit doubleClicked(); - Super::mouseDoubleClickEvent(event); - } - - void ElidingLabel::resizeEvent(QResizeEvent *event) - { - if (!d->isModeElideNone()) - d->elideText(event->size().width()); - - Super::resizeEvent(event); - } - - bool ElidingLabel::hasPixmap() const - { - return !pixmap().isNull(); - } - - QSize ElidingLabel::minimumSizeHint() const - { - if (hasPixmap() || d->isModeElideNone()) - return QLabel::minimumSizeHint(); - - const QFontMetrics &fm = fontMetrics(); - QSize size(fm.horizontalAdvance(d->m_text.left(2) + "…"), fm.height()); - return size; - } - - QSize ElidingLabel::sizeHint() const - { - if (hasPixmap() || d->isModeElideNone()) - return QLabel::sizeHint(); - - const QFontMetrics &fm = fontMetrics(); - QSize size(fm.horizontalAdvance(d->m_text), QLabel::sizeHint().height()); - return size; - } - - void ElidingLabel::setText(const QString &text) - { - d->m_text = text; - if (d->isModeElideNone()) { - Super::setText(text); - } else { - internal::setToolTip(this, text); - d->elideText(this->size().width()); - } - } - - QString ElidingLabel::text() const - { - return d->m_text; - } +bool ElidingLabel::hasPixmap() const +{ + return !pixmap().isNull(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.h b/src/libs/advanceddockingsystem/elidinglabel.h index 8ee2e9bf8af..e7d00dfc199 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.h +++ b/src/libs/advanceddockingsystem/elidinglabel.h @@ -9,20 +9,19 @@ namespace ADS { -struct ElidingLabelPrivate; +class ElidingLabelPrivate; /** * A QLabel that supports eliding text. - * Because the functions setText() and text() are no virtual functions setting - * and reading the text via a pointer to the base class QLabel does not work - * properly + * Because the functions setText() and text() are no virtual functions setting and reading the + * text via a pointer to the base class QLabel does not work properly. */ class ADS_EXPORT ElidingLabel : public QLabel { Q_OBJECT private: ElidingLabelPrivate *d; - friend struct ElidingLabelPrivate; + friend class ElidingLabelPrivate; protected: void mouseReleaseEvent(QMouseEvent *event) override; @@ -32,8 +31,10 @@ protected: public: using Super = QLabel; - ElidingLabel(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); - ElidingLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); + ElidingLabel(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ElidingLabel(const QString &text, + QWidget *parent = nullptr, + Qt::WindowFlags flags = Qt::WindowFlags()); ~ElidingLabel() override; /** diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp index c99044a3d5a..9e547a20a5d 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -9,7 +9,6 @@ #include "dockmanager.h" #include "dockoverlay.h" #include "dockwidget.h" -#include "linux/floatingwidgettitlebar.h" #include @@ -19,6 +18,9 @@ #pragma comment(lib, "User32.lib") #endif #endif +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include "linux/floatingwidgettitlebar.h" +#endif #include #include @@ -30,8 +32,8 @@ #include #include -namespace ADS -{ +namespace ADS { + #ifdef Q_OS_WIN #if 0 // set to 1 if you need this function for debugging /** @@ -329,344 +331,429 @@ static const char* windowsMessageString(int messageId) #endif #endif - AbstractFloatingWidget::~AbstractFloatingWidget() = default; +AbstractFloatingWidget::~AbstractFloatingWidget() = default; + +static unsigned int zOrderCounter = 0; +/** + * Private data class of FloatingDockContainer class (pimpl) + */ +class FloatingDockContainerPrivate +{ +public: + FloatingDockContainer *q; + DockContainerWidget *m_dockContainer = nullptr; + unsigned int m_zOrderIndex = ++zOrderCounter; + QPointer m_dockManager; + eDragState m_draggingState = DraggingInactive; + QPoint m_dragStartMousePosition; + DockContainerWidget *m_dropContainer = nullptr; + DockAreaWidget *m_singleDockArea = nullptr; + QPoint m_dragStartPos; + bool m_hiding = false; + bool m_autoHideChildren = true; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QWidget *m_mouseEventHandler = nullptr; + FloatingWidgetTitleBar *m_titleBar = nullptr; + bool m_isResizing = false; + bool m_mousePressed = false; +#endif - static unsigned int zOrderCounter = 0; /** - * Private data class of FloatingDockContainer class (pimpl) + * Private data constructor */ - class FloatingDockContainerPrivate + FloatingDockContainerPrivate(FloatingDockContainer *parent); + + void titleMouseReleaseEvent(); + + void updateDropOverlays(const QPoint &globalPosition); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) { - public: - FloatingDockContainer *q; - DockContainerWidget *m_dockContainer = nullptr; - unsigned int m_zOrderIndex = ++zOrderCounter; - QPointer m_dockManager; - eDragState m_draggingState = DraggingInactive; - QPoint m_dragStartMousePosition; - DockContainerWidget *m_dropContainer = nullptr; - DockAreaWidget *m_singleDockArea = nullptr; - QPoint m_dragStartPos; - bool m_hiding = false; - QWidget *m_mouseEventHandler = nullptr; // linux only - FloatingWidgetTitleBar *m_titleBar = nullptr; // linux only - - /** - * Private data constructor - */ - FloatingDockContainerPrivate(FloatingDockContainer *parent); - - void titleMouseReleaseEvent(); - void updateDropOverlays(const QPoint &globalPosition); - - /** - * Returns true if the given config flag is set - */ - static bool testConfigFlag(DockManager::eConfigFlag flag) - { - return DockManager::testConfigFlag(flag); - } - - /** - * Tests is a certain state is active - */ - bool isState(eDragState stateId) const { return stateId == m_draggingState; } - - void setState(eDragState stateId) { m_draggingState = stateId; } - - void setWindowTitle(const QString &text) - { - if (Utils::HostOsInfo::isLinuxHost()) - m_titleBar->setTitle(text); - else - q->setWindowTitle(text); - } - - /** - * Reflect the current dock widget title in the floating widget windowTitle() - * depending on the DockManager::FloatingContainerHasWidgetTitle flag - */ - void reflectCurrentWidget(DockWidget *currentWidget) - { - // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title - if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) - setWindowTitle(currentWidget->windowTitle()); - else - setWindowTitle(QApplication::applicationDisplayName()); - - // reflect currentWidget's icon if configured to do so, otherwise display application icon as window icon - QIcon currentWidgetIcon = currentWidget->icon(); - if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) && !currentWidgetIcon.isNull()) - q->setWindowIcon(currentWidget->icon()); - else - q->setWindowIcon(QApplication::windowIcon()); - } - - /** - * Handles escape key press when dragging around the floating widget - */ - void handleEscapeKey(); - }; // class FloatingDockContainerPrivate - - FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) - : q(parent) - {} - - void FloatingDockContainerPrivate::titleMouseReleaseEvent() - { - setState(DraggingInactive); - if (!m_dropContainer) - return; - - if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea - || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { - DockOverlay *overlay = m_dockManager->containerOverlay(); - if (!overlay->dropOverlayRect().isValid()) - overlay = m_dockManager->dockAreaOverlay(); - - // Resize the floating widget to the size of the highlighted drop area rectangle - QRect rect = overlay->dropOverlayRect(); - int frameWidth = (q->frameSize().width() - q->rect().width()) / 2; - int titleBarHeight = q->frameSize().height() - q->rect().height() - frameWidth; - if (rect.isValid()) { - QPoint topLeft = overlay->mapToGlobal(rect.topLeft()); - topLeft.ry() += titleBarHeight; - q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight))); - QApplication::processEvents(); - } - m_dropContainer->dropFloatingWidget(q, QCursor::pos()); - } - - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); + return DockManager::testConfigFlag(flag); } - void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) + /** + * Tests is a certain state is active + */ + bool isState(eDragState stateId) const { return stateId == m_draggingState; } + + /** + * Sets the dragging state and posts a FloatingWidgetDragStartEvent if dragging starts. + */ + void setState(eDragState stateId) { - if (!q->isVisible() || !m_dockManager) + if (m_draggingState == stateId) return; - auto containers = m_dockManager->dockContainers(); - DockContainerWidget *topContainer = nullptr; - for (auto containerWidget : containers) { - if (!containerWidget->isVisible()) - continue; + m_draggingState = stateId; + if (m_draggingState == DraggingFloatingWidget) + qApp->postEvent(q, new QEvent((QEvent::Type) internal::g_floatingWidgetDragStartEvent)); + } - if (m_dockContainer == containerWidget) - continue; + void setWindowTitle(const QString &text) + { +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (m_titleBar) + m_titleBar->setTitle(text); +#else + q->setWindowTitle(text); +#endif + } - QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); - if (containerWidget->rect().contains(mappedPos)) { - if (!topContainer || containerWidget->isInFrontOf(topContainer)) - topContainer = containerWidget; - } + /** + * Reflect the current dock widget title in the floating widget windowTitle() + * depending on the DockManager::FloatingContainerHasWidgetTitle flag. + */ + void reflectCurrentWidget(DockWidget *currentWidget) + { + // Reflect currentWidget's title if configured to do so, otherwise display application name as window title + if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) + setWindowTitle(currentWidget->windowTitle()); + else + setWindowTitle(floatingContainersTitle()); + + // Reflect currentWidget's icon if configured to do so, otherwise display application icon as window icon + QIcon currentWidgetIcon = currentWidget->icon(); + if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) + && !currentWidgetIcon.isNull()) + q->setWindowIcon(currentWidget->icon()); + else + q->setWindowIcon(QApplication::windowIcon()); + } + + /** + * Handles escape key press when dragging around the floating widget. + */ + void handleEscapeKey(); + + /** + * Returns the title used by all FloatingContainer that do not reflect the title of the + * current dock widget. + * + * If no title was set with DockManager::setFloatingContainersTitle(), + * it returns QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle() { return DockManager::floatingContainersTitle(); } +}; // class FloatingDockContainerPrivate + +FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) + : q(parent) +{} + +void FloatingDockContainerPrivate::titleMouseReleaseEvent() +{ + setState(DraggingInactive); + if (!m_dropContainer) + return; + + if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea + || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { + DockOverlay *overlay = m_dockManager->containerOverlay(); + if (!overlay->dropOverlayRect().isValid()) + overlay = m_dockManager->dockAreaOverlay(); + + // Resize the floating widget to the size of the highlighted drop area rectangle + QRect rect = overlay->dropOverlayRect(); + int frameWidth = (q->frameSize().width() - q->rect().width()) / 2; + int titleBarHeight = q->frameSize().height() - q->rect().height() - frameWidth; + if (rect.isValid()) { + QPoint topLeft = overlay->mapToGlobal(rect.topLeft()); + topLeft.ry() += titleBarHeight; + q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight))); + QApplication::processEvents(); } + m_dropContainer->dropFloatingWidget(q, QCursor::pos()); + } - m_dropContainer = topContainer; - auto containerOverlay = m_dockManager->containerOverlay(); - auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); +} - if (!topContainer) { - containerOverlay->hideOverlay(); - dockAreaOverlay->hideOverlay(); - return; +void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) +{ + if (!q->isVisible() || !m_dockManager) + return; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Prevent display of drop overlays and docking as long as a modal dialog is active + if (qApp->activeModalWidget()) + return; +#endif + + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) + continue; + + if (m_dockContainer == containerWidget) + continue; + + QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPos)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) + topContainer = containerWidget; } + } - int visibleDockAreas = topContainer->visibleDockAreaCount(); - containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); - DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); - containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); - auto dockArea = topContainer->dockAreaAt(globalPosition); - if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { - dockAreaOverlay->enableDropPreview(true); - dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea - : dockArea->allowedAreas()); - DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); - // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in - // the title bar. If the ContainerArea is valid then we ignore the dock area of the - // dockAreaOverlay() and disable the drop preview - if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { - dockAreaOverlay->enableDropPreview(false); - containerOverlay->enableDropPreview(true); - } else { - containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); - } + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + return; + } + + int visibleDockAreas = topContainer->visibleDockAreaCount(); + containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); + DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); + containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); + auto dockArea = topContainer->dockAreaAt(globalPosition); + if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in + // the title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview. + if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); } else { - dockAreaOverlay->hideOverlay(); + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); } + } else { + dockAreaOverlay->hideOverlay(); } +} - void FloatingDockContainerPrivate::handleEscapeKey() - { - qCInfo(adsLog) << Q_FUNC_INFO; - setState(DraggingInactive); - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); - } +void FloatingDockContainerPrivate::handleEscapeKey() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + setState(DraggingInactive); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); +} - FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) - : FloatingWidgetBaseType(dockManager) - , d(new FloatingDockContainerPrivate(this)) - { - d->m_dockManager = dockManager; - d->m_dockContainer = new DockContainerWidget(dockManager, this); - connect(d->m_dockContainer, - &DockContainerWidget::dockAreasAdded, - this, - &FloatingDockContainer::onDockAreasAddedOrRemoved); - connect(d->m_dockContainer, - &DockContainerWidget::dockAreasRemoved, - this, - &FloatingDockContainer::onDockAreasAddedOrRemoved); +FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) + : FloatingWidgetBaseType(dockManager) + , d(new FloatingDockContainerPrivate(this)) +{ + d->m_dockManager = dockManager; + d->m_dockContainer = new DockContainerWidget(dockManager, this); - #ifdef Q_OS_LINUX - d->m_titleBar = new FloatingWidgetTitleBar(this); - setWindowFlags(windowFlags() | Qt::Tool); - QDockWidget::setWidget(d->m_dockContainer); - QDockWidget::setFloating(true); - QDockWidget::setFeatures(DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable); - setTitleBarWidget(d->m_titleBar); - connect(d->m_titleBar, - &FloatingWidgetTitleBar::closeRequested, - this, - &FloatingDockContainer::close); - #else - setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint | Qt::Tool); - QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); - boxLayout->setContentsMargins(0, 0, 0, 0); - boxLayout->setSpacing(0); - setLayout(boxLayout); - boxLayout->addWidget(d->m_dockContainer); - #endif - dockManager->registerFloatingWidget(this); - } + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasAdded, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasRemoved, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); - FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) - : FloatingDockContainer(dockArea->dockManager()) - { - d->m_dockContainer->addDockArea(dockArea); - #ifdef Q_OS_LINUX - d->m_titleBar->enableCloseButton(isClosable()); - #endif - if (auto dw = topLevelDockWidget()) - dw->emitTopLevelChanged(true); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Order here is really important. setWindowFlags() must come first otherwise the resize handles + // on linux are missing from floating dock widgets. + setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint | Qt::Tool); + QDockWidget::setWidget(d->m_dockContainer); + //QDockWidget::setFloating(true); + QDockWidget::setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable + | QDockWidget::DockWidgetFloatable); - d->m_dockManager->notifyWidgetOrAreaRelocation(dockArea); - } + d->m_titleBar = new FloatingWidgetTitleBar(this); + setTitleBarWidget(d->m_titleBar); + d->m_titleBar->enableCloseButton(isClosable()); + d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); + connect(d->m_titleBar, + &FloatingWidgetTitleBar::closeRequested, + this, + &FloatingDockContainer::close); + connect(d->m_titleBar, + &FloatingWidgetTitleBar::maximizeRequested, + this, + &FloatingDockContainer::onMaximizeRequest); - FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) - : FloatingDockContainer(dockWidget->dockManager()) - { - d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); - #ifdef Q_OS_LINUX - d->m_titleBar->enableCloseButton(isClosable()); - #endif - if (auto dw = topLevelDockWidget()) - dw->emitTopLevelChanged(true); +#else + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint | Qt::Tool); + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); + boxLayout->setContentsMargins(0, 0, 0, 0); + boxLayout->setSpacing(0); + setLayout(boxLayout); + boxLayout->addWidget(d->m_dockContainer); +#endif + dockManager->registerFloatingWidget(this); +} - d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); - } +FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) + : FloatingDockContainer(dockArea->dockManager()) +{ + d->m_dockContainer->addDockArea(dockArea); - FloatingDockContainer::~FloatingDockContainer() - { - qCInfo(adsLog) << Q_FUNC_INFO; - if (d->m_dockManager) - d->m_dockManager->removeFloatingWidget(this); + if (auto dw = topLevelDockWidget()) + dw->emitTopLevelChanged(true); - delete d; - } + d->m_dockManager->notifyWidgetOrAreaRelocation(dockArea); +} - DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; } +FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) + : FloatingDockContainer(dockWidget->dockManager()) +{ + d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); - void FloatingDockContainer::changeEvent(QEvent *event) - { - QWidget::changeEvent(event); - if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { - qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::ActivationChange"; + if (auto dw = topLevelDockWidget()) + dw->emitTopLevelChanged(true); + + d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); +} + +FloatingDockContainer::~FloatingDockContainer() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (d->m_dockManager) + d->m_dockManager->removeFloatingWidget(this); + + delete d; +} + +DockContainerWidget *FloatingDockContainer::dockContainer() const +{ + return d->m_dockContainer; +} + +void FloatingDockContainer::changeEvent(QEvent *event) +{ + Super::changeEvent(event); + switch (event->type()) { + case QEvent::ActivationChange: + if (isActiveWindow()) { + qCInfo(adsLog) << Q_FUNC_INFO << "Event::ActivationChange"; d->m_zOrderIndex = ++zOrderCounter; - return; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (d->m_draggingState == DraggingFloatingWidget) { + d->titleMouseReleaseEvent(); + d->m_draggingState = DraggingInactive; + } +#endif } + break; + + case QEvent::WindowStateChange: + // If the DockManager window is restored from minimized on Windows then the FloatingWidgets + // are not properly restored to maximized but to normal state. + // We simply check here, if the FloatingWidget was maximized before and if the DockManager + // is just leaving the minimized state. In this case, we restore the maximized state of + // this floating widget. + if (d->m_dockManager->isLeavingMinimizedState()) { + QWindowStateChangeEvent *ev = static_cast(event); + if (ev->oldState().testFlag(Qt::WindowMaximized)) + showMaximized(); + } + break; + + default: + break; // do nothing } +} #ifdef Q_OS_WIN bool FloatingDockContainer::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { QWidget::nativeEvent(eventType, message, result); MSG *msg = static_cast(message); - switch (msg->message) - { - case WM_MOVING: - { - if (d->isState(DraggingFloatingWidget)) - d->updateDropOverlays(QCursor::pos()); + switch (msg->message) { + case WM_MOVING: { + if (d->isState(DraggingFloatingWidget)) + d->updateDropOverlays(QCursor::pos()); + } break; + + case WM_NCLBUTTONDOWN: + if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_NCLBUTTONDOWN" << eventType; + d->m_dragStartPos = pos(); + d->setState(DraggingMousePressed); } break; - case WM_NCLBUTTONDOWN: - if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_NCLBUTTONDOWN" << eventType; - d->m_dragStartPos = pos(); - d->setState(DraggingMousePressed); - } - break; + case WM_NCLBUTTONDBLCLK: + d->setState(DraggingInactive); + break; - case WM_NCLBUTTONDBLCLK: - d->setState(DraggingInactive); - break; + case WM_ENTERSIZEMOVE: + if (d->isState(DraggingMousePressed)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_ENTERSIZEMOVE" << eventType; + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + break; - case WM_ENTERSIZEMOVE: - if (d->isState(DraggingMousePressed)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_ENTERSIZEMOVE" << eventType; - d->setState(DraggingFloatingWidget); - d->updateDropOverlays(QCursor::pos()); - } - break; - - case WM_EXITSIZEMOVE: - if (d->isState(DraggingFloatingWidget)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_EXITSIZEMOVE" << eventType; - if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) - d->handleEscapeKey(); - else - d->titleMouseReleaseEvent(); - } - break; + case WM_EXITSIZEMOVE: + if (d->isState(DraggingFloatingWidget)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_EXITSIZEMOVE" << eventType; + if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) + d->handleEscapeKey(); + else + d->titleMouseReleaseEvent(); + } + break; } return false; } #endif - void FloatingDockContainer::closeEvent(QCloseEvent *event) - { - qCInfo(adsLog) << Q_FUNC_INFO; - d->setState(DraggingInactive); - event->ignore(); +void FloatingDockContainer::closeEvent(QCloseEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO << "closable" << isClosable(); + d->setState(DraggingInactive); + event->ignore(); - if (isClosable()) { - auto dw = topLevelDockWidget(); - if (dw && dw->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - if (!dw->closeDockWidgetInternal()) - return; - } + if (!isClosable()) + return; - this->hide(); + bool hasOpenDockWidgets = false; + for (auto dockWidget : d->m_dockContainer->openedDockWidgets()) { + if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + || dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) { + bool closed = dockWidget->closeDockWidgetInternal(); + if (!closed) + hasOpenDockWidgets = true; + } else { + dockWidget->toggleView(false); } } - void FloatingDockContainer::hideEvent(QHideEvent *event) - { - Super::hideEvent(event); - if (event->spontaneous()) - return; + if (hasOpenDockWidgets) + return; - // Prevent toogleView() events during restore state - if (d->m_dockManager->isRestoringState()) - return; + // In Qt version after 5.9.2 there seems to be a bug that causes the QWidget::event() function + // to not receive any NonClientArea mouse events anymore after a close/show cycle. The bug is + // reported here: https://bugreports.qt.io/browse/QTBUG-73295 + // The following code is a workaround for Qt versions > 5.9.2 that seems to work. Starting from + // Qt version 5.12.2 this seems to work again. But now the QEvent::NonClientAreaMouseButtonPress + // function returns always Qt::RightButton even if the left button was pressed. + hide(); +} +void FloatingDockContainer::hideEvent(QHideEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + Super::hideEvent(event); + if (event->spontaneous()) + return; + + // Prevent toogleView() events during restore state + if (d->m_dockManager->isRestoringState()) + return; + + if (d->m_autoHideChildren) { d->m_hiding = true; for (auto dockArea : d->m_dockContainer->openedDockAreas()) { for (auto dockWidget : dockArea->openedDockWidgets()) @@ -674,204 +761,218 @@ bool FloatingDockContainer::nativeEvent(const QByteArray &eventType, void *messa } d->m_hiding = false; } +} - void FloatingDockContainer::showEvent(QShowEvent *event) - { - Super::showEvent(event); - #ifdef Q_OS_LINUX - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - window()->activateWindow(); - #endif - } +void FloatingDockContainer::showEvent(QShowEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO; - void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, - const QSize &size, - eDragState dragState, - QWidget *mouseEventHandler) - { - #ifndef Q_OS_LINUX - Q_UNUSED(mouseEventHandler) - #endif + Super::showEvent(event); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + window()->activateWindow(); +#endif +} + +void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!isMaximized()) { resize(size); - d->setState(dragState); d->m_dragStartMousePosition = dragStartMousePos; + } + d->setState(dragState); + if (dragState == DraggingFloatingWidget) { + d->m_mouseEventHandler = mouseEventHandler; + if (d->m_mouseEventHandler) + d->m_mouseEventHandler->grabMouse(); + } - #ifdef Q_OS_LINUX - if (DraggingFloatingWidget == dragState) { - setAttribute(Qt::WA_X11NetWmWindowTypeDock, true); - d->m_mouseEventHandler = mouseEventHandler; - if (d->m_mouseEventHandler) - d->m_mouseEventHandler->grabMouse(); - } - #endif + if (!isMaximized()) moveFloating(); - show(); + + show(); +#else + Q_UNUSED(mouseEventHandler) + resize(size); + d->m_dragStartMousePosition = dragStartMousePos; + d->setState(dragState); + moveFloating(); + show(); +#endif +} + +void FloatingDockContainer::moveFloating() +{ + const int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - QPoint(borderSize, 0); + move(moveToPos); + + switch (d->m_draggingState) { + case DraggingMousePressed: + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + break; + + case DraggingFloatingWidget: + d->updateDropOverlays(QCursor::pos()); + // On macOS when hiding the DockAreaOverlay the application would set the main window as + // the active window for some reason. This fixes that by resetting the active window to + // the floating widget after updating the overlays. + if (Utils::HostOsInfo::isMacHost()) + activateWindow(); + + break; + default: + break; } +} - void FloatingDockContainer::moveFloating() - { - const int borderSize = (frameSize().width() - size().width()) / 2; - const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - - QPoint(borderSize, 0); - move(moveToPos); +bool FloatingDockContainer::isClosable() const +{ + return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); +} - switch (d->m_draggingState) - { - case DraggingMousePressed: - d->setState(DraggingFloatingWidget); - d->updateDropOverlays(QCursor::pos()); - break; - - case DraggingFloatingWidget: - d->updateDropOverlays(QCursor::pos()); - // On macOS when hiding the DockAreaOverlay the application would set - // the main window as the active window for some reason. This fixes - // that by resetting the active window to the floating widget after - // updating the overlays. - if (Utils::HostOsInfo::isMacHost()) - QApplication::setActiveWindow(this); - - break; - default: - break; - } - } - - bool FloatingDockContainer::isClosable() const - { - return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); - } - - void FloatingDockContainer::onDockAreasAddedOrRemoved() - { - qCInfo(adsLog) << Q_FUNC_INFO; - auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); - if (topLevelDockArea) { - d->m_singleDockArea = topLevelDockArea; - DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); - d->reflectCurrentWidget(currentWidget); - connect(d->m_singleDockArea, - &DockAreaWidget::currentChanged, - this, - &FloatingDockContainer::onDockAreaCurrentChanged); - } else { - if (d->m_singleDockArea) { - disconnect(d->m_singleDockArea, - &DockAreaWidget::currentChanged, - this, - &FloatingDockContainer::onDockAreaCurrentChanged); - d->m_singleDockArea = nullptr; - } - d->setWindowTitle(QApplication::applicationDisplayName()); - setWindowIcon(QApplication::windowIcon()); - } - } - - void FloatingDockContainer::updateWindowTitle() - { - // If this floating container will be hidden, then updating the window - // title is not required anymore - if (d->m_hiding) - return; - - if (auto topLevelDockArea = d->m_dockContainer->topLevelDockArea()) { - if (DockWidget *currentWidget = topLevelDockArea->currentDockWidget()) - d->reflectCurrentWidget(currentWidget); - } else { - d->setWindowTitle(QApplication::applicationDisplayName()); - setWindowIcon(QApplication::windowIcon()); - } - } - - void FloatingDockContainer::onDockAreaCurrentChanged(int index) - { - Q_UNUSED(index) +void FloatingDockContainer::onDockAreasAddedOrRemoved() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); + if (topLevelDockArea) { + d->m_singleDockArea = topLevelDockArea; DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); d->reflectCurrentWidget(currentWidget); - } - - bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) - { - if (!d->m_dockContainer->restoreState(stream, testing)) - return false; - - onDockAreasAddedOrRemoved(); - return true; - } - - bool FloatingDockContainer::hasTopLevelDockWidget() const - { - return d->m_dockContainer->hasTopLevelDockWidget(); - } - - DockWidget *FloatingDockContainer::topLevelDockWidget() const - { - return d->m_dockContainer->topLevelDockWidget(); - } - - QList FloatingDockContainer::dockWidgets() const - { - return d->m_dockContainer->dockWidgets(); - } - - void FloatingDockContainer::finishDragging() - { - qCInfo(adsLog) << Q_FUNC_INFO; - - #ifdef Q_OS_LINUX - setAttribute(Qt::WA_X11NetWmWindowTypeDock, false); - setWindowOpacity(1); - activateWindow(); - if (d->m_mouseEventHandler) { - d->m_mouseEventHandler->releaseMouse(); - d->m_mouseEventHandler = nullptr; + connect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + } else { + if (d->m_singleDockArea) { + disconnect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + d->m_singleDockArea = nullptr; } - #endif - d->titleMouseReleaseEvent(); + d->setWindowTitle(d->floatingContainersTitle()); + setWindowIcon(QApplication::windowIcon()); } +} + +void FloatingDockContainer::updateWindowTitle() +{ + // If this floating container will be hidden, updating the window title is not required anymore. + if (d->m_hiding) + return; + + if (auto topLevelDockArea = d->m_dockContainer->topLevelDockArea()) { + if (DockWidget *currentWidget = topLevelDockArea->currentDockWidget()) + d->reflectCurrentWidget(currentWidget); + } else { + d->setWindowTitle(QApplication::applicationDisplayName()); + setWindowIcon(QApplication::windowIcon()); + } +} + +void FloatingDockContainer::onDockAreaCurrentChanged(int index) +{ + Q_UNUSED(index) + DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); + d->reflectCurrentWidget(currentWidget); +} + +bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) +{ + if (!d->m_dockContainer->restoreState(stream, testing)) + return false; + + onDockAreasAddedOrRemoved(); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); +#endif + return true; +} + +bool FloatingDockContainer::hasTopLevelDockWidget() const +{ + return d->m_dockContainer->hasTopLevelDockWidget(); +} + +DockWidget *FloatingDockContainer::topLevelDockWidget() const +{ + return d->m_dockContainer->topLevelDockWidget(); +} + +QList FloatingDockContainer::dockWidgets() const +{ + return d->m_dockContainer->dockWidgets(); +} + +void FloatingDockContainer::hideAndDeleteLater() +{ + // Widget has been redocked, so it must be hidden right way (see + // https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351) + // but AutoHideChildren must be set to false because "this" still contains + // dock widgets that shall not be toggled hidden. + d->m_autoHideChildren = false; + hide(); + deleteLater(); + if (d->m_dockManager) { + d->m_dockManager->removeFloatingWidget(this); + d->m_dockManager->removeDockContainer(dockContainer()); + } +} + +void FloatingDockContainer::finishDragging() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + setWindowOpacity(1); + activateWindow(); + if (d->m_mouseEventHandler) { + d->m_mouseEventHandler->releaseMouse(); + d->m_mouseEventHandler = nullptr; + } +#endif + d->titleMouseReleaseEvent(); +} #ifdef Q_OS_MACOS bool FloatingDockContainer::event(QEvent *event) { - switch (d->m_draggingState) - { - case DraggingInactive: - { - // Normally we would check here, if the left mouse button is pressed. - // But from QT version 5.12.2 on the mouse events from - // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button - // The event always returns Qt::RightButton even if the left button - // is clicked. - // It is really great to work around the whole NonClientMouseArea - // bugs + switch (d->m_draggingState) { + case DraggingInactive: { + // Normally we would check here, if the left mouse button is pressed. But from Qt version + // 5.12.2 on the mouse events from QEvent::NonClientAreaMouseButtonPress return the wrong + // mouse button. The event always returns Qt::RightButton even if the left button is + // clicked. It is really great to work around the whole NonClientMouseArea bugs. if (event->type() == QEvent::NonClientAreaMouseButtonPress - /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) - { + /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress" - << event->type(); + << event->type(); d->m_dragStartPos = pos(); d->setState(DraggingMousePressed); } - } - break; + } break; case DraggingMousePressed: - switch (event->type()) - { + switch (event->type()) { case QEvent::NonClientAreaMouseButtonDblClick: qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonDblClick"; d->setState(DraggingInactive); break; case QEvent::Resize: - // If the first event after the mouse press is a resize event, then - // the user resizes the window instead of dragging it around. - // But there is one exception. If the window is maximized, - // then dragging the window via title bar will cause the widget to - // leave the maximized state. This in turn will trigger a resize event. - // To know, if the resize event was triggered by user via moving a - // corner of the window frame or if it was caused by a windows state - // change, we check, if we are not in maximized state. + // If the first event after the mouse press is a resize event, then the user resizes + // the window instead of dragging it around. But there is one exception. If the window + // is maximized, then dragging the window via title bar will cause the widget to + // leave the maximized state. This in turn will trigger a resize event. To know, if the + // resize event was triggered by user via moving a corner of the window frame or if it + // was caused by a windows state change, we check, if we are not in maximized state. if (!isMaximized()) d->setState(DraggingInactive); break; @@ -882,8 +983,7 @@ bool FloatingDockContainer::event(QEvent *event) break; case DraggingFloatingWidget: - if (event->type() == QEvent::NonClientAreaMouseButtonRelease) - { + if (event->type() == QEvent::NonClientAreaMouseButtonRelease) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonRelease"; d->titleMouseReleaseEvent(); } @@ -893,17 +993,14 @@ bool FloatingDockContainer::event(QEvent *event) break; } -#if (ADS_DEBUG_LEVEL > 0) - qDebug() << Q_FUNC_INFO << event->type(); -#endif + qCInfo(adsLog) << Q_FUNC_INFO << event->type(); return QWidget::event(event); } void FloatingDockContainer::moveEvent(QMoveEvent *event) { QWidget::moveEvent(event); - switch (d->m_draggingState) - { + switch (d->m_draggingState) { case DraggingMousePressed: d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); @@ -911,15 +1008,85 @@ void FloatingDockContainer::moveEvent(QMoveEvent *event) case DraggingFloatingWidget: d->updateDropOverlays(QCursor::pos()); - // On macOS when hiding the DockAreaOverlay the application would set - // the main window as the active window for some reason. This fixes - // that by resetting the active window to the floating widget after - // updating the overlays. - QApplication::setActiveWindow(this); + // On macOS when hiding the DockAreaOverlay the application would set the main window as + // the active window for some reason. This fixes that by resetting the active window to + // the floating widget after updating the overlays. + activateWindow(); break; default: break; } } #endif + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +void FloatingDockContainer::onMaximizeRequest() +{ + if (windowState() == Qt::WindowMaximized) + showNormal(); + else + showMaximized(); +} + +void FloatingDockContainer::showNormal(bool fixGeometry) +{ + if (windowState() == Qt::WindowMaximized) { + QRect oldNormal = normalGeometry(); + Super::showNormal(); + if (fixGeometry) + setGeometry(oldNormal); + } + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(false); +} + +void FloatingDockContainer::showMaximized() +{ + Super::showMaximized(); + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(true); +} + +bool FloatingDockContainer::isMaximized() const +{ + return windowState() == Qt::WindowMaximized; +} + +void FloatingDockContainer::resizeEvent(QResizeEvent *event) +{ + d->m_isResizing = true; + Super::resizeEvent(event); +} + +void FloatingDockContainer::moveEvent(QMoveEvent *event) +{ + Super::moveEvent(event); + if (!d->m_isResizing && event->spontaneous() && d->m_mousePressed) { + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + d->m_isResizing = false; +} + +bool FloatingDockContainer::event(QEvent *e) +{ + bool result = Super::event(e); + switch (e->type()) { + case QEvent::WindowActivate: + d->m_mousePressed = false; + break; + case QEvent::WindowDeactivate: + d->m_mousePressed = true; + break; + default: + break; + } + return result; +} + +bool FloatingDockContainer::hasNativeTitleBar() +{ + return d->m_titleBar == nullptr; +} +#endif } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.h b/src/libs/advanceddockingsystem/floatingdockcontainer.h index e19ce428492..da2fc42138a 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.h +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.h @@ -8,7 +8,7 @@ #include #include -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) using FloatingWidgetBaseType = QDockWidget; #else using FloatingWidgetBaseType = QWidget; @@ -33,8 +33,8 @@ class DockingStateReader; /** * Pure virtual interface for floating widgets. - * This interface is used for opaque and non-opaque undocking. If opaque - * undocking is used, the a real FloatingDockContainer widget will be created + * This interface is used for opaque and non-opaque undocking. If opaque undocking is used, + * the a real FloatingDockContainer widget will be created. */ class AbstractFloatingWidget { @@ -42,8 +42,7 @@ public: virtual ~AbstractFloatingWidget() = 0; /** * Starts floating. - * This function should get called typically from a mouse press event - * handler + * This function should get called typically from a mouse press event handler. */ virtual void startFloating(const QPoint &dragStartMousePos, const QSize &size, @@ -52,26 +51,23 @@ public: = 0; /** - * Moves the widget to a new position relative to the position given when - * startFloating() was called. - * This function should be called from a mouse mouve event handler to - * move the floating widget on mouse move events. + * Moves the widget to a new position relative to the position given when startFloating() + * was called. This function should be called from a mouse mouve event handler to move the + * floating widget on mouse move events. */ virtual void moveFloating() = 0; /** - * Tells the widget that to finish dragging if the mouse is released. - * This function should be called from a mouse release event handler - * to finish the dragging + * Tells the widget that to finish dragging if the mouse is released. This function should be + * called from a mouse release event handler to finish the dragging. */ virtual void finishDragging() = 0; }; /** - * This implements a floating widget that is a dock container that accepts - * docking of dock widgets like the main window and that can be docked into - * another dock container. - * Every floating window of the docking system is a FloatingDockContainer. + * This implements a floating widget that is a dock container that accepts docking of dock widgets + * like the main window and that can be docked into another dock container. Every floating window + * of the docking system is a FloatingDockContainer. */ class ADS_EXPORT FloatingDockContainer : public FloatingWidgetBaseType, public AbstractFloatingWidget @@ -96,9 +92,8 @@ private: protected: /** - * Starts floating at the given global position. - * Use moveToGlobalPos() to move the widget to a new position - * depending on the start position given in Pos parameter + * Starts floating at the given global position. Use moveToGlobalPos() to move the widget + * to a new position depending on the start position given in Pos parameter. */ void startFloating(const QPoint &dragStartMousePos, const QSize &size, @@ -106,7 +101,7 @@ protected: QWidget *mouseEventHandler) override; /** - * Call this function to start dragging the floating widget + * Call this function to start dragging the floating widget. */ void startDragging(const QPoint &dragStartMousePos, const QSize &size, @@ -116,14 +111,13 @@ protected: } /** - * Call this function if you explicitly want to signal that dragging has - * finished + * Call this function if you explicitly want to signal that dragging has finished. */ void finishDragging() override; /** - * Call this function if you just want to initialize the position - * and size of the floating widget + * Call this function if you just want to initialize the position and size of the + * floating widget. */ void initFloatingGeometry(const QPoint &dragStartMousePos, const QSize &size) { @@ -131,21 +125,20 @@ protected: } /** - * Moves the widget to a new position relative to the position given when - * startFloating() was called + * Moves the widget to a new position relative to the position given when startFloating() + * was called. */ void moveFloating() override; /** - * Restores the state from given stream. - * If Testing is true, the function only parses the data from the given - * stream but does not restore anything. You can use this check for - * faulty files before you start restoring the state + * Restores the state from given stream. If Testing is true, the function only parses the + * data from the given stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state. */ bool restoreState(DockingStateReader &stream, bool testing); /** - * Call this function to update the window title + * Call this function to update the window title. */ void updateWindowTitle(); @@ -158,6 +151,10 @@ protected: // reimplements QWidget #ifdef Q_OS_MACOS virtual bool event(QEvent *event) override; virtual void moveEvent(QMoveEvent *event) override; +#elif defined(Q_OS_UNIX) + virtual bool event(QEvent *e) override; + virtual void moveEvent(QMoveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; #endif #ifdef Q_OS_WIN @@ -202,26 +199,61 @@ public: bool isClosable() const; /** - * This function returns true, if this floating widget has only one single - * visible dock widget in a single visible dock area. - * The single dock widget is a real top level floating widget because no - * other widgets are docked. + * This function returns true, if this floating widget has only one single visible dock widget + * in a single visible dock area. The single dock widget is a real top level floating widget + * because no other widgets are docked. */ bool hasTopLevelDockWidget() const; /** - * This function returns the first dock widget in the first dock area. - * If the function hasSingleDockWidget() returns true, then this function - * returns this single dock widget. + * This function returns the first dock widget in the first dock area. If the function + * hasSingleDockWidget() returns true, then this function returns this single dock widget. */ DockWidget *topLevelDockWidget() const; /** - * This function returns a list of all dock widget in this floating widget. - * This is a simple convenience function that simply calls the dockWidgets() - * function of the internal container widget. + * This function returns a list of all dock widget in this floating widget. This is a simple + * convenience function that simply calls the dockWidgets() function of the internal + * container widget. */ QList dockWidgets() const; + + /** + * This function hides the floating bar instantely and delete it later. + */ + void hideAndDeleteLater(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + /** + * This is a function that responds to FloatingWidgetTitleBar::maximizeRequest() + * Maximize or normalize the container size. + */ + void onMaximizeRequest(); + + /** + * Normalize (Unmaximize) the window. + * fixGeometry parameter fixes a "bug" in QT where immediately after calling showNormal + * geometry is not set properly. + * Set this true when moving the window immediately after normalizing. + */ + void showNormal(bool fixGeometry = false); + + /** + * Maximizes the window. + */ + void showMaximized(); + + /** + * Returns if the window is currently maximized or not. + */ + bool isMaximized() const; + + /** + * Returns true if the floating widget has a native titlebar or false if + * the floating widget has a QWidget based title bar + */ + bool hasNativeTitleBar(); +#endif }; // class FloatingDockContainer } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp index 3111e46c6bd..d9298870f99 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.cpp +++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp @@ -4,6 +4,7 @@ #include "floatingdragpreview.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" @@ -20,311 +21,359 @@ #include -namespace ADS +namespace ADS { + +/** + * Private data class (pimpl) + */ +class FloatingDragPreviewPrivate { +public: + FloatingDragPreview *q; + QWidget *m_content = nullptr; + DockAreaWidget *m_contentSourceArea = nullptr; + QPoint m_dragStartMousePosition; + DockManager *m_dockManager = nullptr; + DockContainerWidget *m_dropContainer = nullptr; + bool m_hidden = false; + QPixmap m_contentPreviewPixmap; + bool m_canceled = false; + /** - * Private data class (pimpl) + * Private data constructor */ - class FloatingDragPreviewPrivate + FloatingDragPreviewPrivate(FloatingDragPreview *parent); + void updateDropOverlays(const QPoint &globalPosition); + + void setHidden(bool value) { - public: - FloatingDragPreview *q; - QWidget *m_content = nullptr; - DockAreaWidget *m_contentSourceArea = nullptr; - QPoint m_dragStartMousePosition; - DockManager *m_dockManager = nullptr; - DockContainerWidget *m_dropContainer = nullptr; - bool m_hidden = false; - QPixmap m_contentPreviewPixmap; - bool m_canceled = false; - - /** - * Private data constructor - */ - FloatingDragPreviewPrivate(FloatingDragPreview *parent); - void updateDropOverlays(const QPoint &globalPosition); - - void setHidden(bool value) - { - m_hidden = value; - q->update(); - } - - /** - * Cancel dragging and emit the draggingCanceled event - */ - void cancelDragging() - { - m_canceled = true; - emit q->draggingCanceled(); - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); - q->close(); - } - - /** - * Creates the real floating widget in case the mouse is released outside - * outside of any drop area - */ - void createFloatingWidget(); - }; // class FloatingDragPreviewPrivate - - void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPosition) - { - if (!q->isVisible() || !m_dockManager) - return; - - auto containers = m_dockManager->dockContainers(); - DockContainerWidget *topContainer = nullptr; - for (auto containerWidget : containers) { - if (!containerWidget->isVisible()) - continue; - - const QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); - if (containerWidget->rect().contains(mappedPosition)) { - if (!topContainer || containerWidget->isInFrontOf(topContainer)) - topContainer = containerWidget; - } - } - - m_dropContainer = topContainer; - auto containerOverlay = m_dockManager->containerOverlay(); - auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); - auto dockDropArea = dockAreaOverlay->dropAreaUnderCursor(); - auto containerDropArea = containerOverlay->dropAreaUnderCursor(); - - if (!topContainer) { - containerOverlay->hideOverlay(); - dockAreaOverlay->hideOverlay(); - if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) - setHidden(false); - - return; - } - - const int visibleDockAreas = topContainer->visibleDockAreaCount(); - containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); - auto dockArea = topContainer->dockAreaAt(globalPosition); - if (dockArea && dockArea->isVisible() && visibleDockAreas >= 0 - && dockArea != m_contentSourceArea) { - dockAreaOverlay->enableDropPreview(true); - dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea - : dockArea->allowedAreas()); - DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); - - // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in the - // title bar. If the ContainerArea is valid then we ignore the dock area of the - // dockAreaOverlay() and disable the drop preview - if ((area == CenterDockWidgetArea) && (containerDropArea != InvalidDockWidgetArea)) { - dockAreaOverlay->enableDropPreview(false); - containerOverlay->enableDropPreview(true); - } else { - containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); - } - containerOverlay->showOverlay(topContainer); - } else { - dockAreaOverlay->hideOverlay(); - // If there is only one single visible dock area in a container, then - // it does not make sense to show a dock overlay because the dock area - // would be removed and inserted at the same position - if (visibleDockAreas <= 1) - containerOverlay->hideOverlay(); - else - containerOverlay->showOverlay(topContainer); - - if (dockArea == m_contentSourceArea && InvalidDockWidgetArea == containerDropArea) - m_dropContainer = nullptr; - } - - if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) { - setHidden(dockDropArea != InvalidDockWidgetArea - || containerDropArea != InvalidDockWidgetArea); - } + m_hidden = value; + q->update(); } - FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(FloatingDragPreview *parent) - : q(parent) - {} + /** + * Cancel dragging and emit the draggingCanceled event. + */ + void cancelDragging() + { + m_canceled = true; + emit q->draggingCanceled(); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); + q->close(); + } - void FloatingDragPreviewPrivate::createFloatingWidget() + /** + * Creates the real floating widget in case the mouse is released outside outside of any + * drop area. + */ + void createFloatingWidget(); + + /** + * Returns true, if the content is floatable. + */ + bool isContentFloatable() const { DockWidget *dockWidget = qobject_cast(m_content); - DockAreaWidget *dockArea = qobject_cast(m_content); - - FloatingDockContainer *floatingWidget = nullptr; - if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) - floatingWidget = new FloatingDockContainer(dockWidget); - else if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - floatingWidget = new FloatingDockContainer(dockArea); + return true; - if (floatingWidget) { - floatingWidget->setGeometry(q->geometry()); - floatingWidget->show(); - if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - QApplication::processEvents(); - int frameHeight = floatingWidget->frameGeometry().height() - floatingWidget->geometry().height(); - QRect fixedGeometry = q->geometry(); - fixedGeometry.adjust(0, frameHeight, 0, 0); - floatingWidget->setGeometry(fixedGeometry); - } + DockAreaWidget *dockArea = qobject_cast(m_content); + if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + return true; + + return false; + } +}; // class FloatingDragPreviewPrivate + +void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPosition) +{ + if (!q->isVisible() || !m_dockManager) + return; + + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) + continue; + + const QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPosition)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) + topContainer = containerWidget; } } - FloatingDragPreview::FloatingDragPreview(QWidget *content, QWidget *parent) - : QWidget(parent) - , d(new FloatingDragPreviewPrivate(this)) - { - d->m_content = content; - setAttribute(Qt::WA_DeleteOnClose); - if (DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + auto dockDropArea = dockAreaOverlay->dropAreaUnderCursor(); + auto containerDropArea = containerOverlay->dropAreaUnderCursor(); + + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) + setHidden(false); + + return; + } + + int visibleDockAreas = topContainer->visibleDockAreaCount(); + + // Include the overlay widget we're dragging as a visible widget + auto dockAreaWidget = qobject_cast(m_content); + if (dockAreaWidget && dockAreaWidget->isAutoHide()) + visibleDockAreas++; + + containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); + auto dockArea = topContainer->dockAreaAt(globalPosition); + if (dockArea && dockArea->isVisible() && visibleDockAreas >= 0 + && dockArea != m_contentSourceArea) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in the + // title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview. + if ((area == CenterDockWidgetArea) && (containerDropArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); } else { - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); } + containerOverlay->showOverlay(topContainer); + } else { + dockAreaOverlay->hideOverlay(); + // If there is only one single visible dock area in a container, then it does not make + // sense to show a dock overlay because the dock area would be removed and inserted at + // the same position. + if (visibleDockAreas == 1) + containerOverlay->hideOverlay(); + else + containerOverlay->showOverlay(topContainer); - if (Utils::HostOsInfo::isLinuxHost()) { - auto flags = windowFlags(); - flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; - setWindowFlags(flags); - } - - setWindowOpacity(0.6); - - // Create a static image of the widget that should get undocked - // This is like some kind preview image like it is uses in drag and drop operations - if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) { - d->m_contentPreviewPixmap = QPixmap(content->size()); - content->render(&d->m_contentPreviewPixmap); - } - connect(qApp, - &QApplication::applicationStateChanged, - this, - &FloatingDragPreview::onApplicationStateChanged); - // The only safe way to receive escape key presses is to install an event - // filter for the application object - QApplication::instance()->installEventFilter(this); + if (dockArea == m_contentSourceArea && InvalidDockWidgetArea == containerDropArea) + m_dropContainer = nullptr; } - FloatingDragPreview::FloatingDragPreview(DockWidget *content) - : FloatingDragPreview(static_cast(content), - content->dockManager()) // TODO static_cast? - { - d->m_dockManager = content->dockManager(); - if (content->dockAreaWidget()->openDockWidgetsCount() == 1) - d->m_contentSourceArea = content->dockAreaWidget(); + if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) + setHidden(dockDropArea != InvalidDockWidgetArea + || containerDropArea != InvalidDockWidgetArea); +} - setWindowTitle(content->windowTitle()); - } +FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(FloatingDragPreview *parent) + : q(parent) +{} - FloatingDragPreview::FloatingDragPreview(DockAreaWidget *content) - : FloatingDragPreview(static_cast(content), - content->dockManager()) // TODO static_cast? - { - d->m_dockManager = content->dockManager(); - d->m_contentSourceArea = content; - setWindowTitle(content->currentDockWidget()->windowTitle()); - } +void FloatingDragPreviewPrivate::createFloatingWidget() +{ + DockWidget *dockWidget = qobject_cast(m_content); + DockAreaWidget *dockArea = qobject_cast(m_content); - FloatingDragPreview::~FloatingDragPreview() { delete d; } + FloatingDockContainer *floatingWidget = nullptr; - void FloatingDragPreview::moveFloating() - { - const int borderSize = (frameSize().width() - size().width()) / 2; - const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - - QPoint(borderSize, 0); - move(moveToPos); - d->updateDropOverlays(QCursor::pos()); - } + if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) + floatingWidget = new FloatingDockContainer(dockWidget); + else if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + floatingWidget = new FloatingDockContainer(dockArea); - void FloatingDragPreview::startFloating(const QPoint &dragStartMousePos, - const QSize &size, - eDragState dragState, - QWidget *mouseEventHandler) - { - Q_UNUSED(mouseEventHandler) - Q_UNUSED(dragState) - resize(size); - d->m_dragStartMousePosition = dragStartMousePos; - moveFloating(); - show(); - } - - void FloatingDragPreview::finishDragging() - { - qCInfo(adsLog) << Q_FUNC_INFO; - auto dockDropArea = d->m_dockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(); - auto containerDropArea = d->m_dockManager->containerOverlay()->visibleDropAreaUnderCursor(); - if (!d->m_dropContainer) { - d->createFloatingWidget(); - } else if (dockDropArea != InvalidDockWidgetArea) { - d->m_dropContainer->dropWidget(d->m_content, dockDropArea, d->m_dropContainer->dockAreaAt(QCursor::pos())); - } else if (containerDropArea != InvalidDockWidgetArea) { - // If there is only one single dock area, and we drop into the center - // then we tabify the dropped widget into the only visible dock area - if (d->m_dropContainer->visibleDockAreaCount() <= 1 && CenterDockWidgetArea == containerDropArea) - d->m_dropContainer->dropWidget(d->m_content, containerDropArea, d->m_dropContainer->dockAreaAt(QCursor::pos())); - else - d->m_dropContainer->dropWidget(d->m_content, containerDropArea, nullptr); - } else { - d->createFloatingWidget(); - } - - this->close(); - d->m_dockManager->containerOverlay()->hideOverlay(); - d->m_dockManager->dockAreaOverlay()->hideOverlay(); - } - - void FloatingDragPreview::paintEvent(QPaintEvent *event) - { - Q_UNUSED(event) - if (d->m_hidden) - return; - - QPainter painter(this); - if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) - painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap); - - // If we do not have a window frame then we paint a QRubberBand like frameless window + if (floatingWidget) { + floatingWidget->setGeometry(q->geometry()); + floatingWidget->show(); if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - QColor color = palette().color(QPalette::Active, QPalette::Highlight); - QPen pen = painter.pen(); - pen.setColor(color.darker(120)); - pen.setStyle(Qt::SolidLine); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); - color = color.lighter(130); - color.setAlpha(64); - painter.setBrush(color); - painter.drawRect(rect().adjusted(0, 0, -1, -1)); + QApplication::processEvents(); + int frameHeight = floatingWidget->frameGeometry().height() + - floatingWidget->geometry().height(); + QRect fixedGeometry = q->geometry(); + fixedGeometry.adjust(0, frameHeight, 0, 0); + floatingWidget->setGeometry(fixedGeometry); } } +} - void FloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) - { - if (state != Qt::ApplicationActive) { - disconnect(qApp, - &QApplication::applicationStateChanged, - this, - &FloatingDragPreview::onApplicationStateChanged); +FloatingDragPreview::FloatingDragPreview(QWidget *content, QWidget *parent) + : QWidget(parent) + , d(new FloatingDragPreviewPrivate(this)) +{ + d->m_content = content; + setAttribute(Qt::WA_DeleteOnClose); + if (DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + } else { + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + } + + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) { + auto flags = windowFlags(); + flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; + setWindowFlags(flags); + } + + setWindowOpacity(0.6); + + // Create a static image of the widget that should get undocked. This is like some kind preview + // image like it is uses in drag and drop operations. + if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) { + d->m_contentPreviewPixmap = QPixmap(content->size()); + content->render(&d->m_contentPreviewPixmap); + } + connect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); + // The only safe way to receive escape key presses is to install an event filter for the + // application object. + QApplication::instance()->installEventFilter(this); +} + +FloatingDragPreview::FloatingDragPreview(DockWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? +{ + d->m_dockManager = content->dockManager(); + if (content->dockAreaWidget()->openDockWidgetsCount() == 1) + d->m_contentSourceArea = content->dockAreaWidget(); + + setWindowTitle(content->windowTitle()); +} + +FloatingDragPreview::FloatingDragPreview(DockAreaWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? +{ + d->m_dockManager = content->dockManager(); + d->m_contentSourceArea = content; + setWindowTitle(content->currentDockWidget()->windowTitle()); +} + +FloatingDragPreview::~FloatingDragPreview() +{ + delete d; +} + +void FloatingDragPreview::moveFloating() +{ + const int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - QPoint(borderSize, 0); + move(moveToPos); + d->updateDropOverlays(QCursor::pos()); +} + +void FloatingDragPreview::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) +{ + Q_UNUSED(mouseEventHandler) + Q_UNUSED(dragState) + resize(size); + d->m_dragStartMousePosition = dragStartMousePos; + moveFloating(); + show(); +} + +void FloatingDragPreview::finishDragging() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + auto dockDropArea = d->m_dockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(); + auto containerDropArea = d->m_dockManager->containerOverlay()->visibleDropAreaUnderCursor(); + bool validDropArea = (dockDropArea != InvalidDockWidgetArea) + || (containerDropArea != InvalidDockWidgetArea); + + // Non floatable auto hide widgets should stay in its current auto hide state if they are + // dragged into a floating window. + if (validDropArea || d->isContentFloatable()) + cleanupAutoHideContainerWidget(); + + if (!d->m_dropContainer) { + d->createFloatingWidget(); + } else if (dockDropArea != InvalidDockWidgetArea) { + d->m_dropContainer->dropWidget(d->m_content, + dockDropArea, + d->m_dropContainer->dockAreaAt(QCursor::pos())); + } else if (containerDropArea != InvalidDockWidgetArea) { + // If there is only one single dock area, and we drop into the center then we tabify the + // dropped widget into the only visible dock area. + if (d->m_dropContainer->visibleDockAreaCount() <= 1 + && CenterDockWidgetArea == containerDropArea) + d->m_dropContainer->dropWidget(d->m_content, + containerDropArea, + d->m_dropContainer->dockAreaAt(QCursor::pos())); + else + d->m_dropContainer->dropWidget(d->m_content, containerDropArea, nullptr); + } else { + d->createFloatingWidget(); + } + + close(); + d->m_dockManager->containerOverlay()->hideOverlay(); + d->m_dockManager->dockAreaOverlay()->hideOverlay(); +} + +void FloatingDragPreview::cleanupAutoHideContainerWidget() +{ + auto droppedDockWidget = qobject_cast(d->m_content); + if (droppedDockWidget && droppedDockWidget->autoHideDockContainer()) + droppedDockWidget->autoHideDockContainer()->cleanupAndDelete(); + + auto droppedArea = qobject_cast(d->m_content); + if (droppedArea && droppedArea->autoHideDockContainer()) + droppedArea->autoHideDockContainer()->cleanupAndDelete(); +} + +void FloatingDragPreview::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + if (d->m_hidden) + return; + + QPainter painter(this); + if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) + painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap); + + // If we do not have a window frame then we paint a QRubberBand like frameless window + if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + +void FloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) +{ + if (state != Qt::ApplicationActive) { + disconnect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); + d->cancelDragging(); + } +} + +bool FloatingDragPreview::eventFilter(QObject *watched, QEvent *event) +{ + if (!d->m_canceled && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + watched->removeEventFilter(this); d->cancelDragging(); } } - bool FloatingDragPreview::eventFilter(QObject *watched, QEvent *event) - { - if (!d->m_canceled && event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Escape) { - watched->removeEventFilter(this); - d->cancelDragging(); - } - } - - return false; - } + return false; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.h b/src/libs/advanceddockingsystem/floatingdragpreview.h index ed578da993a..80bbb6dbec9 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.h +++ b/src/libs/advanceddockingsystem/floatingdragpreview.h @@ -86,6 +86,11 @@ public: // implements AbstractFloatingWidget */ void finishDragging() override; + /** + * Cleanup auto hide container if the dragged widget has one. + */ + void cleanupAutoHideContainerWidget(); + signals: /** * This signal is emitted, if dragging has been canceled by escape key diff --git a/src/libs/advanceddockingsystem/iconprovider.cpp b/src/libs/advanceddockingsystem/iconprovider.cpp index b1db84c56ff..b8ff67e2552 100644 --- a/src/libs/advanceddockingsystem/iconprovider.cpp +++ b/src/libs/advanceddockingsystem/iconprovider.cpp @@ -6,44 +6,45 @@ #include namespace ADS { + +/** + * Private data class (pimpl) + */ +struct IconProviderPrivate +{ + IconProvider *q; + QVector m_userIcons{IconCount, QIcon()}; + /** - * Private data class (pimpl) + * Private data constructor */ - struct IconProviderPrivate - { - IconProvider *q; - QVector m_userIcons{IconCount, QIcon()}; + IconProviderPrivate(IconProvider *parent); +}; +// struct IconProviderPrivate - /** - * Private data constructor - */ - IconProviderPrivate(IconProvider *parent); - }; - // struct IconProviderPrivate +IconProviderPrivate::IconProviderPrivate(IconProvider *parent) + : q(parent) +{} - IconProviderPrivate::IconProviderPrivate(IconProvider *parent) - : q(parent) - {} +IconProvider::IconProvider() + : d(new IconProviderPrivate(this)) +{} - IconProvider::IconProvider() - : d(new IconProviderPrivate(this)) - {} +IconProvider::~IconProvider() +{ + delete d; +} - IconProvider::~IconProvider() - { - delete d; - } +QIcon IconProvider::customIcon(eIcon iconId) const +{ + Q_ASSERT(iconId < d->m_userIcons.size()); + return d->m_userIcons[iconId]; +} - QIcon IconProvider::customIcon(eIcon iconId) const - { - Q_ASSERT(iconId < d->m_userIcons.size()); - return d->m_userIcons[iconId]; - } - - void IconProvider::registerCustomIcon(eIcon iconId, const QIcon &icon) - { - Q_ASSERT(iconId < d->m_userIcons.size()); - d->m_userIcons[iconId] = icon; - } +void IconProvider::registerCustomIcon(eIcon iconId, const QIcon &icon) +{ + Q_ASSERT(iconId < d->m_userIcons.size()); + d->m_userIcons[iconId] = icon; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/iconprovider.h b/src/libs/advanceddockingsystem/iconprovider.h index ec72121ae9a..2675e17b9ee 100644 --- a/src/libs/advanceddockingsystem/iconprovider.h +++ b/src/libs/advanceddockingsystem/iconprovider.h @@ -12,8 +12,7 @@ namespace ADS { struct IconProviderPrivate; /** - * This object provides all icons that are required by the advanced docking - * system. + * This object provides all icons that are required by the advanced docking system. * The IconProvider enables the user to register custom icons in case using * stylesheets is not an option. */ diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp index 0201ba25995..699a0086fb9 100644 --- a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp @@ -4,8 +4,10 @@ #include "floatingwidgettitlebar.h" #include "ads_globals.h" +#include "dockmanager.h" #include "elidinglabel.h" #include "floatingdockcontainer.h" +#include "iconprovider.h" #include #include @@ -20,6 +22,7 @@ namespace ADS { using TabLabelType = ElidingLabel; using CloseButtonType = QToolButton; +using MaximizeButtonType = QToolButton; /** * @brief Private data class of public interface CFloatingWidgetTitleBar @@ -31,8 +34,12 @@ public: QLabel *m_iconLabel = nullptr; TabLabelType *m_titleLabel = nullptr; CloseButtonType *m_closeButton = nullptr; + MaximizeButtonType *m_maximizeButton = nullptr; FloatingDockContainer *m_floatingWidget = nullptr; eDragState m_dragState = DraggingInactive; + QIcon m_maximizeIcon; + QIcon m_normalIcon; + bool m_maximized = false; FloatingWidgetTitleBarPrivate(FloatingWidgetTitleBar *parent) : q(parent) @@ -46,15 +53,18 @@ public: void FloatingWidgetTitleBarPrivate::createLayout() { + // Title label m_titleLabel = new TabLabelType(); m_titleLabel->setElideMode(Qt::ElideRight); m_titleLabel->setText("DockWidget->windowTitle()"); m_titleLabel->setObjectName("floatingTitleLabel"); m_titleLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + // Close button m_closeButton = new CloseButtonType(); m_closeButton->setObjectName("floatingTitleCloseButton"); m_closeButton->setAutoRaise(true); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, ADS::FloatingWidgetCloseIcon); @@ -68,6 +78,21 @@ void FloatingWidgetTitleBarPrivate::createLayout() q, &FloatingWidgetTitleBar::closeRequested); + // Maximize button + m_maximizeButton = new MaximizeButtonType(); + m_maximizeButton->setObjectName("floatingTitleMaxButton"); + m_maximizeButton->setAutoRaise(true); + + m_maximizeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_maximizeButton->setIconSize(QSize(11, 11)); + m_maximizeButton->setFixedSize(QSize(17, 17)); + m_maximizeButton->setVisible(true); + m_maximizeButton->setFocusPolicy(Qt::NoFocus); + QObject::connect(m_maximizeButton, + &QPushButton::clicked, + q, + &FloatingWidgetTitleBar::maximizeRequested); + QFontMetrics fontMetrics(m_titleLabel->font()); int spacing = qRound(fontMetrics.height() / 4.0); @@ -78,6 +103,7 @@ void FloatingWidgetTitleBarPrivate::createLayout() q->setLayout(layout); layout->addWidget(m_titleLabel, 1); layout->addSpacing(spacing); + layout->addWidget(m_maximizeButton); layout->addWidget(m_closeButton); layout->addSpacing(1); layout->setAlignment(Qt::AlignCenter); @@ -91,6 +117,26 @@ FloatingWidgetTitleBar::FloatingWidgetTitleBar(FloatingDockContainer *parent) { d->m_floatingWidget = parent; d->createLayout(); + + d->m_normalIcon = DockManager::iconProvider().customIcon(ADS::FloatingWidgetNormalIcon); + if (d->m_normalIcon.isNull()) { + auto normalPixmap = style()->standardPixmap(QStyle::SP_TitleBarNormalButton, + 0, + d->m_maximizeButton); + d->m_normalIcon.addPixmap(normalPixmap, QIcon::Normal); + d->m_normalIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), + QIcon::Disabled); + } + + d->m_maximizeIcon = DockManager::iconProvider().customIcon(ADS::FloatingWidgetMaximizeIcon); + if (d->m_maximizeIcon.isNull()) { + auto maxPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, + 0, + d->m_maximizeButton); + d->m_maximizeIcon.addPixmap(maxPixmap, QIcon::Normal); + d->m_maximizeIcon.addPixmap(internal::createTransparentPixmap(maxPixmap, 0.25), + QIcon::Disabled); + } } FloatingWidgetTitleBar::~FloatingWidgetTitleBar() @@ -125,8 +171,11 @@ void FloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *event) return; } - // move floating window + // Move floating window if (DraggingFloatingWidget == d->m_dragState) { + if (d->m_floatingWidget->isMaximized()) + d->m_floatingWidget->showNormal(true); + d->m_floatingWidget->moveFloating(); Super::mouseMoveEvent(event); return; @@ -134,6 +183,16 @@ void FloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *event) Super::mouseMoveEvent(event); } +void FloatingWidgetTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) { + emit maximizeRequested(); + event->accept(); + } else { + QWidget::mouseDoubleClickEvent(event); + } +} + void FloatingWidgetTitleBar::enableCloseButton(bool enable) { d->m_closeButton->setEnabled(enable); @@ -149,4 +208,13 @@ void FloatingWidgetTitleBar::updateStyle() internal::repolishStyle(this, internal::RepolishDirectChildren); } +void FloatingWidgetTitleBar::setMaximizedIcon(bool maximized) +{ + d->m_maximized = maximized; + if (maximized) + d->m_maximizeButton->setIcon(d->m_normalIcon); + else + d->m_maximizeButton->setIcon(d->m_maximizeIcon); +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h index 0913fabd240..4b945bbe3b9 100644 --- a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace ADS { @@ -11,11 +12,10 @@ class FloatingDockContainer; class FloatingWidgetTitleBarPrivate; /** - * Titlebar for floating widgets to capture non client are mouse events. + * Title bar for floating widgets to capture non client area mouse events. * Linux does not support NonClientArea mouse events like - * QEvent::NonClientAreaMouseButtonPress. Because these events are required - * for the docking system to work properly, we use our own titlebar here to - * capture the required mouse events. + * QEvent::NonClientAreaMouseButtonPress. Because these events are required for the docking system + * to work properly, we use our own titlebar here to capture the required mouse events. */ class FloatingWidgetTitleBar : public QFrame { @@ -27,6 +27,7 @@ protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; public: using Super = QWidget; @@ -52,11 +53,21 @@ public: */ void updateStyle(); + /** + * Change the maximize button icon according to current windows state + */ + void setMaximizedIcon(bool maximized); + signals: /** * This signal is emitted, if the close button is clicked. */ void closeRequested(); + + /** + * This signal is emitted, if the maximize button is clicked. + */ + void maximizeRequested(); }; } // namespace ADS diff --git a/src/libs/advanceddockingsystem/pushbutton.cpp b/src/libs/advanceddockingsystem/pushbutton.cpp new file mode 100644 index 00000000000..ddc4b83ca88 --- /dev/null +++ b/src/libs/advanceddockingsystem/pushbutton.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "pushbutton.h" + +#include +#include +#include +#include + +namespace ADS { + +QSize PushButton::sizeHint() const +{ + QSize sh = QPushButton::sizeHint(); + + if (m_orientation != PushButton::Horizontal) + sh.transpose(); + + return sh; +} + +PushButton::Orientation PushButton::buttonOrientation() const +{ + return m_orientation; +} + +void PushButton::setButtonOrientation(Orientation orientation) +{ + m_orientation = orientation; + updateGeometry(); +} + +void PushButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + QStyleOptionButton option; + initStyleOption(&option); + + if (m_orientation == PushButton::VerticalTopToBottom) { + painter.rotate(90); + painter.translate(0, -1 * width()); + option.rect = option.rect.transposed(); + } else if (m_orientation == PushButton::VerticalBottomToTop) { + painter.rotate(-90); + painter.translate(-1 * height(), 0); + option.rect = option.rect.transposed(); + } + + painter.drawControl(QStyle::CE_PushButton, option); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/pushbutton.h b/src/libs/advanceddockingsystem/pushbutton.h new file mode 100644 index 00000000000..b3260a633d8 --- /dev/null +++ b/src/libs/advanceddockingsystem/pushbutton.h @@ -0,0 +1,47 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +/** + * ADS specific push button class with orientation support + */ +class ADS_EXPORT PushButton : public QPushButton +{ + Q_OBJECT + +public: + enum Orientation { + Horizontal, + VerticalTopToBottom, + VerticalBottomToTop + }; + + using QPushButton::QPushButton; + + virtual QSize sizeHint() const override; + + /** + * Returns the current orientation + */ + Orientation buttonOrientation() const; + + /** + * Set the orientation of this button + */ + void setButtonOrientation(Orientation orientation); + +protected: + virtual void paintEvent(QPaintEvent *event) override; + +private: + Orientation m_orientation = Horizontal; +}; // class PushButton + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/resizehandle.cpp b/src/libs/advanceddockingsystem/resizehandle.cpp new file mode 100644 index 00000000000..9cdccdeaada --- /dev/null +++ b/src/libs/advanceddockingsystem/resizehandle.cpp @@ -0,0 +1,269 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "resizehandle.h" + +#include +#include +#include +#include +#include +#include + +namespace ADS { + +/** + * Private data class of CResizeHandle class (pimpl) + */ +class ResizeHandlePrivate +{ +public: + ResizeHandle *q = nullptr; + Qt::Edge m_handlePosition = Qt::LeftEdge; + QWidget *m_target = nullptr; + int m_mouseOffset = 0; + bool m_pressed = false; + int m_minSize = 0; + int m_maxSize = 1; + QPointer m_rubberBand; + bool m_opaqueResize = false; + int m_handleWidth = 4; + + /** + * Private data constructor + */ + ResizeHandlePrivate(ResizeHandle *parent); + + /** + * Pick position component from pos depending on orientation + */ + int pick(const QPoint &pos) const + { + return q->orientation() == Qt::Horizontal ? pos.x() : pos.y(); + } + + /** + * Returns true, if orientation is horizontal + */ + bool isHorizontal() const { return q->orientation() == Qt::Horizontal; } + + /** + * Set rubberband position + */ + void setRubberBand(int pos); + + /** + * Calculates the resize position and geometry + */ + void doResizing(QMouseEvent *event, bool forceResize = false); +}; +// class ResizeHandlePrivate + +ResizeHandlePrivate::ResizeHandlePrivate(ResizeHandle *parent) + : q(parent) +{} + +void ResizeHandlePrivate::setRubberBand(int pos) +{ + if (!m_rubberBand) + m_rubberBand = new QRubberBand(QRubberBand::Line, m_target->parentWidget()); + + auto geometry = q->geometry(); + auto topLeft = m_target->mapTo(m_target->parentWidget(), geometry.topLeft()); + switch (m_handlePosition) { + case Qt::LeftEdge: + case Qt::RightEdge: + topLeft.rx() += pos; + break; + case Qt::TopEdge: + case Qt::BottomEdge: + topLeft.ry() += pos; + break; + } + + geometry.moveTopLeft(topLeft); + m_rubberBand->setGeometry(geometry); + m_rubberBand->show(); +} + +void ResizeHandlePrivate::doResizing(QMouseEvent *event, bool forceResize) +{ + int pos = pick(event->pos()) - m_mouseOffset; + auto oldGeometry = m_target->geometry(); + auto newGeometry = oldGeometry; + switch (m_handlePosition) { + case Qt::LeftEdge: { + newGeometry.adjust(pos, 0, 0, 0); + int size = qBound(m_minSize, newGeometry.width(), m_maxSize); + pos += (newGeometry.width() - size); + newGeometry.setWidth(size); + newGeometry.moveTopRight(oldGeometry.topRight()); + } break; + + case Qt::RightEdge: { + newGeometry.adjust(0, 0, pos, 0); + int size = qBound(m_minSize, newGeometry.width(), m_maxSize); + pos -= (newGeometry.width() - size); + newGeometry.setWidth(size); + } break; + + case Qt::TopEdge: { + newGeometry.adjust(0, pos, 0, 0); + int size = qBound(m_minSize, newGeometry.height(), m_maxSize); + pos += (newGeometry.height() - size); + newGeometry.setHeight(size); + newGeometry.moveBottomLeft(oldGeometry.bottomLeft()); + } break; + + case Qt::BottomEdge: { + newGeometry.adjust(0, 0, 0, pos); + int size = qBound(m_minSize, newGeometry.height(), m_maxSize); + pos -= (newGeometry.height() - size); + newGeometry.setHeight(size); + } break; + } + + if (q->opaqueResize() || forceResize) { + m_target->setGeometry(newGeometry); + } else { + setRubberBand(pos); + } +} + +ResizeHandle::ResizeHandle(Qt::Edge handlePosition, QWidget *parent) + : Super(parent) + , d(new ResizeHandlePrivate(this)) +{ + d->m_target = parent; + setMinResizeSize(48); + setHandlePosition(handlePosition); +} + +ResizeHandle::~ResizeHandle() +{ + delete d; +} + +void ResizeHandle::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton)) + return; + + d->doResizing(event); +} + +void ResizeHandle::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->m_mouseOffset = d->pick(event->pos()); + d->m_pressed = true; + update(); + } +} + +void ResizeHandle::mouseReleaseEvent(QMouseEvent *event) +{ + if (!opaqueResize() && event->button() == Qt::LeftButton) { + if (d->m_rubberBand) + d->m_rubberBand->deleteLater(); + + d->doResizing(event, true); + } + + if (event->button() == Qt::LeftButton) { + d->m_pressed = false; + update(); + } +} + +void ResizeHandle::setHandlePosition(Qt::Edge handlePosition) +{ + d->m_handlePosition = handlePosition; + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + setCursor(Qt::SizeHorCursor); + break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + setCursor(Qt::SizeVerCursor); + break; + } + + setMaxResizeSize(d->isHorizontal() ? parentWidget()->height() : parentWidget()->width()); + if (!d->isHorizontal()) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + } else { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + } +} + +Qt::Edge ResizeHandle::handlePostion() const +{ + return d->m_handlePosition; +} + +Qt::Orientation ResizeHandle::orientation() const +{ + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + return Qt::Horizontal; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + return Qt::Vertical; + } + + return Qt::Horizontal; +} + +QSize ResizeHandle::sizeHint() const +{ + QSize result; + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + result = QSize(d->m_handleWidth, d->m_target->height()); + break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + result = QSize(d->m_target->width(), d->m_handleWidth); + break; + } + + return result; +} + +bool ResizeHandle::isResizing() const +{ + return d->m_pressed; +} + +void ResizeHandle::setMinResizeSize(int minSize) +{ + d->m_minSize = minSize; +} + +void ResizeHandle::setMaxResizeSize(int maxSize) +{ + d->m_maxSize = maxSize; +} + +void ResizeHandle::setOpaqueResize(bool opaque) +{ + if (d->m_opaqueResize == opaque) + return; + + d->m_opaqueResize = opaque; + emit opaqueResizeChanged(); +} + +bool ResizeHandle::opaqueResize() const +{ + return d->m_opaqueResize; +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/resizehandle.h b/src/libs/advanceddockingsystem/resizehandle.h new file mode 100644 index 00000000000..540a946e071 --- /dev/null +++ b/src/libs/advanceddockingsystem/resizehandle.h @@ -0,0 +1,99 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +class ResizeHandlePrivate; + +/** + * Resize handle for resizing its parent widget + */ +class ADS_EXPORT ResizeHandle : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY(ResizeHandle) + Q_PROPERTY(bool opaqueResize READ opaqueResize WRITE setOpaqueResize NOTIFY opaqueResizeChanged) + +private: + ResizeHandlePrivate *d; ///< private data (pimpl) + friend class ResizeHandlePrivate; + +protected: + void mouseMoveEvent(QMouseEvent *) override; + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + +public: + using Super = QFrame; + + /** + * Default Constructor + */ + ResizeHandle(Qt::Edge handlePosition, QWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~ResizeHandle(); + + /** + * Sets the handle position + */ + void setHandlePosition(Qt::Edge handlePosition); + + /** + * Returns the handle position + */ + Qt::Edge handlePostion() const; + + /** + * Returns the orientation of this resize handle + */ + Qt::Orientation orientation() const; + + /** + * Returns the size hint + */ + QSize sizeHint() const override; + + /** + * Returns true, if resizing is active + */ + bool isResizing() const; + + /** + * Sets the minimum size for the widget that is going to be resized. + * The resize handle will not resize the target widget to a size smaller + * than this value + */ + void setMinResizeSize(int minSize); + + /** + * Sets the maximum size for the widget that is going to be resized + * The resize handle will not resize the target widget to a size bigger + * than this value + */ + void setMaxResizeSize(int maxSize); + + /** + * Enable / disable opaque resizing + */ + void setOpaqueResize(bool opaque = true); + + /** + * Returns true if widgets are resized dynamically (opaquely) while + * interactively moving the resize handle. Otherwise returns false. + */ + bool opaqueResize() const; + +signals: + void opaqueResizeChanged(); +}; // class ResizeHandle + +} // namespace ADS diff --git a/src/plugins/qmldesigner/components/resources/dockwidgets.css b/src/plugins/qmldesigner/components/resources/dockwidgets.css index 4ff1b46f52d..2fc05b6014f 100644 --- a/src/plugins/qmldesigner/components/resources/dockwidgets.css +++ b/src/plugins/qmldesigner/components/resources/dockwidgets.css @@ -93,6 +93,20 @@ ADS--TitleBarButton:press { background: creatorTheme.DStitleBarButtonPress; } +#floatingTitleMaxButton { + margin: 1px; + background: none; + border: none; +} + +#floatingTitleMaxButton:hover { + background: creatorTheme.DStitleBarButtonHover; +} + +#floatingTitleMaxButton:pressed { + background: creatorTheme.DStitleBarButtonPress; +} + QScrollArea#dockWidgetScrollArea { background-color: creatorTheme.DSpanelBackground; padding: 0px; @@ -139,3 +153,180 @@ ADS--DockAreaTitleBar { ADS--DockAreaWidget[focused="true"] ADS--DockAreaTitleBar { border-bottom-color: creatorTheme.DStabFocusBackground; } + +ADS--AutoHideTab { + qproperty-iconSize: 16px 16px; /* this is optional in case you would like to change icon size */ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + +ADS--AutoHideTab:hover { + color: creatorTheme.DSinteraction; +} + +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="0"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="1"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid creatorTheme.DSinteraction; +} + +/* Auto hide tabs with icon only */ +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + +/* Auto hide tabs with icon only hover */ +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid creatorTheme.DSinteraction; +} + +/* AutoHideSideBar */ +ADS--AutoHideSideBar { + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + +ADS--AutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + +/* AutoHideDockContainer */ +ADS--AutoHideDockContainer { + background: palette(window); +} + +ADS--AutoHideDockContainer ADS--DockAreaTitleBar { + background: creatorTheme.DSinteraction; + padding: 0px; + border: none; +} + +/* + * This is required because the ADS--DockAreaWidget[focused="true"] will + * overwrite the ADS--AutoHideDockContainer ADS--DockAreaTitleBar rule + */ +ADS--AutoHideDockContainer ADS--DockAreaWidget[focused="true"] ADS--DockAreaTitleBar { + background: creatorTheme.DSinteraction; + padding: 0px; + border: none; +} + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + +/* AutoHideDockContainer titlebar buttons */ +#dockAreaAutoHideButton { + /*qproperty-icon: url(:/ads/images/vs-pin-button.svg);*/ + qproperty-iconSize: 16px; +} + +ADS--AutoHideDockContainer #dockAreaAutoHideButton { + /*qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg);*/ + qproperty-iconSize: 16px; +} + +ADS--AutoHideDockContainer #dockAreaCloseButton{ + /*qproperty-icon: url(:/ads/images/close-button-focused.svg)*/ +} + +ADS--AutoHideDockContainer ADS--TitleBarButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CAutoHideDockContainer ADS--TitleBarButton:pressed { + background: rgba(255, 255, 255, 96); +} + +/* AutoHideDockContainer Titlebar and Buttons */ + +/* ResizeHandle */ +ADS--ResizeHandle { + background: palette(window); +} + + +ADS--AutoHideDockContainer[sideBarLocation="0"] ADS--ResizeHandle { + border-top: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="1"] ADS--ResizeHandle { + border-left: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="2"] ADS--ResizeHandle { + border-right: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="3"] ADS--ResizeHandle { + border-top: 1px solid palette(dark); +} diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index aab6aec18ec..e0fda6b367c 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -179,7 +179,11 @@ void DesignModeWidget::setup() ADS::DockManager::setConfigFlag(ADS::DockManager::DockAreaHasUndockButton, false); ADS::DockManager::setConfigFlag(ADS::DockManager::DockAreaHasTabsMenuButton, false); ADS::DockManager::setConfigFlag(ADS::DockManager::OpaqueSplitterResize, true); - ADS::DockManager::setConfigFlag(ADS::DockManager::AllTabsHaveCloseButton, true); + ADS::DockManager::setConfigFlag(ADS::DockManager::AllTabsHaveCloseButton, false); + ADS::DockManager::setConfigFlag(ADS::DockManager::RetainTabSizeWhenCloseButtonHidden, true); + + //ADS::DockManager::setAutoHideConfigFlags(ADS::DockManager::DefaultAutoHideConfig); + m_dockManager = new ADS::DockManager(this); m_dockManager->setSettings(settings); m_dockManager->setWorkspacePresetsPath( @@ -190,7 +194,9 @@ void DesignModeWidget::setup() m_dockManager->setStyleSheet(Theme::replaceCssColors(sheet)); // Setup icons - const QString closeUnicode = Theme::getIconUnicode(Theme::Icon::adsClose); + const QString closeUnicode = Theme::getIconUnicode(Theme::Icon::close_small); + const QString maximizeUnicode = Theme::getIconUnicode(Theme::Icon::maxBar_small); + const QString normalUnicode = Theme::getIconUnicode(Theme::Icon::normalBar_small); const QString fontName = "qtds_propertyIconFont.ttf"; const QSize size = QSize(28, 28); @@ -202,13 +208,41 @@ void DesignModeWidget::setup() auto tabCloseIconFocus = Utils::StyleHelper::IconFontHelper( closeUnicode, Theme::getColor(Theme::DSdockWidgetTitleBar), size, QIcon::Selected, QIcon::Off); - const QIcon tabsCloseIcon = Utils::StyleHelper::getIconFromIconFont( - fontName, {tabCloseIconNormal, - tabCloseIconActive, - tabCloseIconFocus}); + const QIcon tabsCloseIcon = Utils::StyleHelper::getIconFromIconFont(fontName, + {tabCloseIconNormal, + tabCloseIconActive, + tabCloseIconFocus}); ADS::DockManager::iconProvider().registerCustomIcon(ADS::TabCloseIcon, tabsCloseIcon); + auto floatingWidgetCloseIconNormal = Utils::StyleHelper::IconFontHelper( + closeUnicode, Theme::getColor(Theme::DStitleBarText), QSize(17, 17), QIcon::Normal, QIcon::Off); + const QIcon floatingWidgetCloseIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetCloseIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetCloseIcon, + floatingWidgetCloseIcon); + + auto floatingWidgetMaxIconNormal = Utils::StyleHelper::IconFontHelper(maximizeUnicode, + Theme::getColor( + Theme::DStitleBarText), + QSize(17, 17), + QIcon::Normal, + QIcon::Off); + const QIcon floatingWidgetMaxIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetMaxIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetMaximizeIcon, + floatingWidgetMaxIcon); + + auto floatingWidgetNormalIconNormal = Utils::StyleHelper::IconFontHelper( + normalUnicode, Theme::getColor(Theme::DStitleBarText), QSize(17, 17), QIcon::Normal, QIcon::Off); + const QIcon floatingWidgetNormalIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetNormalIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetNormalIcon, + floatingWidgetNormalIcon); + // Setup Actions and Menus Core::ActionContainer *mview = Core::ActionManager::actionContainer(Core::Constants::M_VIEW); // View > Views