File indexing completed on 2024-06-02 03:47:27

0001 /*******************************************************************************
0002 ** Qt Advanced Docking System
0003 ** Copyright (C) 2017 Uwe Kindler
0004 **
0005 ** This library is free software; you can redistribute it and/or
0006 ** modify it under the terms of the GNU Lesser General Public
0007 ** License as published by the Free Software Foundation; either
0008 ** version 2.1 of the License, or (at your option) any later version.
0009 **
0010 ** This library is distributed in the hope that it will be useful,
0011 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013 ** Lesser General Public License for more details.
0014 **
0015 ** You should have received a copy of the GNU Lesser General Public
0016 ** License along with this library; If not, see <http://www.gnu.org/licenses/>.
0017 ******************************************************************************/
0018 
0019 
0020 //============================================================================
0021 /// \file   DockAreaTabBar.cpp
0022 /// \author Uwe Kindler
0023 /// \date   24.08.2018
0024 /// \brief  Implementation of CDockAreaTabBar class
0025 //============================================================================
0026 
0027 //============================================================================
0028 //                                   INCLUDES
0029 //============================================================================
0030 #include "FloatingDragPreview.h"
0031 #include "DockAreaTabBar.h"
0032 
0033 #include <QMouseEvent>
0034 #include <QScrollBar>
0035 #include <QDebug>
0036 #include <QBoxLayout>
0037 #include <QApplication>
0038 #include <QtGlobal>
0039 #include <QTimer>
0040 
0041 #include "FloatingDockContainer.h"
0042 #include "DockAreaWidget.h"
0043 #include "DockOverlay.h"
0044 #include "DockManager.h"
0045 #include "DockWidget.h"
0046 #include "DockWidgetTab.h"
0047 
0048 #include <iostream>
0049 
0050 
0051 namespace ads
0052 {
0053 /**
0054  * Private data class of CDockAreaTabBar class (pimpl)
0055  */
0056 struct DockAreaTabBarPrivate
0057 {
0058     CDockAreaTabBar* _this;
0059     CDockAreaWidget* DockArea;
0060     QWidget* TabsContainerWidget;
0061     QBoxLayout* TabsLayout;
0062     int CurrentIndex = -1;
0063 
0064     /**
0065      * Private data constructor
0066      */
0067     DockAreaTabBarPrivate(CDockAreaTabBar* _public);
0068 
0069     /**
0070      * Update tabs after current index changed or when tabs are removed.
0071      * The function reassigns the stylesheet to update the tabs
0072      */
0073     void updateTabs();
0074 
0075     /**
0076      * Convenience function to access first tab
0077      */
0078     CDockWidgetTab* firstTab() const {return _this->tab(0);}
0079 
0080     /**
0081      * Convenience function to access last tab
0082      */
0083     CDockWidgetTab* lastTab() const {return _this->tab(_this->count() - 1);}
0084 };
0085 // struct DockAreaTabBarPrivate
0086 
0087 //============================================================================
0088 DockAreaTabBarPrivate::DockAreaTabBarPrivate(CDockAreaTabBar* _public) :
0089     _this(_public)
0090 {
0091 
0092 }
0093 
0094 
0095 //============================================================================
0096 void DockAreaTabBarPrivate::updateTabs()
0097 {
0098     // Set active TAB and update all other tabs to be inactive
0099     for (int i = 0; i < _this->count(); ++i)
0100     {
0101         auto TabWidget = _this->tab(i);
0102         if (!TabWidget)
0103         {
0104             continue;
0105         }
0106 
0107         if (i == CurrentIndex)
0108         {
0109             TabWidget->show();
0110             TabWidget->setActiveTab(true);
0111             // Sometimes the synchronous calculation of the rectangular area fails
0112             // Therefore we use QTimer::singleShot here to execute the call
0113             // within the event loop - see #520
0114             QTimer::singleShot(0, [&, TabWidget]{
0115                 _this->ensureWidgetVisible(TabWidget);
0116             });
0117         }
0118         else
0119         {
0120             TabWidget->setActiveTab(false);
0121         }
0122     }
0123 }
0124 
0125 
0126 //============================================================================
0127 CDockAreaTabBar::CDockAreaTabBar(CDockAreaWidget* parent) :
0128     QScrollArea(parent),
0129     d(new DockAreaTabBarPrivate(this))
0130 {
0131     d->DockArea = parent;
0132     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0133     setFrameStyle(QFrame::NoFrame);
0134     setWidgetResizable(true);
0135     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0136     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0137 
0138     d->TabsContainerWidget = new QWidget();
0139     d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0140     d->TabsContainerWidget->setObjectName("tabsContainerWidget");
0141     d->TabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
0142     d->TabsLayout->setContentsMargins(0, 0, 0, 0);
0143     d->TabsLayout->setSpacing(0);
0144     d->TabsLayout->addStretch(1);
0145     d->TabsContainerWidget->setLayout(d->TabsLayout);
0146     setWidget(d->TabsContainerWidget);
0147 
0148     setFocusPolicy(Qt::NoFocus);
0149 }
0150 
0151 
0152 //============================================================================
0153 CDockAreaTabBar::~CDockAreaTabBar()
0154 {
0155     delete d;
0156 }
0157 
0158 
0159 //============================================================================
0160 void CDockAreaTabBar::wheelEvent(QWheelEvent* Event)
0161 {
0162     Event->accept();
0163     const int direction = Event->angleDelta().y();
0164     if (direction < 0)
0165     {
0166         horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
0167     }
0168     else
0169     {
0170         horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
0171     }
0172 }
0173 
0174 
0175 //============================================================================
0176 void CDockAreaTabBar::setCurrentIndex(int index)
0177 {
0178     if (index == d->CurrentIndex)
0179     {
0180         return;
0181     }
0182 
0183     if (index < -1 || index > (count() - 1))
0184     {
0185         qWarning() << Q_FUNC_INFO << "Invalid index" << index;
0186         return;
0187     }
0188 
0189     Q_EMIT currentChanging(index);
0190     d->CurrentIndex = index;
0191     d->updateTabs();
0192     updateGeometry();
0193     Q_EMIT currentChanged(index);
0194 }
0195 
0196 
0197 //============================================================================
0198 int CDockAreaTabBar::count() const
0199 {
0200     // The tab bar contains a stretch item as last item
0201     return d->TabsLayout->count() - 1;
0202 }
0203 
0204 
0205 //===========================================================================
0206 void CDockAreaTabBar::insertTab(int Index, CDockWidgetTab* Tab)
0207 {
0208     d->TabsLayout->insertWidget(Index, Tab);
0209     connect(Tab, SIGNAL(clicked()), this, SLOT(onTabClicked()));
0210     connect(Tab, SIGNAL(closeRequested()), this, SLOT(onTabCloseRequested()));
0211     connect(Tab, SIGNAL(closeOtherTabsRequested()), this, SLOT(onCloseOtherTabsRequested()));
0212     connect(Tab, SIGNAL(moved(QPoint)), this, SLOT(onTabWidgetMoved(QPoint)));
0213     connect(Tab, SIGNAL(elidedChanged(bool)), this, SIGNAL(elidedChanged(bool)));
0214     Tab->installEventFilter(this);
0215     Q_EMIT tabInserted(Index);
0216     if (Index <= d->CurrentIndex)
0217     {
0218         setCurrentIndex(d->CurrentIndex + 1);
0219     }
0220     else if (d->CurrentIndex == -1)
0221     {
0222         setCurrentIndex(Index);
0223     }
0224 
0225     updateGeometry();
0226 }
0227 
0228 
0229 //===========================================================================
0230 void CDockAreaTabBar::removeTab(CDockWidgetTab* Tab)
0231 {
0232     if (!count())
0233     {
0234         return;
0235     }
0236     ADS_PRINT("CDockAreaTabBar::removeTab ");
0237     int NewCurrentIndex = currentIndex();
0238     int RemoveIndex = d->TabsLayout->indexOf(Tab);
0239     if (count() == 1)
0240     {
0241         NewCurrentIndex = -1;
0242     }
0243     if (NewCurrentIndex > RemoveIndex)
0244     {
0245         NewCurrentIndex--;
0246     }
0247     else if (NewCurrentIndex == RemoveIndex)
0248     {
0249         NewCurrentIndex = -1;
0250         // First we walk to the right to search for the next visible tab
0251         for (int i = (RemoveIndex + 1); i < count(); ++i)
0252         {
0253             if (tab(i)->isVisibleTo(this))
0254             {
0255                 NewCurrentIndex = i - 1;
0256                 break;
0257             }
0258         }
0259 
0260         // If there is no visible tab right to this tab then we walk to
0261         // the left to find a visible tab
0262         if (NewCurrentIndex < 0)
0263         {
0264             for (int i = (RemoveIndex - 1); i >= 0; --i)
0265             {
0266                 if (tab(i)->isVisibleTo(this))
0267                 {
0268                     NewCurrentIndex = i;
0269                     break;
0270                 }
0271             }
0272         }
0273     }
0274 
0275     Q_EMIT removingTab(RemoveIndex);
0276     d->TabsLayout->removeWidget(Tab);
0277     Tab->disconnect(this);
0278     Tab->removeEventFilter(this);
0279     ADS_PRINT("NewCurrentIndex " << NewCurrentIndex);
0280     if (NewCurrentIndex != d->CurrentIndex)
0281     {
0282         setCurrentIndex(NewCurrentIndex);
0283     }
0284     else
0285     {
0286         d->updateTabs();
0287     }
0288 
0289     updateGeometry();
0290 }
0291 
0292 
0293 //===========================================================================
0294 int CDockAreaTabBar::currentIndex() const
0295 {
0296     return d->CurrentIndex;
0297 }
0298 
0299 
0300 //===========================================================================
0301 CDockWidgetTab* CDockAreaTabBar::currentTab() const
0302 {
0303     if (d->CurrentIndex < 0 || d->CurrentIndex >= d->TabsLayout->count())
0304     {
0305         return nullptr;
0306     }
0307     else
0308     {
0309         return qobject_cast<CDockWidgetTab*>(d->TabsLayout->itemAt(d->CurrentIndex)->widget());
0310     }
0311 }
0312 
0313 
0314 //===========================================================================
0315 void CDockAreaTabBar::onTabClicked()
0316 {
0317     CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(sender());
0318     if (!Tab)
0319     {
0320         return;
0321     }
0322 
0323     int index = d->TabsLayout->indexOf(Tab);
0324     if (index < 0)
0325     {
0326         return;
0327     }
0328     setCurrentIndex(index);
0329     Q_EMIT tabBarClicked(index);
0330 }
0331 
0332 
0333 //===========================================================================
0334 void CDockAreaTabBar::onTabCloseRequested()
0335 {
0336     CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(sender());
0337     int Index = d->TabsLayout->indexOf(Tab);
0338     closeTab(Index);
0339 }
0340 
0341 
0342 //===========================================================================
0343 void CDockAreaTabBar::onCloseOtherTabsRequested()
0344 {
0345     auto Sender = qobject_cast<CDockWidgetTab*>(sender());
0346     for (int i = 0; i < count(); ++i)
0347     {
0348         auto Tab = tab(i);
0349         if (Tab->isClosable() && !Tab->isHidden() && Tab != Sender)
0350         {
0351             // If the dock widget is deleted with the closeTab() call, its tab
0352             // it will no longer be in the layout, and thus the index needs to
0353             // be updated to not skip any tabs
0354             int Offset = Tab->dockWidget()->features().testFlag(
0355                 CDockWidget::DockWidgetDeleteOnClose) ? 1 : 0;
0356             closeTab(i);
0357 
0358             // If the the dock widget blocks closing, i.e. if the flag
0359             // CustomCloseHandling is set, and the dock widget is still open,
0360             // then we do not need to correct the index
0361             if (Tab->dockWidget()->isClosed())
0362             {
0363                 i -= Offset;
0364             }
0365         }
0366     }
0367 }
0368 
0369 
0370 //===========================================================================
0371 CDockWidgetTab* CDockAreaTabBar::tab(int Index) const
0372 {
0373     if (Index >= count() || Index < 0)
0374     {
0375         return nullptr;
0376     }
0377     return qobject_cast<CDockWidgetTab*>(d->TabsLayout->itemAt(Index)->widget());
0378 }
0379 
0380 
0381 //===========================================================================
0382 void CDockAreaTabBar::onTabWidgetMoved(const QPoint& GlobalPos)
0383 {
0384     CDockWidgetTab* MovingTab = qobject_cast<CDockWidgetTab*>(sender());
0385     if (!MovingTab)
0386     {
0387         return;
0388     }
0389 
0390     int fromIndex = d->TabsLayout->indexOf(MovingTab);
0391     auto MousePos = mapFromGlobal(GlobalPos);
0392     MousePos.rx() = qMax(d->firstTab()->geometry().left(), MousePos.x());
0393     MousePos.rx() = qMin(d->lastTab()->geometry().right(), MousePos.x());
0394     int toIndex = -1;
0395     // Find tab under mouse
0396     for (int i = 0; i < count(); ++i)
0397     {
0398         CDockWidgetTab* DropTab = tab(i);
0399         if (DropTab == MovingTab || !DropTab->isVisibleTo(this)
0400             || !DropTab->geometry().contains(MousePos))
0401         {
0402             continue;
0403         }
0404 
0405         toIndex = d->TabsLayout->indexOf(DropTab);
0406         if (toIndex == fromIndex)
0407         {
0408             toIndex = -1;
0409         }
0410         break;
0411     }
0412 
0413     if (toIndex > -1)
0414     {
0415         d->TabsLayout->removeWidget(MovingTab);
0416         d->TabsLayout->insertWidget(toIndex, MovingTab);
0417         ADS_PRINT("tabMoved from " << fromIndex << " to " << toIndex);
0418         Q_EMIT tabMoved(fromIndex, toIndex);
0419         setCurrentIndex(toIndex);
0420     }
0421     else
0422     {
0423         // Ensure that the moved tab is reset to its start position
0424         d->TabsLayout->update();
0425     }
0426 }
0427 
0428 //===========================================================================
0429 void CDockAreaTabBar::closeTab(int Index)
0430 {
0431     if (Index < 0 || Index >= count())
0432     {
0433         return;
0434     }
0435 
0436     auto Tab = tab(Index);
0437     if (Tab->isHidden())
0438     {
0439         return;
0440     }
0441     Q_EMIT tabCloseRequested(Index);
0442 }
0443 
0444 
0445 //===========================================================================
0446 bool CDockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
0447 {
0448     bool Result = Super::eventFilter(watched, event);
0449     CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(watched);
0450     if (!Tab)
0451     {
0452         return Result;
0453     }
0454 
0455     switch (event->type())
0456     {
0457     case QEvent::Hide:
0458          Q_EMIT tabClosed(d->TabsLayout->indexOf(Tab));
0459          updateGeometry();
0460          break;
0461 
0462     case QEvent::Show:
0463          Q_EMIT tabOpened(d->TabsLayout->indexOf(Tab));
0464          updateGeometry();
0465          break;
0466 
0467     // Setting the text of a tab will cause a LayoutRequest event
0468     case QEvent::LayoutRequest:
0469          updateGeometry();
0470          break;
0471 
0472     default:
0473         break;
0474     }
0475 
0476     return Result;
0477 }
0478 
0479 
0480 //===========================================================================
0481 bool CDockAreaTabBar::isTabOpen(int Index) const
0482 {
0483     if (Index < 0 || Index >= count())
0484     {
0485         return false;
0486     }
0487 
0488     return !tab(Index)->isHidden();
0489 }
0490 
0491 
0492 //===========================================================================
0493 QSize CDockAreaTabBar::minimumSizeHint() const
0494 {
0495     QSize Size = sizeHint();
0496     Size.setWidth(10);
0497     return Size;
0498 }
0499 
0500 
0501 //===========================================================================
0502 QSize CDockAreaTabBar::sizeHint() const
0503 {
0504     return d->TabsContainerWidget->sizeHint();
0505 }
0506 
0507 
0508 //===========================================================================
0509 int CDockAreaTabBar::tabAt(const QPoint& Pos) const
0510 {
0511     if (!isVisible())
0512     {
0513         return TabInvalidIndex;
0514     }
0515 
0516     if (Pos.x() < tab(0)->geometry().x())
0517     {
0518         return -1;
0519     }
0520 
0521     for (int i = 0; i < count(); ++i)
0522     {
0523         if (tab(i)->geometry().contains(Pos))
0524         {
0525             return i;
0526         }
0527     }
0528 
0529     return count();
0530 }
0531 
0532 
0533 //===========================================================================
0534 int CDockAreaTabBar::tabInsertIndexAt(const QPoint& Pos) const
0535 {
0536     int Index = tabAt(Pos);
0537     if (Index == TabInvalidIndex)
0538     {
0539         return TabDefaultInsertIndex;
0540     }
0541     else
0542     {
0543         return (Index < 0) ? 0 : Index;
0544     }
0545 }
0546 
0547 } // namespace ads
0548 
0549 
0550 //---------------------------------------------------------------------------
0551 // EOF DockAreaTabBar.cpp