DockAreaTabBar.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*******************************************************************************
  2. ** Qt Advanced Docking System
  3. ** Copyright (C) 2017 Uwe Kindler
  4. **
  5. ** This library is free software; you can redistribute it and/or
  6. ** modify it under the terms of the GNU Lesser General Public
  7. ** License as published by the Free Software Foundation; either
  8. ** version 2.1 of the License, or (at your option) any later version.
  9. **
  10. ** This library is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. ** Lesser General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU Lesser General Public
  16. ** License along with this library; If not, see <http://www.gnu.org/licenses/>.
  17. ******************************************************************************/
  18. //============================================================================
  19. /// \file DockAreaTabBar.cpp
  20. /// \author Uwe Kindler
  21. /// \date 24.08.2018
  22. /// \brief Implementation of CDockAreaTabBar class
  23. //============================================================================
  24. //============================================================================
  25. // INCLUDES
  26. //============================================================================
  27. #include "FloatingDragPreview.h"
  28. #include "DockAreaTabBar.h"
  29. #include <QMouseEvent>
  30. #include <QScrollBar>
  31. #include <QDebug>
  32. #include <QBoxLayout>
  33. #include <QApplication>
  34. #include <QtGlobal>
  35. #include <QTimer>
  36. #include "FloatingDockContainer.h"
  37. #include "DockAreaWidget.h"
  38. #include "DockOverlay.h"
  39. #include "DockManager.h"
  40. #include "DockWidget.h"
  41. #include "DockWidgetTab.h"
  42. #include <iostream>
  43. namespace ads
  44. {
  45. /**
  46. * Private data class of CDockAreaTabBar class (pimpl)
  47. */
  48. struct DockAreaTabBarPrivate
  49. {
  50. CDockAreaTabBar* _this;
  51. CDockAreaWidget* DockArea;
  52. QWidget* TabsContainerWidget;
  53. QBoxLayout* TabsLayout;
  54. int CurrentIndex = -1;
  55. /**
  56. * Private data constructor
  57. */
  58. DockAreaTabBarPrivate(CDockAreaTabBar* _public);
  59. /**
  60. * Update tabs after current index changed or when tabs are removed.
  61. * The function reassigns the stylesheet to update the tabs
  62. */
  63. void updateTabs();
  64. /**
  65. * Convenience function to access first tab
  66. */
  67. CDockWidgetTab* firstTab() const {return _this->tab(0);}
  68. /**
  69. * Convenience function to access last tab
  70. */
  71. CDockWidgetTab* lastTab() const {return _this->tab(_this->count() - 1);}
  72. };
  73. // struct DockAreaTabBarPrivate
  74. //============================================================================
  75. DockAreaTabBarPrivate::DockAreaTabBarPrivate(CDockAreaTabBar* _public) :
  76. _this(_public)
  77. {
  78. }
  79. //============================================================================
  80. void DockAreaTabBarPrivate::updateTabs()
  81. {
  82. // Set active TAB and update all other tabs to be inactive
  83. for (int i = 0; i < _this->count(); ++i)
  84. {
  85. auto TabWidget = _this->tab(i);
  86. if (!TabWidget)
  87. {
  88. continue;
  89. }
  90. if (i == CurrentIndex)
  91. {
  92. TabWidget->show();
  93. TabWidget->setActiveTab(true);
  94. // Sometimes the synchronous calculation of the rectangular area fails
  95. // Therefore we use QTimer::singleShot here to execute the call
  96. // within the event loop - see #520
  97. QTimer::singleShot(0, _this, [&, TabWidget]
  98. {
  99. _this->ensureWidgetVisible(TabWidget);
  100. });
  101. }
  102. else
  103. {
  104. TabWidget->setActiveTab(false);
  105. }
  106. }
  107. }
  108. //============================================================================
  109. CDockAreaTabBar::CDockAreaTabBar(CDockAreaWidget* parent) :
  110. QScrollArea(parent),
  111. d(new DockAreaTabBarPrivate(this))
  112. {
  113. d->DockArea = parent;
  114. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  115. setFrameStyle(QFrame::NoFrame);
  116. setWidgetResizable(true);
  117. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  118. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  119. d->TabsContainerWidget = new QWidget();
  120. d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  121. d->TabsContainerWidget->setObjectName("tabsContainerWidget");
  122. d->TabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
  123. d->TabsLayout->setContentsMargins(0, 0, 0, 0);
  124. d->TabsLayout->setSpacing(0);
  125. d->TabsLayout->addStretch(1);
  126. d->TabsContainerWidget->setLayout(d->TabsLayout);
  127. setWidget(d->TabsContainerWidget);
  128. setFocusPolicy(Qt::NoFocus);
  129. }
  130. //============================================================================
  131. CDockAreaTabBar::~CDockAreaTabBar()
  132. {
  133. delete d;
  134. }
  135. //============================================================================
  136. void CDockAreaTabBar::wheelEvent(QWheelEvent* Event)
  137. {
  138. QCoreApplication::sendEvent(horizontalScrollBar(), Event);
  139. }
  140. //============================================================================
  141. void CDockAreaTabBar::setCurrentIndex(int index)
  142. {
  143. if (index == d->CurrentIndex)
  144. {
  145. return;
  146. }
  147. if (index < -1 || index > (count() - 1))
  148. {
  149. qWarning() << Q_FUNC_INFO << "Invalid index" << index;
  150. return;
  151. }
  152. Q_EMIT currentChanging(index);
  153. d->CurrentIndex = index;
  154. d->updateTabs();
  155. updateGeometry();
  156. Q_EMIT currentChanged(index);
  157. }
  158. //============================================================================
  159. int CDockAreaTabBar::count() const
  160. {
  161. // The tab bar contains a stretch item as last item
  162. return d->TabsLayout->count() - 1;
  163. }
  164. //===========================================================================
  165. void CDockAreaTabBar::insertTab(int Index, CDockWidgetTab* Tab)
  166. {
  167. d->TabsLayout->insertWidget(Index, Tab);
  168. connect(Tab, SIGNAL(clicked()), this, SLOT(onTabClicked()));
  169. connect(Tab, SIGNAL(closeRequested()), this, SLOT(onTabCloseRequested()));
  170. connect(Tab, SIGNAL(closeOtherTabsRequested()), this, SLOT(onCloseOtherTabsRequested()));
  171. connect(Tab, SIGNAL(moved(QPoint)), this, SLOT(onTabWidgetMoved(QPoint)));
  172. connect(Tab, SIGNAL(elidedChanged(bool)), this, SIGNAL(elidedChanged(bool)));
  173. Tab->installEventFilter(this);
  174. Q_EMIT tabInserted(Index);
  175. if (Index <= d->CurrentIndex)
  176. {
  177. setCurrentIndex(d->CurrentIndex + 1);
  178. }
  179. else if (d->CurrentIndex == -1)
  180. {
  181. setCurrentIndex(Index);
  182. }
  183. updateGeometry();
  184. }
  185. //===========================================================================
  186. void CDockAreaTabBar::removeTab(CDockWidgetTab* Tab)
  187. {
  188. if (!count())
  189. {
  190. return;
  191. }
  192. ADS_PRINT("CDockAreaTabBar::removeTab ");
  193. int NewCurrentIndex = currentIndex();
  194. int RemoveIndex = d->TabsLayout->indexOf(Tab);
  195. if (count() == 1)
  196. {
  197. NewCurrentIndex = -1;
  198. }
  199. if (NewCurrentIndex > RemoveIndex)
  200. {
  201. NewCurrentIndex--;
  202. }
  203. else if (NewCurrentIndex == RemoveIndex)
  204. {
  205. NewCurrentIndex = -1;
  206. // First we walk to the right to search for the next visible tab
  207. for (int i = (RemoveIndex + 1); i < count(); ++i)
  208. {
  209. if (tab(i)->isVisibleTo(this))
  210. {
  211. NewCurrentIndex = i - 1;
  212. break;
  213. }
  214. }
  215. // If there is no visible tab right to this tab then we walk to
  216. // the left to find a visible tab
  217. if (NewCurrentIndex < 0)
  218. {
  219. for (int i = (RemoveIndex - 1); i >= 0; --i)
  220. {
  221. if (tab(i)->isVisibleTo(this))
  222. {
  223. NewCurrentIndex = i;
  224. break;
  225. }
  226. }
  227. }
  228. }
  229. Q_EMIT removingTab(RemoveIndex);
  230. d->TabsLayout->removeWidget(Tab);
  231. Tab->disconnect(this);
  232. Tab->removeEventFilter(this);
  233. ADS_PRINT("NewCurrentIndex " << NewCurrentIndex);
  234. if (NewCurrentIndex != d->CurrentIndex)
  235. {
  236. setCurrentIndex(NewCurrentIndex);
  237. }
  238. else
  239. {
  240. d->updateTabs();
  241. }
  242. updateGeometry();
  243. }
  244. //===========================================================================
  245. int CDockAreaTabBar::currentIndex() const
  246. {
  247. return d->CurrentIndex;
  248. }
  249. //===========================================================================
  250. CDockWidgetTab* CDockAreaTabBar::currentTab() const
  251. {
  252. if (d->CurrentIndex < 0 || d->CurrentIndex >= d->TabsLayout->count())
  253. {
  254. return nullptr;
  255. }
  256. else
  257. {
  258. return qobject_cast<CDockWidgetTab*>(d->TabsLayout->itemAt(d->CurrentIndex)->widget());
  259. }
  260. }
  261. //===========================================================================
  262. void CDockAreaTabBar::onTabClicked()
  263. {
  264. CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(sender());
  265. if (!Tab)
  266. {
  267. return;
  268. }
  269. int index = d->TabsLayout->indexOf(Tab);
  270. if (index < 0)
  271. {
  272. return;
  273. }
  274. setCurrentIndex(index);
  275. Q_EMIT tabBarClicked(index);
  276. }
  277. //===========================================================================
  278. void CDockAreaTabBar::onTabCloseRequested()
  279. {
  280. CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(sender());
  281. int Index = d->TabsLayout->indexOf(Tab);
  282. closeTab(Index);
  283. }
  284. //===========================================================================
  285. void CDockAreaTabBar::onCloseOtherTabsRequested()
  286. {
  287. auto Sender = qobject_cast<CDockWidgetTab*>(sender());
  288. for (int i = count() - 1; i >= 0; --i) {
  289. auto Tab = tab(i);
  290. if (Tab->isClosable() && !Tab->isHidden() && Tab != Sender) {
  291. closeTab(i);
  292. }
  293. }
  294. }
  295. //===========================================================================
  296. CDockWidgetTab* CDockAreaTabBar::tab(int Index) const
  297. {
  298. if (Index >= count() || Index < 0)
  299. {
  300. return nullptr;
  301. }
  302. return qobject_cast<CDockWidgetTab*>(d->TabsLayout->itemAt(Index)->widget());
  303. }
  304. //===========================================================================
  305. void CDockAreaTabBar::onTabWidgetMoved(const QPoint& GlobalPos)
  306. {
  307. CDockWidgetTab* MovingTab = qobject_cast<CDockWidgetTab*>(sender());
  308. if (!MovingTab)
  309. {
  310. return;
  311. }
  312. int fromIndex = d->TabsLayout->indexOf(MovingTab);
  313. auto MousePos = mapFromGlobal(GlobalPos);
  314. MousePos.rx() = qMax(0, MousePos.x());
  315. MousePos.rx() = qMin(width(), MousePos.x());
  316. int toIndex = -1;
  317. // Find tab under mouse
  318. for (int i = 0; i < count(); ++i)
  319. {
  320. CDockWidgetTab* DropTab = tab(i);
  321. auto TabGeometry = DropTab->geometry();
  322. TabGeometry.setTopLeft(d->TabsContainerWidget->mapToParent(TabGeometry.topLeft()));
  323. TabGeometry.setBottomRight(d->TabsContainerWidget->mapToParent(TabGeometry.bottomRight()));
  324. if (DropTab == MovingTab || !DropTab->isVisibleTo(this)
  325. || !TabGeometry.contains(MousePos))
  326. {
  327. continue;
  328. }
  329. toIndex = d->TabsLayout->indexOf(DropTab);
  330. if (toIndex == fromIndex)
  331. {
  332. toIndex = -1;
  333. }
  334. break;
  335. }
  336. if (toIndex > -1)
  337. {
  338. d->TabsLayout->removeWidget(MovingTab);
  339. d->TabsLayout->insertWidget(toIndex, MovingTab);
  340. ADS_PRINT("tabMoved from " << fromIndex << " to " << toIndex);
  341. Q_EMIT tabMoved(fromIndex, toIndex);
  342. setCurrentIndex(toIndex);
  343. }
  344. else
  345. {
  346. // Ensure that the moved tab is reset to its start position
  347. d->TabsLayout->update();
  348. }
  349. }
  350. //===========================================================================
  351. void CDockAreaTabBar::closeTab(int Index)
  352. {
  353. if (Index < 0 || Index >= count())
  354. {
  355. return;
  356. }
  357. auto Tab = tab(Index);
  358. if (Tab->isHidden())
  359. {
  360. return;
  361. }
  362. Q_EMIT tabCloseRequested(Index);
  363. }
  364. //===========================================================================
  365. bool CDockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
  366. {
  367. bool Result = Super::eventFilter(watched, event);
  368. CDockWidgetTab* Tab = qobject_cast<CDockWidgetTab*>(watched);
  369. if (!Tab)
  370. {
  371. return Result;
  372. }
  373. switch (event->type())
  374. {
  375. case QEvent::Hide:
  376. Q_EMIT tabClosed(d->TabsLayout->indexOf(Tab));
  377. updateGeometry();
  378. break;
  379. case QEvent::Show:
  380. Q_EMIT tabOpened(d->TabsLayout->indexOf(Tab));
  381. updateGeometry();
  382. break;
  383. // Setting the text of a tab will cause a LayoutRequest event
  384. case QEvent::LayoutRequest:
  385. updateGeometry();
  386. break;
  387. // Manage wheel event
  388. case QEvent::Wheel:
  389. // Ignore wheel events if tab is currently dragged
  390. if (Tab->dragState() == DraggingInactive)
  391. {
  392. wheelEvent((QWheelEvent* )event);
  393. }
  394. break;
  395. default:
  396. break;
  397. }
  398. return Result;
  399. }
  400. //===========================================================================
  401. bool CDockAreaTabBar::isTabOpen(int Index) const
  402. {
  403. if (Index < 0 || Index >= count())
  404. {
  405. return false;
  406. }
  407. return !tab(Index)->isHidden();
  408. }
  409. //===========================================================================
  410. QSize CDockAreaTabBar::minimumSizeHint() const
  411. {
  412. QSize Size = sizeHint();
  413. Size.setWidth(10);
  414. return Size;
  415. }
  416. //===========================================================================
  417. QSize CDockAreaTabBar::sizeHint() const
  418. {
  419. return d->TabsContainerWidget->sizeHint();
  420. }
  421. //===========================================================================
  422. int CDockAreaTabBar::tabAt(const QPoint& Pos) const
  423. {
  424. if (!isVisible())
  425. {
  426. return TabInvalidIndex;
  427. }
  428. if (Pos.x() < tab(0)->geometry().x())
  429. {
  430. return -1;
  431. }
  432. for (int i = 0; i < count(); ++i)
  433. {
  434. if (tab(i)->geometry().contains(Pos))
  435. {
  436. return i;
  437. }
  438. }
  439. return count();
  440. }
  441. //===========================================================================
  442. int CDockAreaTabBar::tabInsertIndexAt(const QPoint& Pos) const
  443. {
  444. int Index = tabAt(Pos);
  445. if (Index == TabInvalidIndex)
  446. {
  447. return TabDefaultInsertIndex;
  448. }
  449. else
  450. {
  451. return (Index < 0) ? 0 : Index;
  452. }
  453. }
  454. //===========================================================================
  455. bool CDockAreaTabBar::areTabsOverflowing() const
  456. {
  457. return d->TabsContainerWidget->width() > width();
  458. }
  459. } // namespace ads
  460. //---------------------------------------------------------------------------
  461. // EOF DockAreaTabBar.cpp