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