dockareatitlebar.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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 "dockareatitlebar.h"
  36. #include "ads_globals.h"
  37. #include "dockareatabbar.h"
  38. #include "dockareawidget.h"
  39. #include "dockmanager.h"
  40. #include "dockoverlay.h"
  41. #include "dockwidget.h"
  42. #include "dockwidgettab.h"
  43. #include "floatingdockcontainer.h"
  44. #include "floatingdragpreview.h"
  45. #include "iconprovider.h"
  46. #include "dockcomponentsfactory.h"
  47. #include <QBoxLayout>
  48. #include <QLoggingCategory>
  49. #include <QMenu>
  50. #include <QMouseEvent>
  51. #include <QPushButton>
  52. #include <QScrollArea>
  53. #include <QStyle>
  54. #include <iostream>
  55. static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg)
  56. namespace ADS
  57. {
  58. /**
  59. * Private data class of DockAreaTitleBar class (pimpl)
  60. */
  61. struct DockAreaTitleBarPrivate
  62. {
  63. DockAreaTitleBar *q;
  64. QPointer<TitleBarButtonType> m_tabsMenuButton;
  65. QPointer<TitleBarButtonType> m_undockButton;
  66. QPointer<TitleBarButtonType> m_closeButton;
  67. QBoxLayout *m_layout;
  68. DockAreaWidget *m_dockArea;
  69. DockAreaTabBar *m_tabBar;
  70. bool m_menuOutdated = true;
  71. QMenu *m_tabsMenu;
  72. QList<TitleBarButtonType *> m_dockWidgetActionsButtons;
  73. QPoint m_dragStartMousePos;
  74. eDragState m_dragState = DraggingInactive;
  75. AbstractFloatingWidget *m_floatingWidget = nullptr;
  76. /**
  77. * Private data constructor
  78. */
  79. DockAreaTitleBarPrivate(DockAreaTitleBar *parent);
  80. /**
  81. * Creates the title bar close and menu buttons
  82. */
  83. void createButtons();
  84. /**
  85. * Creates the internal TabBar
  86. */
  87. void createTabBar();
  88. /**
  89. * Convenience function for DockManager access
  90. */
  91. DockManager *dockManager() const { return m_dockArea->dockManager(); }
  92. /**
  93. * Returns true if the given config flag is set
  94. */
  95. static bool testConfigFlag(DockManager::eConfigFlag flag)
  96. {
  97. return DockManager::configFlags().testFlag(flag);
  98. }
  99. /**
  100. * Test function for current drag state
  101. */
  102. bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; }
  103. /**
  104. * Starts floating
  105. */
  106. void startFloating(const QPoint &offset);
  107. /**
  108. * Makes the dock area floating
  109. */
  110. AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState);
  111. }; // struct DockAreaTitleBarPrivate
  112. DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent)
  113. : q(parent)
  114. {}
  115. void DockAreaTitleBarPrivate::createButtons()
  116. {
  117. QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
  118. // Tabs menu button
  119. m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton));
  120. m_tabsMenuButton->setObjectName("tabsMenuButton");
  121. m_tabsMenuButton->setAutoRaise(true);
  122. m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup);
  123. internal::setButtonIcon(m_tabsMenuButton,
  124. QStyle::SP_TitleBarUnshadeButton,
  125. ADS::DockAreaMenuIcon);
  126. QMenu *tabsMenu = new QMenu(m_tabsMenuButton);
  127. #ifndef QT_NO_TOOLTIP
  128. tabsMenu->setToolTipsVisible(true);
  129. #endif
  130. QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow);
  131. m_tabsMenuButton->setMenu(tabsMenu);
  132. internal::setToolTip(m_tabsMenuButton, QObject::tr("List All Tabs"));
  133. m_tabsMenuButton->setSizePolicy(sizePolicy);
  134. m_layout->addWidget(m_tabsMenuButton, 0);
  135. QObject::connect(m_tabsMenuButton->menu(),
  136. &QMenu::triggered,
  137. q,
  138. &DockAreaTitleBar::onTabsMenuActionTriggered);
  139. // Undock button
  140. m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton));
  141. m_undockButton->setObjectName("undockButton");
  142. m_undockButton->setAutoRaise(true);
  143. internal::setToolTip(m_undockButton, QObject::tr("Detach Group"));
  144. internal::setButtonIcon(m_undockButton,
  145. QStyle::SP_TitleBarNormalButton,
  146. ADS::DockAreaUndockIcon);
  147. m_undockButton->setSizePolicy(sizePolicy);
  148. m_layout->addWidget(m_undockButton, 0);
  149. QObject::connect(m_undockButton,
  150. &QToolButton::clicked,
  151. q,
  152. &DockAreaTitleBar::onUndockButtonClicked);
  153. // Close button
  154. m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton));
  155. m_closeButton->setObjectName("closeButton");
  156. m_closeButton->setAutoRaise(true);
  157. internal::setButtonIcon(m_closeButton,
  158. QStyle::SP_TitleBarCloseButton,
  159. ADS::DockAreaCloseIcon);
  160. if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) {
  161. internal::setToolTip(m_closeButton, QObject::tr("Close Active Tab"));
  162. } else {
  163. internal::setToolTip(m_closeButton, QObject::tr("Close Group"));
  164. }
  165. m_closeButton->setSizePolicy(sizePolicy);
  166. m_closeButton->setIconSize(QSize(16, 16));
  167. m_layout->addWidget(m_closeButton, 0);
  168. QObject::connect(m_closeButton,
  169. &QToolButton::clicked,
  170. q,
  171. &DockAreaTitleBar::onCloseButtonClicked);
  172. }
  173. void DockAreaTitleBarPrivate::createTabBar()
  174. {
  175. m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea);
  176. m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  177. m_layout->addWidget(m_tabBar);
  178. QObject::connect(m_tabBar,
  179. &DockAreaTabBar::tabClosed,
  180. q,
  181. &DockAreaTitleBar::markTabsMenuOutdated);
  182. QObject::connect(m_tabBar,
  183. &DockAreaTabBar::tabOpened,
  184. q,
  185. &DockAreaTitleBar::markTabsMenuOutdated);
  186. QObject::connect(m_tabBar,
  187. &DockAreaTabBar::tabInserted,
  188. q,
  189. &DockAreaTitleBar::markTabsMenuOutdated);
  190. QObject::connect(m_tabBar,
  191. &DockAreaTabBar::removingTab,
  192. q,
  193. &DockAreaTitleBar::markTabsMenuOutdated);
  194. QObject::connect(m_tabBar,
  195. &DockAreaTabBar::tabMoved,
  196. q,
  197. &DockAreaTitleBar::markTabsMenuOutdated);
  198. QObject::connect(m_tabBar,
  199. &DockAreaTabBar::currentChanged,
  200. q,
  201. &DockAreaTitleBar::onCurrentTabChanged);
  202. QObject::connect(m_tabBar,
  203. &DockAreaTabBar::tabBarClicked,
  204. q,
  205. &DockAreaTitleBar::tabBarClicked);
  206. QObject::connect(m_tabBar,
  207. &DockAreaTabBar::elidedChanged,
  208. q,
  209. &DockAreaTitleBar::markTabsMenuOutdated);
  210. }
  211. AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset,
  212. eDragState dragState)
  213. {
  214. QSize size = m_dockArea->size();
  215. m_dragState = dragState;
  216. bool opaqueUndocking = DockManager::configFlags().testFlag(DockManager::OpaqueUndocking)
  217. || (DraggingFloatingWidget != dragState);
  218. FloatingDockContainer *floatingDockContainer = nullptr;
  219. AbstractFloatingWidget *floatingWidget;
  220. if (opaqueUndocking) {
  221. floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea);
  222. } else {
  223. auto w = new FloatingDragPreview(m_dockArea);
  224. QObject::connect(w, &FloatingDragPreview::draggingCanceled, [=]() {
  225. m_dragState = DraggingInactive;
  226. });
  227. floatingWidget = w;
  228. }
  229. floatingWidget->startFloating(offset, size, dragState, nullptr);
  230. if (floatingDockContainer) {
  231. auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget();
  232. if (topLevelDockWidget) {
  233. topLevelDockWidget->emitTopLevelChanged(true);
  234. }
  235. }
  236. return floatingWidget;
  237. }
  238. void DockAreaTitleBarPrivate::startFloating(const QPoint &offset)
  239. {
  240. m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget);
  241. }
  242. TitleBarButton::TitleBarButton(bool visible, QWidget *parent)
  243. : TitleBarButtonType(parent),
  244. m_visible(visible),
  245. m_hideWhenDisabled(DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons))
  246. {}
  247. void TitleBarButton::setVisible(bool visible)
  248. {
  249. // 'visible' can stay 'true' if and only if this button is configured to generaly visible:
  250. visible = visible && m_visible;
  251. // 'visible' can stay 'true' unless: this button is configured to be invisible when it
  252. // is disabled and it is currently disabled:
  253. if (visible && m_hideWhenDisabled) {
  254. visible = isEnabled();
  255. }
  256. Super::setVisible(visible);
  257. }
  258. bool TitleBarButton::event(QEvent *event)
  259. {
  260. if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) {
  261. // force setVisible() call
  262. // Calling setVisible() directly here doesn't work well when button is expected to be shown first time
  263. QMetaObject::invokeMethod(this, "setVisible", Qt::QueuedConnection, Q_ARG(bool, isEnabled()));
  264. }
  265. return Super::event(event);
  266. }
  267. SpacerWidget::SpacerWidget(QWidget *parent)
  268. : QWidget(parent)
  269. {
  270. setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  271. setStyleSheet("border: none; background: none;");
  272. }
  273. DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent)
  274. : QFrame(parent)
  275. , d(new DockAreaTitleBarPrivate(this))
  276. {
  277. d->m_dockArea = parent;
  278. setObjectName("dockAreaTitleBar");
  279. d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight);
  280. d->m_layout->setContentsMargins(0, 0, 0, 0);
  281. d->m_layout->setSpacing(0);
  282. setLayout(d->m_layout);
  283. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
  284. d->createTabBar();
  285. d->m_layout->addWidget(new SpacerWidget(this));
  286. d->createButtons();
  287. }
  288. DockAreaTitleBar::~DockAreaTitleBar() {
  289. if (!d->m_closeButton.isNull())
  290. delete d->m_closeButton;
  291. if (!d->m_tabsMenuButton.isNull())
  292. delete d->m_tabsMenuButton;
  293. if (!d->m_undockButton.isNull())
  294. delete d->m_undockButton;
  295. delete d;
  296. }
  297. DockAreaTabBar *DockAreaTitleBar::tabBar() const { return d->m_tabBar; }
  298. void DockAreaTitleBar::markTabsMenuOutdated() {
  299. if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaDynamicTabsMenuButtonVisibility)) {
  300. bool hasElidedTabTitle = false;
  301. for (int i = 0; i < d->m_tabBar->count(); ++i) {
  302. if (!d->m_tabBar->isTabOpen(i))
  303. continue;
  304. DockWidgetTab* tab = d->m_tabBar->tab(i);
  305. if (tab->isTitleElided()) {
  306. hasElidedTabTitle = true;
  307. break;
  308. }
  309. }
  310. bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1));
  311. QMetaObject::invokeMethod(d->m_tabsMenuButton, "setVisible", Qt::QueuedConnection, Q_ARG(bool, visible));
  312. }
  313. d->m_menuOutdated = true;
  314. }
  315. void DockAreaTitleBar::onTabsMenuAboutToShow()
  316. {
  317. if (!d->m_menuOutdated) {
  318. return;
  319. }
  320. QMenu *menu = d->m_tabsMenuButton->menu();
  321. menu->clear();
  322. for (int i = 0; i < d->m_tabBar->count(); ++i) {
  323. if (!d->m_tabBar->isTabOpen(i))
  324. continue;
  325. auto tab = d->m_tabBar->tab(i);
  326. QAction *action = menu->addAction(tab->icon(), tab->text());
  327. internal::setToolTip(action, tab->toolTip());
  328. action->setData(i);
  329. }
  330. d->m_menuOutdated = false;
  331. }
  332. void DockAreaTitleBar::onCloseButtonClicked()
  333. {
  334. qCInfo(adsLog) << Q_FUNC_INFO;
  335. if (d->testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) {
  336. d->m_tabBar->closeTab(d->m_tabBar->currentIndex());
  337. } else {
  338. d->m_dockArea->closeArea();
  339. }
  340. }
  341. void DockAreaTitleBar::onUndockButtonClicked()
  342. {
  343. if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) {
  344. d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive);
  345. }
  346. }
  347. void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action)
  348. {
  349. int index = action->data().toInt();
  350. d->m_tabBar->setCurrentIndex(index);
  351. emit tabBarClicked(index);
  352. }
  353. void DockAreaTitleBar::updateDockWidgetActionsButtons()
  354. {
  355. DockWidget* dockWidget = d->m_tabBar->currentTab()->dockWidget();
  356. if (!d->m_dockWidgetActionsButtons.isEmpty()) {
  357. for (auto button : d->m_dockWidgetActionsButtons) {
  358. d->m_layout->removeWidget(button);
  359. delete button;
  360. }
  361. d->m_dockWidgetActionsButtons.clear();
  362. }
  363. auto actions = dockWidget->titleBarActions();
  364. if (actions.isEmpty())
  365. return;
  366. int insertIndex = indexOf(d->m_tabsMenuButton);
  367. for (auto action : actions) {
  368. auto button = new TitleBarButton(true, this);
  369. button->setDefaultAction(action);
  370. button->setAutoRaise(true);
  371. button->setPopupMode(QToolButton::InstantPopup);
  372. button->setObjectName(action->objectName());
  373. d->m_layout->insertWidget(insertIndex++, button, 0);
  374. d->m_dockWidgetActionsButtons.append(button);
  375. }
  376. }
  377. void DockAreaTitleBar::onCurrentTabChanged(int index)
  378. {
  379. if (index < 0)
  380. return;
  381. if (d->testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) {
  382. DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget();
  383. d->m_closeButton->setEnabled(
  384. dockWidget->features().testFlag(DockWidget::DockWidgetClosable));
  385. }
  386. updateDockWidgetActionsButtons();
  387. }
  388. QAbstractButton *DockAreaTitleBar::button(eTitleBarButton which) const
  389. {
  390. switch (which) {
  391. case TitleBarButtonTabsMenu:
  392. return d->m_tabsMenuButton;
  393. case TitleBarButtonUndock:
  394. return d->m_undockButton;
  395. case TitleBarButtonClose:
  396. return d->m_closeButton;
  397. }
  398. return nullptr;
  399. }
  400. void DockAreaTitleBar::setVisible(bool visible)
  401. {
  402. Super::setVisible(visible);
  403. markTabsMenuOutdated();
  404. }
  405. void DockAreaTitleBar::mousePressEvent(QMouseEvent *event)
  406. {
  407. if (event->button() == Qt::LeftButton) {
  408. event->accept();
  409. d->m_dragStartMousePos = event->pos();
  410. d->m_dragState = DraggingMousePressed;
  411. return;
  412. }
  413. Super::mousePressEvent(event);
  414. }
  415. void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event)
  416. {
  417. if (event->button() == Qt::LeftButton) {
  418. qCInfo(adsLog) << Q_FUNC_INFO;
  419. event->accept();
  420. auto CurrentDragState = d->m_dragState;
  421. d->m_dragStartMousePos = QPoint();
  422. d->m_dragState = DraggingInactive;
  423. if (DraggingFloatingWidget == CurrentDragState)
  424. d->m_floatingWidget->finishDragging();
  425. return;
  426. }
  427. Super::mouseReleaseEvent(event);
  428. }
  429. void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event)
  430. {
  431. Super::mouseMoveEvent(event);
  432. if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) {
  433. d->m_dragState = DraggingInactive;
  434. return;
  435. }
  436. // move floating window
  437. if (d->isDraggingState(DraggingFloatingWidget)) {
  438. d->m_floatingWidget->moveFloating();
  439. return;
  440. }
  441. // If this is the last dock area in a dock container it does not make
  442. // sense to move it to a new floating widget and leave this one empty
  443. if (d->m_dockArea->dockContainer()->isFloating()
  444. && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) {
  445. return;
  446. }
  447. // If one single dock widget in this area is not floatable then the whole
  448. // area is not floatable
  449. // If we do non opaque undocking, then we can create the floating drag
  450. // preview if the dock widget is movable
  451. auto features = d->m_dockArea->features();
  452. if (!features.testFlag(DockWidget::DockWidgetFloatable)
  453. && !(features.testFlag(DockWidget::DockWidgetMovable)
  454. && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) {
  455. return;
  456. }
  457. int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength();
  458. if (dragDistance >= DockManager::startDragDistance()) {
  459. qCInfo(adsLog) << "TabsScrollArea::startFloating";
  460. d->startFloating(d->m_dragStartMousePos);
  461. auto overlay = d->m_dockArea->dockManager()->containerOverlay();
  462. overlay->setAllowedAreas(OuterDockAreas);
  463. }
  464. return;
  465. }
  466. void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
  467. {
  468. // If this is the last dock area in a dock container it does not make
  469. // sense to move it to a new floating widget and leave this one empty
  470. if (d->m_dockArea->dockContainer()->isFloating()
  471. && d->m_dockArea->dockContainer()->dockAreaCount() == 1)
  472. return;
  473. if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable))
  474. return;
  475. d->makeAreaFloating(event->pos(), DraggingInactive);
  476. }
  477. void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event)
  478. {
  479. event->accept();
  480. if (d->isDraggingState(DraggingFloatingWidget))
  481. return;
  482. QMenu menu(this);
  483. auto action = menu.addAction(tr("Detach Area"),
  484. this,
  485. &DockAreaTitleBar::onUndockButtonClicked);
  486. action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable));
  487. menu.addSeparator();
  488. action = menu.addAction(tr("Close Area"), this, &DockAreaTitleBar::onCloseButtonClicked);
  489. action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable));
  490. menu.addAction(tr("Close Other Areas"), d->m_dockArea, &DockAreaWidget::closeOtherAreas);
  491. menu.exec(event->globalPos());
  492. }
  493. void DockAreaTitleBar::insertWidget(int index, QWidget *widget)
  494. {
  495. d->m_layout->insertWidget(index, widget);
  496. }
  497. int DockAreaTitleBar::indexOf(QWidget *widget) const
  498. {
  499. return d->m_layout->indexOf(widget);
  500. }
  501. } // namespace ADS