/******************************************************************************* ** 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 DockAreaWidget.cpp /// \author Uwe Kindler /// \date 24.02.2017 /// \brief Implementation of CDockAreaWidget class //============================================================================ //============================================================================ // INCLUDES //============================================================================ #include #include "DockAreaWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "DockContainerWidget.h" #include "DockWidget.h" #include "FloatingDockContainer.h" #include "DockManager.h" #include "DockOverlay.h" #include "DockAreaTabBar.h" #include "DockSplitter.h" #include "DockAreaTitleBar.h" #include namespace ads { static const char* const INDEX_PROPERTY = "index"; static const char* const ACTION_PROPERTY = "action"; static const char* const DOCKWIDGET_PROPERTY = "dockwidget"; static const int APPEND = -1; /** * New dock area layout mimics stack layout but ony inserts the current * widget */ class CDockAreaLayout { private: QBoxLayout* m_ParentLayout; QList m_Widgets; int m_CurrentIndex = -1; QWidget* m_CurrentWidget = nullptr; public: CDockAreaLayout(QBoxLayout* ParentLayout) : m_ParentLayout(ParentLayout) { } int count() const { return m_Widgets.count(); } void insertWidget(int index, QWidget* Widget) { Widget->setParent(0); 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; } } } void removeWidget(QWidget* Widget) { if (currentWidget() == Widget) { auto LayoutItem = m_ParentLayout->takeAt(1); if (LayoutItem) { LayoutItem->widget()->setParent(0); } m_CurrentWidget = nullptr; m_CurrentIndex = -1; } m_Widgets.removeOne(Widget); } QWidget* currentWidget() const { return m_CurrentWidget; } void setCurrentIndex(int index) { QWidget *prev = currentWidget(); QWidget *next = widget(index); if (!next || (next == prev && !m_CurrentWidget)) { return; } bool reenableUpdates = false; QWidget *parent = m_ParentLayout->parentWidget(); if (parent && parent->updatesEnabled()) { reenableUpdates = true; parent->setUpdatesEnabled(false); } auto LayoutItem = m_ParentLayout->takeAt(1); if (LayoutItem) { LayoutItem->widget()->setParent(0); } m_ParentLayout->addWidget(next); if (prev) { prev->hide(); } m_CurrentIndex = index; m_CurrentWidget = next; if (reenableUpdates) { parent->setUpdatesEnabled(true); } } int currentIndex() const { return m_CurrentIndex; } bool isEmpty() const { return m_Widgets.empty(); } int indexOf(QWidget* w) const { return m_Widgets.indexOf(w); } QWidget* widget(int index) const { return (index < m_Widgets.size()) ? m_Widgets.at(index) : nullptr; } QRect geometry() const { return m_Widgets.empty() ? QRect() : currentWidget()->geometry(); } }; using DockAreaLayout = CDockAreaLayout; /** * Private data class of CDockAreaWidget class (pimpl) */ struct DockAreaWidgetPrivate { CDockAreaWidget* _this; QBoxLayout* Layout; DockAreaLayout* ContentsLayout; CDockAreaTitleBar* TitleBar; CDockManager* DockManager = nullptr; /** * Private data constructor */ DockAreaWidgetPrivate(CDockAreaWidget* _public); /** * Creates the layout for top area with tabs and close button */ void createTitleBar(); /** * Returns the dock widget with the given index */ CDockWidget* dockWidgetAt(int index) { return dynamic_cast(ContentsLayout->widget(index)); } /** * Convenience function to ease title widget access by index */ CDockWidgetTab* tabWidgetAt(int index) { return dockWidgetAt(index)->tabWidget(); } /** * Returns the tab action of the given dock widget */ QAction* dockWidgetTabAction(CDockWidget* DockWidget) const { return qvariant_cast(DockWidget->property(ACTION_PROPERTY)); } /** * Returns the index of the given dock widget */ int dockWidgetIndex(CDockWidget* DockWidget) const { return DockWidget->property(INDEX_PROPERTY).toInt(); } /** * Updates the tab bar visibility depending on the number of dock widgets * in this area */ void updateTabBar(); /** * Convenience function for tabbar access */ CDockAreaTabBar* tabBar() const { return TitleBar->tabBar(); } }; // struct DockAreaWidgetPrivate //============================================================================ DockAreaWidgetPrivate::DockAreaWidgetPrivate(CDockAreaWidget* _public) : _this(_public) { } //============================================================================ void DockAreaWidgetPrivate::createTitleBar() { TitleBar = new CDockAreaTitleBar(_this); Layout->addWidget(TitleBar); _this->connect(tabBar(), SIGNAL(tabCloseRequested(int)), SLOT(onTabCloseRequested(int))); _this->connect(TitleBar, SIGNAL(tabBarClicked(int)), SLOT(setCurrentIndex(int))); _this->connect(tabBar(), SIGNAL(tabMoved(int, int)), SLOT(reorderDockWidget(int, int))); } //============================================================================ void DockAreaWidgetPrivate::updateTabBar() { CDockContainerWidget* Container = _this->dockContainer(); if (!Container) { return; } TitleBar->setVisible(!Container->isFloating() || !Container->hasTopLevelDockWidget()); } //============================================================================ CDockAreaWidget::CDockAreaWidget(CDockManager* DockManager, CDockContainerWidget* parent) : QFrame(parent), d(new DockAreaWidgetPrivate(this)) { d->DockManager = DockManager; d->Layout = new QBoxLayout(QBoxLayout::TopToBottom); d->Layout->setContentsMargins(0, 0, 0, 0); d->Layout->setSpacing(0); setLayout(d->Layout); d->createTitleBar(); d->ContentsLayout = new DockAreaLayout(d->Layout); } //============================================================================ CDockAreaWidget::~CDockAreaWidget() { qDebug() << "~CDockAreaWidget()"; delete d->ContentsLayout; delete d; } //============================================================================ CDockManager* CDockAreaWidget::dockManager() const { return d->DockManager; } //============================================================================ CDockContainerWidget* CDockAreaWidget::dockContainer() const { return internal::findParent(this); } //============================================================================ void CDockAreaWidget::addDockWidget(CDockWidget* DockWidget) { insertDockWidget(d->ContentsLayout->count(), DockWidget); } //============================================================================ void CDockAreaWidget::insertDockWidget(int index, CDockWidget* DockWidget, bool Activate) { d->ContentsLayout->insertWidget(index, DockWidget); DockWidget->tabWidget()->setDockAreaWidget(this); auto TabWidget = DockWidget->tabWidget(); d->tabBar()->insertTab(index, TabWidget); TabWidget->setVisible(!DockWidget->isClosed()); DockWidget->setProperty(INDEX_PROPERTY, index); if (Activate) { setCurrentIndex(index); } DockWidget->setDockArea(this); } //============================================================================ void CDockAreaWidget::removeDockWidget(CDockWidget* DockWidget) { qDebug() << "CDockAreaWidget::removeDockWidget"; std::cout << "CDockAreaWidget::removeDockWidget" << std::endl; auto NextOpenDockWidget = nextOpenDockWidget(DockWidget); d->ContentsLayout->removeWidget(DockWidget); auto TabWidget = DockWidget->tabWidget(); TabWidget->hide(); d->tabBar()->removeTab(TabWidget); if (NextOpenDockWidget) { setCurrentDockWidget(NextOpenDockWidget); } else if (d->ContentsLayout->isEmpty()) { qDebug() << "Dock Area empty"; dockContainer()->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(); } d->updateTabBar(); auto TopLevelDockWidget = dockContainer()->topLevelDockWidget(); if (TopLevelDockWidget) { TopLevelDockWidget->emitTopLevelChanged(true); } #if (ADS_DEBUG_LEVEL > 0) CDockContainerWidget* DockContainer = dockContainer(); DockContainer->dumpLayout(); #endif } //============================================================================ void CDockAreaWidget::hideAreaWithNoVisibleContent() { this->toggleView(false); // Hide empty parent splitter auto Splitter = internal::findParent(this); while (Splitter && Splitter->isVisible()) { if (!Splitter->hasVisibleContent()) { Splitter->hide(); } Splitter = internal::findParent(Splitter); } //Hide empty floating widget CDockContainerWidget* Container = this->dockContainer(); if (Container->isFloating() && Container->openedDockAreas().isEmpty()) { CFloatingDockContainer* FloatingWidget = internal::findParent(Container); FloatingWidget->hide(); } } //============================================================================ void CDockAreaWidget::onTabCloseRequested(int Index) { qDebug() << "CDockAreaWidget::onTabCloseRequested " << Index; dockWidget(Index)->toggleView(false); } //============================================================================ CDockWidget* CDockAreaWidget::currentDockWidget() const { return dockWidget(currentIndex()); } //============================================================================ void CDockAreaWidget::setCurrentDockWidget(CDockWidget* DockWidget) { if (dockManager()->isRestoringState()) { return; } internalSetCurrentDockWidget(DockWidget); } //============================================================================ void CDockAreaWidget::internalSetCurrentDockWidget(CDockWidget* DockWidget) { int Index = index(DockWidget); if (Index < 0) { return; } setCurrentIndex(Index); } //============================================================================ void CDockAreaWidget::setCurrentIndex(int index) { auto TabBar = d->tabBar(); if (index < 0 || index > (TabBar->count() - 1)) { qWarning() << Q_FUNC_INFO << "Invalid index" << index; return; } emit currentChanging(index); TabBar->setCurrentIndex(index); d->ContentsLayout->setCurrentIndex(index); d->ContentsLayout->currentWidget()->show(); emit currentChanged(index); } //============================================================================ int CDockAreaWidget::currentIndex() const { return d->ContentsLayout->currentIndex(); } //============================================================================ QRect CDockAreaWidget::titleBarGeometry() const { return d->TitleBar->geometry(); } //============================================================================ QRect CDockAreaWidget::contentAreaGeometry() const { return d->ContentsLayout->geometry(); } //============================================================================ int CDockAreaWidget::index(CDockWidget* DockWidget) { return d->ContentsLayout->indexOf(DockWidget); } //============================================================================ QList CDockAreaWidget::dockWidgets() const { QList DockWidgetList; for (int i = 0; i < d->ContentsLayout->count(); ++i) { DockWidgetList.append(dockWidget(i)); } return DockWidgetList; } //============================================================================ int CDockAreaWidget::openDockWidgetsCount() const { int Count = 0; for (int i = 0; i < d->ContentsLayout->count(); ++i) { if (!dockWidget(i)->isClosed()) { ++Count; } } return Count; } //============================================================================ QList CDockAreaWidget::openedDockWidgets() const { QList DockWidgetList; for (int i = 0; i < d->ContentsLayout->count(); ++i) { CDockWidget* DockWidget = dockWidget(i); if (!DockWidget->isClosed()) { DockWidgetList.append(dockWidget(i)); } } return DockWidgetList; } //============================================================================ int CDockAreaWidget::dockWidgetsCount() const { return d->ContentsLayout->count(); } //============================================================================ CDockWidget* CDockAreaWidget::dockWidget(int Index) const { return dynamic_cast(d->ContentsLayout->widget(Index)); } //============================================================================ void CDockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) { qDebug() << "CDockAreaWidget::reorderDockWidget"; if (fromIndex >= d->ContentsLayout->count() || fromIndex < 0 || toIndex >= d->ContentsLayout->count() || toIndex < 0 || fromIndex == toIndex) { qDebug() << "Invalid index for tab movement" << fromIndex << toIndex; return; } auto Widget = d->ContentsLayout->widget(fromIndex); d->ContentsLayout->removeWidget(Widget); d->ContentsLayout->insertWidget(toIndex, Widget); setCurrentIndex(toIndex); } //============================================================================ void CDockAreaWidget::toggleDockWidgetView(CDockWidget* DockWidget, bool Open) { Q_UNUSED(DockWidget); Q_UNUSED(Open); updateTabBarVisibility(); } //============================================================================ void CDockAreaWidget::updateTabBarVisibility() { d->updateTabBar(); } //============================================================================ void CDockAreaWidget::saveState(QXmlStreamWriter& s) const { s.writeStartElement("DockAreaWidget"); s.writeAttribute("Tabs", QString::number(d->ContentsLayout->count())); s.writeAttribute("CurrentDockWidget", currentDockWidget()->objectName()); qDebug() << "CDockAreaWidget::saveState TabCount: " << d->ContentsLayout->count() << " CurrentDockWidge: " << currentDockWidget()->objectName(); for (int i = 0; i < d->ContentsLayout->count(); ++i) { dockWidget(i)->saveState(s); } s.writeEndElement(); } //============================================================================ CDockWidget* CDockAreaWidget::nextOpenDockWidget(CDockWidget* DockWidget) const { auto OpenDockWidgets = openedDockWidgets(); if (OpenDockWidgets.count() > 1 || (OpenDockWidgets.count() == 1 && OpenDockWidgets[0] != DockWidget)) { CDockWidget* NextDockWidget; if (OpenDockWidgets.last() == DockWidget) { NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2]; } else { int NextIndex = OpenDockWidgets.indexOf(DockWidget) + 1; NextDockWidget = OpenDockWidgets[NextIndex]; } return NextDockWidget; } else { return nullptr; } } //============================================================================ CDockWidget::DockWidgetFeatures CDockAreaWidget::features() const { CDockWidget::DockWidgetFeatures Features(CDockWidget::AllDockWidgetFeatures); for (const auto DockWidget : dockWidgets()) { Features &= DockWidget->features(); } return Features; } //============================================================================ void CDockAreaWidget::toggleView(bool Open) { setVisible(Open); emit viewToggled(Open); } } // namespace ads //--------------------------------------------------------------------------- // EOF DockAreaWidget.cpp