floatingdockcontainer.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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 "floatingdockcontainer.h"
  36. #include "dockareawidget.h"
  37. #include "dockcontainerwidget.h"
  38. #include "dockmanager.h"
  39. #include "dockoverlay.h"
  40. #include "dockwidget.h"
  41. #include "linux/floatingwidgettitlebar.h"
  42. #include "utils/hostosinfo.h"
  43. #include <QAbstractButton>
  44. #include <QAction>
  45. #include <QApplication>
  46. #include <QBoxLayout>
  47. #include <QDebug>
  48. #include <QElapsedTimer>
  49. #include <QLoggingCategory>
  50. #include <QMouseEvent>
  51. #include <QPointer>
  52. static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg)
  53. namespace ADS
  54. {
  55. AbstractFloatingWidget::~AbstractFloatingWidget() = default;
  56. static unsigned int zOrderCounter = 0;
  57. /**
  58. * Private data class of FloatingDockContainer class (pimpl)
  59. */
  60. struct FloatingDockContainerPrivate
  61. {
  62. FloatingDockContainer *q;
  63. DockContainerWidget *m_dockContainer;
  64. unsigned int m_zOrderIndex = ++zOrderCounter;
  65. QPointer<DockManager> m_dockManager;
  66. eDragState m_draggingState = DraggingInactive;
  67. QPoint m_dragStartMousePosition;
  68. DockContainerWidget *m_dropContainer = nullptr;
  69. DockAreaWidget *m_singleDockArea = nullptr;
  70. QWidget *m_mouseEventHandler = nullptr;
  71. FloatingWidgetTitleBar *m_titleBar = nullptr;
  72. /**
  73. * Private data constructor
  74. */
  75. FloatingDockContainerPrivate(FloatingDockContainer *parent);
  76. void titleMouseReleaseEvent();
  77. void updateDropOverlays(const QPoint &globalPosition);
  78. /**
  79. * Returns true if the given config flag is set
  80. */
  81. static bool testConfigFlag(DockManager::eConfigFlag flag)
  82. {
  83. return DockManager::configFlags().testFlag(flag);
  84. }
  85. /**
  86. * Tests is a certain state is active
  87. */
  88. bool isState(eDragState stateId) const { return stateId == m_draggingState; }
  89. void setState(eDragState stateId) { m_draggingState = stateId; }
  90. void setWindowTitle(const QString &text)
  91. {
  92. if (Utils::HostOsInfo::isLinuxHost())
  93. m_titleBar->setTitle(text);
  94. else
  95. q->setWindowTitle(text);
  96. }
  97. void reflectCurrentWidget(DockWidget *currentWidget)
  98. {
  99. // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title
  100. if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) {
  101. setWindowTitle(currentWidget->windowTitle());
  102. } else {
  103. setWindowTitle(qApp->applicationDisplayName());
  104. }
  105. // reflect CurrentWidget's icon if configured to do so, otherwise display application icon as window icon
  106. QIcon CurrentWidgetIcon = currentWidget->icon();
  107. if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon)
  108. && !CurrentWidgetIcon.isNull())
  109. {
  110. q->setWindowIcon(currentWidget->icon());
  111. } else {
  112. q->setWindowIcon(QApplication::windowIcon());
  113. }
  114. }
  115. };
  116. // struct FloatingDockContainerPrivate
  117. FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent)
  118. : q(parent)
  119. {}
  120. void FloatingDockContainerPrivate::titleMouseReleaseEvent()
  121. {
  122. setState(DraggingInactive);
  123. if (!m_dropContainer) {
  124. return;
  125. }
  126. if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea
  127. || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) {
  128. // Resize the floating widget to the size of the highlighted drop area rectangle
  129. DockOverlay *overlay = m_dockManager->containerOverlay();
  130. if (!overlay->dropOverlayRect().isValid()) {
  131. overlay = m_dockManager->dockAreaOverlay();
  132. }
  133. QRect rect = overlay->dropOverlayRect();
  134. int frameWidth = (q->frameSize().width() - q->rect().width()) / 2;
  135. int titleBarHeight = q->frameSize().height() - q->rect().height() - frameWidth;
  136. if (rect.isValid()) {
  137. QPoint topLeft = overlay->mapToGlobal(rect.topLeft());
  138. topLeft.ry() += titleBarHeight;
  139. q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight)));
  140. QApplication::processEvents();
  141. }
  142. m_dropContainer->dropFloatingWidget(q, QCursor::pos());
  143. }
  144. m_dockManager->containerOverlay()->hideOverlay();
  145. m_dockManager->dockAreaOverlay()->hideOverlay();
  146. }
  147. void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition)
  148. {
  149. if (!q->isVisible() || !m_dockManager) {
  150. return;
  151. }
  152. auto containers = m_dockManager->dockContainers();
  153. DockContainerWidget *topContainer = nullptr;
  154. for (auto containerWidget : containers) {
  155. if (!containerWidget->isVisible()) {
  156. continue;
  157. }
  158. if (m_dockContainer == containerWidget) {
  159. continue;
  160. }
  161. QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition);
  162. if (containerWidget->rect().contains(mappedPos)) {
  163. if (!topContainer || containerWidget->isInFrontOf(topContainer)) {
  164. topContainer = containerWidget;
  165. }
  166. }
  167. }
  168. m_dropContainer = topContainer;
  169. auto containerOverlay = m_dockManager->containerOverlay();
  170. auto dockAreaOverlay = m_dockManager->dockAreaOverlay();
  171. if (!topContainer) {
  172. containerOverlay->hideOverlay();
  173. dockAreaOverlay->hideOverlay();
  174. return;
  175. }
  176. int visibleDockAreas = topContainer->visibleDockAreaCount();
  177. containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas);
  178. DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer);
  179. containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea);
  180. auto dockArea = topContainer->dockAreaAt(globalPosition);
  181. if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) {
  182. dockAreaOverlay->enableDropPreview(true);
  183. dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea
  184. : dockArea->allowedAreas());
  185. DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea);
  186. // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in
  187. // the title bar. If the ContainerArea is valid then we ignore the dock area of the
  188. // dockAreaOverlay() and disable the drop preview
  189. if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) {
  190. dockAreaOverlay->enableDropPreview(false);
  191. containerOverlay->enableDropPreview(true);
  192. } else {
  193. containerOverlay->enableDropPreview(InvalidDockWidgetArea == area);
  194. }
  195. } else {
  196. dockAreaOverlay->hideOverlay();
  197. }
  198. }
  199. FloatingDockContainer::FloatingDockContainer(DockManager *dockManager)
  200. : FloatingWidgetBaseType(dockManager)
  201. , d(new FloatingDockContainerPrivate(this))
  202. {
  203. d->m_dockManager = dockManager;
  204. d->m_dockContainer = new DockContainerWidget(dockManager, this);
  205. connect(d->m_dockContainer,
  206. &DockContainerWidget::dockAreasAdded,
  207. this,
  208. &FloatingDockContainer::onDockAreasAddedOrRemoved);
  209. connect(d->m_dockContainer,
  210. &DockContainerWidget::dockAreasRemoved,
  211. this,
  212. &FloatingDockContainer::onDockAreasAddedOrRemoved);
  213. #ifdef Q_OS_LINUX
  214. d->m_titleBar = new FloatingWidgetTitleBar(this);
  215. setWindowFlags(windowFlags() | Qt::Tool);
  216. QDockWidget::setWidget(d->m_dockContainer);
  217. QDockWidget::setFloating(true);
  218. QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures);
  219. setTitleBarWidget(d->m_titleBar);
  220. connect(d->m_titleBar,
  221. &FloatingWidgetTitleBar::closeRequested,
  222. this,
  223. &FloatingDockContainer::close);
  224. #else
  225. setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
  226. QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom);
  227. boxLayout->setContentsMargins(0, 0, 0, 0);
  228. boxLayout->setSpacing(0);
  229. setLayout(boxLayout);
  230. boxLayout->addWidget(d->m_dockContainer);
  231. #endif
  232. dockManager->registerFloatingWidget(this);
  233. }
  234. FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea)
  235. : FloatingDockContainer(dockArea->dockManager())
  236. {
  237. d->m_dockContainer->addDockArea(dockArea);
  238. if (Utils::HostOsInfo::isLinuxHost())
  239. d->m_titleBar->enableCloseButton(isClosable());
  240. auto dw = topLevelDockWidget();
  241. if (dw) {
  242. dw->emitTopLevelChanged(true);
  243. }
  244. }
  245. FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget)
  246. : FloatingDockContainer(dockWidget->dockManager())
  247. {
  248. d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget);
  249. if (Utils::HostOsInfo::isLinuxHost())
  250. d->m_titleBar->enableCloseButton(isClosable());
  251. auto dw = topLevelDockWidget();
  252. if (dw) {
  253. dw->emitTopLevelChanged(true);
  254. }
  255. }
  256. FloatingDockContainer::~FloatingDockContainer()
  257. {
  258. qCInfo(adsLog) << Q_FUNC_INFO;
  259. if (d->m_dockManager) {
  260. d->m_dockManager->removeFloatingWidget(this);
  261. }
  262. delete d;
  263. }
  264. DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; }
  265. void FloatingDockContainer::changeEvent(QEvent *event)
  266. {
  267. QWidget::changeEvent(event);
  268. if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) {
  269. qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::ActivationChange";
  270. d->m_zOrderIndex = ++zOrderCounter;
  271. return;
  272. }
  273. }
  274. void FloatingDockContainer::moveEvent(QMoveEvent *event)
  275. {
  276. QWidget::moveEvent(event);
  277. switch (d->m_draggingState) {
  278. case DraggingMousePressed:
  279. d->setState(DraggingFloatingWidget);
  280. d->updateDropOverlays(QCursor::pos());
  281. break;
  282. case DraggingFloatingWidget:
  283. d->updateDropOverlays(QCursor::pos());
  284. if (Utils::HostOsInfo::isMacHost()) {
  285. // In macOS when hiding the DockAreaOverlay the application would set
  286. // the main window as the active window for some reason. This fixes
  287. // that by resetting the active window to the floating widget after
  288. // updating the overlays.
  289. QApplication::setActiveWindow(this);
  290. }
  291. break;
  292. default:
  293. break;
  294. }
  295. }
  296. void FloatingDockContainer::closeEvent(QCloseEvent *event)
  297. {
  298. qCInfo(adsLog) << Q_FUNC_INFO;
  299. d->setState(DraggingInactive);
  300. event->ignore();
  301. if (isClosable()) {
  302. auto dw = topLevelDockWidget();
  303. if (dw && dw->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) {
  304. if (!dw->closeDockWidgetInternal()) {
  305. return;
  306. }
  307. }
  308. this->hide();
  309. }
  310. }
  311. void FloatingDockContainer::hideEvent(QHideEvent *event)
  312. {
  313. Super::hideEvent(event);
  314. if (event->spontaneous()) {
  315. return;
  316. }
  317. // Prevent toogleView() events during restore state
  318. if (d->m_dockManager->isRestoringState()) {
  319. return;
  320. }
  321. for (auto dockArea : d->m_dockContainer->openedDockAreas()) {
  322. for (auto dockWidget : dockArea->openedDockWidgets()) {
  323. dockWidget->toggleView(false);
  324. }
  325. }
  326. }
  327. void FloatingDockContainer::showEvent(QShowEvent *event) { Super::showEvent(event); }
  328. bool FloatingDockContainer::event(QEvent *event)
  329. {
  330. switch (d->m_draggingState) {
  331. case DraggingInactive: {
  332. // Normally we would check here, if the left mouse button is pressed.
  333. // But from QT version 5.12.2 on the mouse events from
  334. // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button
  335. // The event always returns Qt::RightButton even if the left button is clicked.
  336. // It is really great to work around the whole NonClientMouseArea bugs
  337. #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 2))
  338. if (event->type()
  339. == QEvent::
  340. NonClientAreaMouseButtonPress /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) {
  341. qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress"
  342. << event->type();
  343. d->setState(DraggingMousePressed);
  344. }
  345. #else
  346. if (e->type() == QEvent::NonClientAreaMouseButtonPress
  347. && QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)) {
  348. qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress"
  349. << e->type();
  350. d->setState(DraggingMousePressed);
  351. }
  352. #endif
  353. } break;
  354. case DraggingMousePressed:
  355. switch (event->type()) {
  356. case QEvent::NonClientAreaMouseButtonDblClick:
  357. qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonDblClick";
  358. d->setState(DraggingInactive);
  359. break;
  360. case QEvent::Resize:
  361. // If the first event after the mouse press is a resize event, then
  362. // the user resizes the window instead of dragging it around.
  363. // But there is one exception. If the window is maximized,
  364. // then dragging the window via title bar will cause the widget to
  365. // leave the maximized state. This in turn will trigger a resize event.
  366. // To know, if the resize event was triggered by user via moving a
  367. // corner of the window frame or if it was caused by a windows state
  368. // change, we check, if we are not in maximized state.
  369. if (!isMaximized()) {
  370. d->setState(DraggingInactive);
  371. }
  372. break;
  373. default:
  374. break;
  375. }
  376. break;
  377. case DraggingFloatingWidget:
  378. if (event->type() == QEvent::NonClientAreaMouseButtonRelease) {
  379. qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonRelease";
  380. d->titleMouseReleaseEvent();
  381. }
  382. break;
  383. default:
  384. break;
  385. }
  386. #if (ADS_DEBUG_LEVEL > 0)
  387. qDebug() << "FloatingDockContainer::event " << event->type();
  388. #endif
  389. return QWidget::event(event);
  390. }
  391. void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos,
  392. const QSize &size,
  393. eDragState dragState,
  394. QWidget *mouseEventHandler)
  395. {
  396. resize(size);
  397. d->setState(dragState);
  398. d->m_dragStartMousePosition = dragStartMousePos;
  399. if (Utils::HostOsInfo::isLinuxHost()) {
  400. if (DraggingFloatingWidget == dragState) {
  401. setAttribute(Qt::WA_X11NetWmWindowTypeDock, true);
  402. d->m_mouseEventHandler = mouseEventHandler;
  403. if (d->m_mouseEventHandler) {
  404. d->m_mouseEventHandler->grabMouse();
  405. }
  406. }
  407. }
  408. moveFloating();
  409. show();
  410. }
  411. void FloatingDockContainer::moveFloating()
  412. {
  413. int borderSize = (frameSize().width() - size().width()) / 2;
  414. const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition
  415. - QPoint(borderSize, 0);
  416. move(moveToPos);
  417. }
  418. bool FloatingDockContainer::isClosable() const
  419. {
  420. return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable);
  421. }
  422. void FloatingDockContainer::onDockAreasAddedOrRemoved()
  423. {
  424. qCInfo(adsLog) << Q_FUNC_INFO;
  425. auto topLevelDockArea = d->m_dockContainer->topLevelDockArea();
  426. if (topLevelDockArea) {
  427. d->m_singleDockArea = topLevelDockArea;
  428. DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget();
  429. d->reflectCurrentWidget(currentWidget);
  430. connect(d->m_singleDockArea,
  431. &DockAreaWidget::currentChanged,
  432. this,
  433. &FloatingDockContainer::onDockAreaCurrentChanged);
  434. } else {
  435. if (d->m_singleDockArea) {
  436. disconnect(d->m_singleDockArea,
  437. &DockAreaWidget::currentChanged,
  438. this,
  439. &FloatingDockContainer::onDockAreaCurrentChanged);
  440. d->m_singleDockArea = nullptr;
  441. }
  442. d->setWindowTitle(qApp->applicationDisplayName());
  443. setWindowIcon(QApplication::windowIcon());
  444. }
  445. }
  446. void FloatingDockContainer::updateWindowTitle()
  447. {
  448. auto topLevelDockArea = d->m_dockContainer->topLevelDockArea();
  449. if (topLevelDockArea) {
  450. DockWidget *currentWidget = topLevelDockArea->currentDockWidget();
  451. d->reflectCurrentWidget(currentWidget);
  452. } else {
  453. d->setWindowTitle(qApp->applicationDisplayName());
  454. setWindowIcon(QApplication::windowIcon());
  455. }
  456. }
  457. void FloatingDockContainer::onDockAreaCurrentChanged(int index)
  458. {
  459. Q_UNUSED(index)
  460. DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget();
  461. d->reflectCurrentWidget(currentWidget);
  462. }
  463. bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing)
  464. {
  465. if (!d->m_dockContainer->restoreState(stream, testing)) {
  466. return false;
  467. }
  468. onDockAreasAddedOrRemoved();
  469. return true;
  470. }
  471. bool FloatingDockContainer::hasTopLevelDockWidget() const
  472. {
  473. return d->m_dockContainer->hasTopLevelDockWidget();
  474. }
  475. DockWidget *FloatingDockContainer::topLevelDockWidget() const
  476. {
  477. return d->m_dockContainer->topLevelDockWidget();
  478. }
  479. QList<DockWidget *> FloatingDockContainer::dockWidgets() const
  480. {
  481. return d->m_dockContainer->dockWidgets();
  482. }
  483. void FloatingDockContainer::finishDragging()
  484. {
  485. qCInfo(adsLog) << Q_FUNC_INFO;
  486. if (Utils::HostOsInfo::isLinuxHost()) {
  487. setAttribute(Qt::WA_X11NetWmWindowTypeDock, false);
  488. setWindowOpacity(1);
  489. activateWindow();
  490. if (d->m_mouseEventHandler) {
  491. d->m_mouseEventHandler->releaseMouse();
  492. d->m_mouseEventHandler = nullptr;
  493. }
  494. }
  495. d->titleMouseReleaseEvent();
  496. }
  497. } // namespace ADS