/******************************************************************************* ** Qt Advanced Docking System ** Copyright (C) 2017 Uwe Kindler ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; If not, see . ******************************************************************************/ //============================================================================ /// \file AutoHideDockContainer.cpp /// \author Syarif Fakhri /// \date 05.09.2022 /// \brief Implementation of CAutoHideDockContainer class //============================================================================ //============================================================================ // INCLUDES //============================================================================ #include "AutoHideDockContainer.h" #include #include #include #include #include #include #include #include "DockManager.h" #include "DockAreaWidget.h" #include "ResizeHandle.h" #include "DockComponentsFactory.h" #include "AutoHideSideBar.h" #include "AutoHideTab.h" #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 { CAutoHideDockContainer* _this; CDockAreaWidget* DockArea{nullptr}; CDockWidget* DockWidget{nullptr}; SideBarLocation SideTabBarArea = SideBarNone; QBoxLayout* Layout = nullptr; CResizeHandle* ResizeHandle = nullptr; QSize Size; // creates invalid size QPointer SideTab; QSize SizeCache; /** * Private data constructor */ AutoHideDockContainerPrivate(CAutoHideDockContainer *_public); /** * Convenience function to ease access to dock manager components factory */ QSharedPointer componentsFactory() const { if (!DockWidget || !DockWidget->dockManager()) { return CDockComponentsFactory::factory(); } else { return DockWidget->dockManager()->componentsFactory(); } } /** * 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 = _this->dockContainer()->contentRect(); const auto maxResizeHandleSize = ResizeHandle->orientation() == Qt::Horizontal ? Rect.width() : Rect.height(); ResizeHandle->setMaxResizeSize(maxResizeHandleSize - ResizeMargin); } /** * Convenience function to check, if this is an horizontal area */ bool isHorizontal() const { return isHorizontalArea(SideTabBarArea); } /** * Forward this event to the dock container */ void forwardEventToDockContainer(QEvent* event) { auto DockContainer = _this->dockContainer(); if (DockContainer) { DockContainer->handleAutoHideWidgetEvent(event, _this); } } }; // struct AutoHideDockContainerPrivate //============================================================================ AutoHideDockContainerPrivate::AutoHideDockContainerPrivate( CAutoHideDockContainer *_public) : _this(_public) { } //============================================================================ CDockContainerWidget* CAutoHideDockContainer::dockContainer() const { return internal::findParent(this); } //============================================================================ CAutoHideDockContainer::CAutoHideDockContainer(CDockWidget* DockWidget, SideBarLocation area, CDockContainerWidget* parent) : Super(parent), d(new AutoHideDockContainerPrivate(this)) { hide(); // auto hide dock container is initially always hidden d->SideTabBarArea = area; d->SideTab = d->componentsFactory()->createDockWidgetSideTab(nullptr); connect(d->SideTab, &CAutoHideTab::pressed, this, &CAutoHideDockContainer::toggleCollapseState); d->DockArea = new CDockAreaWidget(DockWidget->dockManager(), parent); d->DockArea->setObjectName("autoHideDockArea"); d->DockArea->setAutoHideDockContainer(this); setObjectName("autoHideDockContainer"); d->Layout = new QBoxLayout(isHorizontalArea(area) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); d->Layout->setContentsMargins(0, 0, 0, 0); d->Layout->setSpacing(0); setLayout(d->Layout); d->ResizeHandle = new CResizeHandle(edgeFromSideTabBarArea(area), this); d->ResizeHandle->setMinResizeSize(64); bool OpaqueResize = CDockManager::testConfigFlag(CDockManager::OpaqueSplitterResize); d->ResizeHandle->setOpaqueResize(OpaqueResize); d->Size = d->DockArea->size(); d->SizeCache = DockWidget->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->Layout->addWidget(d->DockArea); d->Layout->insertWidget(resizeHandleLayoutPosition(area), d->ResizeHandle); } //============================================================================ void CAutoHideDockContainer::updateSize() { auto dockContainerParent = dockContainer(); if (!dockContainerParent) { return; } auto rect = dockContainerParent->contentRect(); switch (sideBarLocation()) { case SideBarLocation::SideBarTop: resize(rect.width(), qMin(rect.height() - ResizeMargin, d->Size.height())); move(rect.topLeft()); break; case SideBarLocation::SideBarLeft: resize(qMin(d->Size.width(), rect.width() - ResizeMargin), rect.height()); move(rect.topLeft()); break; case SideBarLocation::SideBarRight: { resize(qMin(d->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->Size.height())); QPoint p = rect.bottomLeft(); p.ry() -= (height() - 1); move(p); } break; default: break; } if (orientation() == Qt::Horizontal) { d->SizeCache.setHeight(this->height()); } else { d->SizeCache.setWidth(this->width()); } } //============================================================================ CAutoHideDockContainer::~CAutoHideDockContainer() { ADS_PRINT("~CAutoHideDockContainer"); // Remove event filter in case there are any queued messages qApp->removeEventFilter(this); if (dockContainer()) { dockContainer()->removeAutoHideWidget(this); } if (d->SideTab) { delete d->SideTab; } delete d; } //============================================================================ CAutoHideSideBar* CAutoHideDockContainer::autoHideSideBar() const { if (d->SideTab) { return d->SideTab->sideBar(); } else { auto DockContainer = dockContainer(); return DockContainer ? DockContainer->autoHideSideBar(d->SideTabBarArea) : nullptr; } } //============================================================================ CAutoHideTab* CAutoHideDockContainer::autoHideTab() const { return d->SideTab; } //============================================================================ CDockWidget* CAutoHideDockContainer::dockWidget() const { return d->DockWidget; } //============================================================================ void CAutoHideDockContainer::addDockWidget(CDockWidget* DockWidget) { if (d->DockWidget) { // Remove the old dock widget at this area d->DockArea->removeDockWidget(d->DockWidget); } d->DockWidget = DockWidget; d->SideTab->setDockWidget(DockWidget); CDockAreaWidget* 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->Size = OldDockArea->size() + QSize(16, 16); OldDockArea->removeDockWidget(DockWidget); } d->DockArea->addDockWidget(DockWidget); updateSize(); // The dock area is not visible and will not update the size when updateSize() // is called for this auto hide container. Therefore we explicitly resize // it here. As soon as it will become visible, it will get the right size d->DockArea->resize(size()); } //============================================================================ SideBarLocation CAutoHideDockContainer::sideBarLocation() const { return d->SideTabBarArea; } //============================================================================ void CAutoHideDockContainer::setSideBarLocation(SideBarLocation SideBarLocation) { if (d->SideTabBarArea == SideBarLocation) { return; } d->SideTabBarArea = SideBarLocation; d->Layout->removeWidget(d->ResizeHandle); d->Layout->setDirection(isHorizontalArea(SideBarLocation) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); d->Layout->insertWidget(resizeHandleLayoutPosition(SideBarLocation), d->ResizeHandle); d->ResizeHandle->setHandlePosition(edgeFromSideTabBarArea(SideBarLocation)); internal::repolishStyle(this, internal::RepolishDirectChildren); } //============================================================================ CDockAreaWidget* CAutoHideDockContainer::dockAreaWidget() const { return d->DockArea; } //============================================================================ void CAutoHideDockContainer::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->DockWidget->setDockArea(nullptr); auto DockContainer = dockContainer(); DockContainer->addDockWidget(d->getDockWidgetArea(d->SideTabBarArea), d->DockWidget); } //============================================================================ void CAutoHideDockContainer::cleanupAndDelete() { const auto dockWidget = d->DockWidget; if (dockWidget) { auto SideTab = d->SideTab; SideTab->removeFromSideBar(); SideTab->setParent(nullptr); SideTab->hide(); } hide(); deleteLater(); } //============================================================================ void CAutoHideDockContainer::saveState(QXmlStreamWriter& s) { s.writeStartElement("Widget"); s.writeAttribute("Name", d->DockWidget->objectName()); s.writeAttribute("Closed", QString::number(d->DockWidget->isClosed() ? 1 : 0)); s.writeAttribute("Size", QString::number(d->isHorizontal() ? d->Size.height() : d->Size.width())); s.writeEndElement(); } //============================================================================ void CAutoHideDockContainer::toggleView(bool Enable) { if (Enable) { if (d->SideTab) { d->SideTab->show(); } } else { if (d->SideTab) { d->SideTab->hide(); } hide(); qApp->removeEventFilter(this); } } //============================================================================ void CAutoHideDockContainer::collapseView(bool Enable) { if (Enable) { hide(); qApp->removeEventFilter(this); } else { updateSize(); d->updateResizeHandleSizeLimitMax(); raise(); show(); d->DockWidget->dockManager()->setDockWidgetFocused(d->DockWidget); qApp->installEventFilter(this); } ADS_PRINT("CAutoHideDockContainer::collapseView " << Enable); d->SideTab->updateStyle(); } //============================================================================ void CAutoHideDockContainer::toggleCollapseState() { collapseView(isVisible()); } //============================================================================ void CAutoHideDockContainer::setSize(int Size) { if (d->isHorizontal()) { d->Size.setHeight(Size); } else { d->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 CAutoHideDockContainer::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->ResizeHandle->isResizing()) { updateSize(); } } else if (event->type() == QEvent::MouseButtonPress) { auto widget = qobject_cast(watched); // Ignore non widget events if (!widget) { return Super::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->SideTab.data()) { return Super::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 Super::eventFilter(watched, event); } // Ignore the mouse click if it is not inside of this container if (!isObjectOrAncestor(widget, dockContainer())) { return Super::eventFilter(watched, event); } // user clicked outside of autohide container - collapse the auto hide widget if (CDockManager::testAutoHideConfigFlag( CDockManager::AutoHideCloseOnOutsideMouseClick)) { collapseView(true); } } else if (event->type() == internal::FloatingWidgetDragStartEvent) { // If we are dragging our own floating widget, the we do not need to // collapse the view auto FloatingWidget = dockContainer()->floatingWidget(); if (FloatingWidget != watched) { collapseView(true); } } else if (event->type() == internal::DockedWidgetDragStartEvent) { collapseView(true); } return Super::eventFilter(watched, event); } //============================================================================ void CAutoHideDockContainer::resizeEvent(QResizeEvent* event) { Super::resizeEvent(event); if (d->ResizeHandle->isResizing()) { d->Size = this->size(); d->updateResizeHandleSizeLimitMax(); } } //============================================================================ void CAutoHideDockContainer::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); } Super::leaveEvent(event); } //============================================================================ bool CAutoHideDockContainer::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 Super::event(event); } //============================================================================ void CAutoHideDockContainer::dragLeaveEvent(QDragLeaveEvent*) { if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover)) { collapseView(true); } } //============================================================================ Qt::Orientation CAutoHideDockContainer::orientation() const { return ads::internal::isHorizontalSideBarLocation(d->SideTabBarArea) ? Qt::Horizontal : Qt::Vertical; } //============================================================================ void CAutoHideDockContainer::resetToInitialDockWidgetSize() { if (orientation() == Qt::Horizontal) { setSize(d->SizeCache.height()); } else { setSize(d->SizeCache.width()); } } //============================================================================ void CAutoHideDockContainer::moveToNewSideBarLocation(SideBarLocation NewSideBarLocation, int TabIndex) { if (NewSideBarLocation == sideBarLocation() && TabIndex == this->tabIndex()) { return; } auto OldOrientation = orientation(); auto SideBar = dockContainer()->autoHideSideBar(NewSideBarLocation); SideBar->addAutoHideWidget(this, TabIndex); // If we move a horizontal auto hide container to a vertical position // then we resize it to the original dock widget size, to avoid // an extremely stretched dock widget after insertion if (SideBar->orientation() != OldOrientation) { resetToInitialDockWidgetSize(); } } //============================================================================ int CAutoHideDockContainer::tabIndex() const { return d->SideTab->tabIndex(); } }