dockareatabbar.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2020 Uwe Kindler
  4. ** Contact: https://www.qt.io/licensing/
  5. **
  6. ** This file is part of Qt Creator.
  7. **
  8. ** Commercial License Usage
  9. ** Licensees holding valid commercial Qt licenses may use this file in
  10. ** accordance with the commercial license agreement provided with the
  11. ** Software or, alternatively, in accordance with the terms contained in
  12. ** a written agreement between you and The Qt Company. For licensing terms
  13. ** and conditions see https://www.qt.io/terms-conditions. For further
  14. ** information use the contact form at https://www.qt.io/contact-us.
  15. **
  16. ** GNU Lesser General Public License Usage
  17. ** Alternatively, this file may be used under the terms of the GNU Lesser
  18. ** General Public License version 2.1 or (at your option) any later version.
  19. ** The licenses are as published by the Free Software Foundation
  20. ** and appearing in the file LICENSE.LGPLv21 included in the packaging
  21. ** of this file. Please review the following information to ensure
  22. ** the GNU Lesser General Public License version 2.1 requirements
  23. ** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  24. **
  25. ** GNU General Public License Usage
  26. ** Alternatively, this file may be used under the terms of the GNU
  27. ** General Public License version 3 or (at your option) any later version
  28. ** approved by the KDE Free Qt Foundation. The licenses are as published by
  29. ** the Free Software Foundation and appearing in the file LICENSE.GPL3
  30. ** included in the packaging of this file. Please review the following
  31. ** information to ensure the GNU General Public License requirements will
  32. ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
  33. **
  34. ****************************************************************************/
  35. #include "dockareatabbar.h"
  36. #include "dockareawidget.h"
  37. #include "dockmanager.h"
  38. #include "dockoverlay.h"
  39. #include "dockwidget.h"
  40. #include "dockwidgettab.h"
  41. #include "floatingdockcontainer.h"
  42. #include "floatingdragpreview.h"
  43. #include <QApplication>
  44. #include <QBoxLayout>
  45. #include <QLoggingCategory>
  46. #include <QMouseEvent>
  47. #include <QScrollBar>
  48. #include <QtGlobal>
  49. #include <iostream>
  50. static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg)
  51. namespace ADS
  52. {
  53. /**
  54. * Private data class of DockAreaTabBar class (pimpl)
  55. */
  56. struct DockAreaTabBarPrivate
  57. {
  58. DockAreaTabBar *q;
  59. DockAreaWidget *m_dockArea;
  60. QWidget *m_tabsContainerWidget;
  61. QBoxLayout *m_tabsLayout;
  62. int m_currentIndex = -1;
  63. /**
  64. * Private data constructor
  65. */
  66. DockAreaTabBarPrivate(DockAreaTabBar *parent);
  67. /**
  68. * Update tabs after current index changed or when tabs are removed.
  69. * The function reassigns the stylesheet to update the tabs
  70. */
  71. void updateTabs();
  72. /**
  73. * Convenience function to access first tab
  74. */
  75. DockWidgetTab *firstTab() const {return q->tab(0);}
  76. /**
  77. * Convenience function to access last tab
  78. */
  79. DockWidgetTab *lastTab() const {return q->tab(q->count() - 1);}
  80. };
  81. // struct DockAreaTabBarPrivate
  82. DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent)
  83. : q(parent)
  84. {}
  85. void DockAreaTabBarPrivate::updateTabs()
  86. {
  87. // Set active TAB and update all other tabs to be inactive
  88. for (int i = 0; i < q->count(); ++i) {
  89. auto tabWidget = q->tab(i);
  90. if (!tabWidget)
  91. continue;
  92. if (i == m_currentIndex) {
  93. tabWidget->show();
  94. tabWidget->setActiveTab(true);
  95. q->ensureWidgetVisible(tabWidget);
  96. } else {
  97. tabWidget->setActiveTab(false);
  98. }
  99. }
  100. }
  101. DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent)
  102. : QScrollArea(parent)
  103. , d(new DockAreaTabBarPrivate(this))
  104. {
  105. d->m_dockArea = parent;
  106. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  107. setFrameStyle(QFrame::NoFrame);
  108. setWidgetResizable(true);
  109. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  110. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  111. d->m_tabsContainerWidget = new QWidget();
  112. d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  113. d->m_tabsContainerWidget->setObjectName("tabsContainerWidget");
  114. d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
  115. d->m_tabsLayout->setContentsMargins(0, 0, 0, 0);
  116. d->m_tabsLayout->setSpacing(0);
  117. d->m_tabsLayout->addStretch(1);
  118. d->m_tabsContainerWidget->setLayout(d->m_tabsLayout);
  119. setWidget(d->m_tabsContainerWidget);
  120. }
  121. DockAreaTabBar::~DockAreaTabBar() { delete d; }
  122. void DockAreaTabBar::wheelEvent(QWheelEvent *event)
  123. {
  124. event->accept();
  125. const int direction = event->angleDelta().y();
  126. if (direction < 0) {
  127. horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
  128. } else {
  129. horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
  130. }
  131. }
  132. void DockAreaTabBar::setCurrentIndex(int index)
  133. {
  134. if (index == d->m_currentIndex)
  135. return;
  136. if (index < -1 || index > (count() - 1)) {
  137. qWarning() << Q_FUNC_INFO << "Invalid index" << index;
  138. return;
  139. }
  140. emit currentChanging(index);
  141. d->m_currentIndex = index;
  142. d->updateTabs();
  143. updateGeometry();
  144. emit currentChanged(index);
  145. }
  146. int DockAreaTabBar::count() const
  147. {
  148. // The tab bar contains a stretch item as last item
  149. return d->m_tabsLayout->count() - 1;
  150. }
  151. void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab)
  152. {
  153. d->m_tabsLayout->insertWidget(index, dockWidgetTab);
  154. connect(dockWidgetTab, &DockWidgetTab::clicked, this, &DockAreaTabBar::onTabClicked);
  155. connect(dockWidgetTab,
  156. &DockWidgetTab::closeRequested,
  157. this,
  158. &DockAreaTabBar::onTabCloseRequested);
  159. connect(dockWidgetTab,
  160. &DockWidgetTab::closeOtherTabsRequested,
  161. this,
  162. &DockAreaTabBar::onCloseOtherTabsRequested);
  163. connect(dockWidgetTab, &DockWidgetTab::moved, this, &DockAreaTabBar::onTabWidgetMoved);
  164. connect(dockWidgetTab,
  165. &DockWidgetTab::elidedChanged,
  166. this,
  167. &DockAreaTabBar::elidedChanged);
  168. dockWidgetTab->installEventFilter(this);
  169. emit tabInserted(index);
  170. if (index <= d->m_currentIndex || d->m_currentIndex == -1) {
  171. setCurrentIndex(d->m_currentIndex + 1);
  172. }
  173. updateGeometry();
  174. }
  175. void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab)
  176. {
  177. if (!count())
  178. return;
  179. qCInfo(adsLog) << Q_FUNC_INFO;
  180. int newCurrentIndex = currentIndex();
  181. int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab);
  182. if (count() == 1)
  183. newCurrentIndex = -1;
  184. if (newCurrentIndex > removeIndex) {
  185. newCurrentIndex--;
  186. } else if (newCurrentIndex == removeIndex) {
  187. newCurrentIndex = -1;
  188. // First we walk to the right to search for the next visible tab
  189. for (int i = (removeIndex + 1); i < count(); ++i) {
  190. if (tab(i)->isVisibleTo(this)) {
  191. newCurrentIndex = i - 1;
  192. break;
  193. }
  194. }
  195. // If there is no visible tab right to this tab then we walk to
  196. // the left to find a visible tab
  197. if (newCurrentIndex < 0) {
  198. for (int i = (removeIndex - 1); i >= 0; --i) {
  199. if (tab(i)->isVisibleTo(this)) {
  200. newCurrentIndex = i;
  201. break;
  202. }
  203. }
  204. }
  205. }
  206. emit removingTab(removeIndex);
  207. d->m_tabsLayout->removeWidget(dockWidgetTab);
  208. dockWidgetTab->disconnect(this);
  209. dockWidgetTab->removeEventFilter(this);
  210. qCInfo(adsLog) << "NewCurrentIndex " << newCurrentIndex;
  211. if (newCurrentIndex != d->m_currentIndex) {
  212. setCurrentIndex(newCurrentIndex);
  213. } else {
  214. d->updateTabs();
  215. }
  216. updateGeometry();
  217. }
  218. int DockAreaTabBar::currentIndex() const { return d->m_currentIndex; }
  219. DockWidgetTab *DockAreaTabBar::currentTab() const
  220. {
  221. if (d->m_currentIndex < 0) {
  222. return nullptr;
  223. } else {
  224. return qobject_cast<DockWidgetTab *>(
  225. d->m_tabsLayout->itemAt(d->m_currentIndex)->widget());
  226. }
  227. }
  228. void DockAreaTabBar::onTabClicked()
  229. {
  230. DockWidgetTab *tab = qobject_cast<DockWidgetTab *>(sender());
  231. if (!tab)
  232. return;
  233. int index = d->m_tabsLayout->indexOf(tab);
  234. if (index < 0)
  235. return;
  236. setCurrentIndex(index);
  237. emit tabBarClicked(index);
  238. }
  239. void DockAreaTabBar::onTabCloseRequested()
  240. {
  241. DockWidgetTab *tab = qobject_cast<DockWidgetTab *>(sender());
  242. int index = d->m_tabsLayout->indexOf(tab);
  243. closeTab(index);
  244. }
  245. void DockAreaTabBar::onCloseOtherTabsRequested()
  246. {
  247. auto senderTab = qobject_cast<DockWidgetTab *>(sender());
  248. for (int i = 0; i < count(); ++i) {
  249. auto currentTab = tab(i);
  250. if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != senderTab) {
  251. // If the dock widget is deleted with the closeTab() call, its tab it will no longer
  252. // be in the layout, and thus the index needs to be updated to not skip any tabs
  253. int offset = currentTab->dockWidget()->features().testFlag(
  254. DockWidget::DockWidgetDeleteOnClose)
  255. ? 1
  256. : 0;
  257. closeTab(i);
  258. // If the the dock widget blocks closing, i.e. if the flag
  259. // CustomCloseHandling is set, and the dock widget is still open,
  260. // then we do not need to correct the index
  261. if (currentTab->dockWidget()->isClosed()) {
  262. i -= offset;
  263. }
  264. }
  265. }
  266. }
  267. DockWidgetTab *DockAreaTabBar::tab(int index) const
  268. {
  269. if (index >= count() || index < 0)
  270. return nullptr;
  271. return qobject_cast<DockWidgetTab *>(d->m_tabsLayout->itemAt(index)->widget());
  272. }
  273. void DockAreaTabBar::onTabWidgetMoved(const QPoint &globalPosition)
  274. {
  275. DockWidgetTab *movingTab = qobject_cast<DockWidgetTab *>(sender());
  276. if (!movingTab)
  277. return;
  278. int fromIndex = d->m_tabsLayout->indexOf(movingTab);
  279. auto mousePos = mapFromGlobal(globalPosition);
  280. mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x());
  281. mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x());
  282. int toIndex = -1;
  283. // Find tab under mouse
  284. for (int i = 0; i < count(); ++i) {
  285. DockWidgetTab *dropTab = tab(i);
  286. if (dropTab == movingTab || !dropTab->isVisibleTo(this)
  287. || !dropTab->geometry().contains(mousePos))
  288. continue;
  289. toIndex = d->m_tabsLayout->indexOf(dropTab);
  290. if (toIndex == fromIndex)
  291. toIndex = -1;
  292. break;
  293. }
  294. if (toIndex > -1) {
  295. d->m_tabsLayout->removeWidget(movingTab);
  296. d->m_tabsLayout->insertWidget(toIndex, movingTab);
  297. qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex;
  298. emit tabMoved(fromIndex, toIndex);
  299. setCurrentIndex(toIndex);
  300. } else {
  301. // Ensure that the moved tab is reset to its start position
  302. d->m_tabsLayout->update();
  303. }
  304. }
  305. void DockAreaTabBar::closeTab(int index)
  306. {
  307. if (index < 0 || index >= count())
  308. return;
  309. auto dockWidgetTab = tab(index);
  310. if (dockWidgetTab->isHidden())
  311. return;
  312. emit tabCloseRequested(index);
  313. }
  314. bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
  315. {
  316. bool result = Super::eventFilter(watched, event);
  317. DockWidgetTab *dockWidgetTab = qobject_cast<DockWidgetTab *>(watched);
  318. if (!dockWidgetTab)
  319. return result;
  320. switch (event->type()) {
  321. case QEvent::Hide:
  322. emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab));
  323. updateGeometry();
  324. break;
  325. case QEvent::Show:
  326. emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab));
  327. updateGeometry();
  328. break;
  329. default:
  330. break;
  331. }
  332. return result;
  333. }
  334. bool DockAreaTabBar::isTabOpen(int index) const
  335. {
  336. if (index < 0 || index >= count())
  337. return false;
  338. return !tab(index)->isHidden();
  339. }
  340. QSize DockAreaTabBar::minimumSizeHint() const
  341. {
  342. QSize size = sizeHint();
  343. size.setWidth(10);
  344. return size;
  345. }
  346. QSize DockAreaTabBar::sizeHint() const
  347. {
  348. return d->m_tabsContainerWidget->sizeHint();
  349. }
  350. } // namespace ADS