1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QMAINWINDOWLAYOUT_P_H
5#define QMAINWINDOWLAYOUT_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtWidgets/private/qtwidgetsglobal_p.h>
19#include "qmainwindow.h"
20
21#include "QtWidgets/qlayout.h"
22#if QT_CONFIG(tabbar)
23#include "QtWidgets/qtabbar.h"
24#include "QtGui/qpainter.h"
25#include "QtGui/qevent.h"
26#endif
27#include "QtCore/qbasictimer.h"
28#include "QtCore/qlist.h"
29#include "QtCore/qset.h"
30#include "private/qlayoutengine_p.h"
31#include "private/qwidgetanimator_p.h"
32#if QT_CONFIG(dockwidget)
33#include "private/qdockwidget_p.h"
34
35#include "qdockarealayout_p.h"
36#include "qdockwidget.h"
37#else
38struct QDockWidgetPrivate {
39 enum class DragScope {
40 Group
41 };
42};
43#endif
44#if QT_CONFIG(toolbar)
45#include "qtoolbararealayout_p.h"
46#include "qtoolbar.h"
47#endif
48
49#include <QtCore/qloggingcategory.h>
50#include <QtCore/qpointer.h>
51
52QT_REQUIRE_CONFIG(mainwindow);
53
54QT_BEGIN_NAMESPACE
55
56Q_DECLARE_LOGGING_CATEGORY(lcQpaDockWidgets);
57
58class QToolBar;
59class QRubberBand;
60
61template <typename Layout> // Make use of the "Curiously recurring template pattern"
62class QMainWindowLayoutSeparatorHelper
63{
64 Layout *layout() { return static_cast<Layout *>(this); }
65 const Layout *layout() const { return static_cast<const Layout *>(this); }
66 QWidget *window() { return layout()->parentWidget(); }
67
68public:
69 Q_DISABLE_COPY_MOVE(QMainWindowLayoutSeparatorHelper)
70
71 QMainWindowLayoutSeparatorHelper() = default;
72
73 QList<int> hoverSeparator;
74 QPoint hoverPos;
75
76#if QT_CONFIG(dockwidget)
77
78#if QT_CONFIG(cursor)
79 QCursor separatorCursor(const QList<int> &path);
80 void adjustCursor(const QPoint &pos);
81 QCursor oldCursor;
82 QCursor adjustedCursor;
83 bool hasOldCursor = false;
84 bool cursorAdjusted = false;
85#endif // QT_CONFIG(cursor)
86
87 QList<int> movingSeparator;
88 QPoint movingSeparatorOrigin, movingSeparatorPos;
89 QBasicTimer separatorMoveTimer;
90
91 bool startSeparatorMove(const QPoint &pos);
92 bool separatorMove(const QPoint &pos);
93 bool endSeparatorMove(const QPoint &pos);
94 bool windowEvent(QEvent *e);
95
96private:
97 QList<int> findSeparator(const QPoint &pos) const;
98
99#endif // QT_CONFIG(dockwidget)
100
101};
102
103#if QT_CONFIG(dockwidget)
104
105#if QT_CONFIG(cursor)
106template <typename Layout>
107QCursor QMainWindowLayoutSeparatorHelper<Layout>::separatorCursor(const QList<int> &path)
108{
109 const QDockAreaLayoutInfo *info = layout()->dockAreaLayoutInfo()->info(path);
110 Q_ASSERT(info != nullptr);
111 if (path.size() == 1) { // is this the "top-level" separator which separates a dock area
112 // from the central widget?
113 switch (path.first()) {
114 case QInternal::LeftDock:
115 case QInternal::RightDock:
116 return Qt::SplitHCursor;
117 case QInternal::TopDock:
118 case QInternal::BottomDock:
119 return Qt::SplitVCursor;
120 default:
121 break;
122 }
123 }
124
125 // no, it's a splitter inside a dock area, separating two dock widgets
126
127 return info->o == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor;
128}
129
130template <typename Layout>
131void QMainWindowLayoutSeparatorHelper<Layout>::adjustCursor(const QPoint &pos)
132{
133 QWidget *w = layout()->window();
134 hoverPos = pos;
135
136 if (pos == QPoint(0, 0)) {
137 if (!hoverSeparator.isEmpty())
138 w->update(layout()->dockAreaLayoutInfo()->separatorRect(hoverSeparator));
139 hoverSeparator.clear();
140
141 if (cursorAdjusted) {
142 cursorAdjusted = false;
143 if (hasOldCursor)
144 w->setCursor(oldCursor);
145 else
146 w->unsetCursor();
147 }
148 } else if (movingSeparator.isEmpty()) { // Don't change cursor when moving separator
149 QList<int> pathToSeparator = findSeparator(pos);
150
151 if (pathToSeparator != hoverSeparator) {
152 if (!hoverSeparator.isEmpty())
153 w->update(layout()->dockAreaLayoutInfo()->separatorRect(hoverSeparator));
154
155 hoverSeparator = pathToSeparator;
156
157 if (hoverSeparator.isEmpty()) {
158 if (cursorAdjusted) {
159 cursorAdjusted = false;
160 if (hasOldCursor)
161 w->setCursor(oldCursor);
162 else
163 w->unsetCursor();
164 }
165 } else {
166 w->update(layout()->dockAreaLayoutInfo()->separatorRect(hoverSeparator));
167 if (!cursorAdjusted) {
168 oldCursor = w->cursor();
169 hasOldCursor = w->testAttribute(attribute: Qt::WA_SetCursor);
170 }
171 adjustedCursor = separatorCursor(path: hoverSeparator);
172 w->setCursor(adjustedCursor);
173 cursorAdjusted = true;
174 }
175 }
176 }
177}
178#endif // QT_CONFIG(cursor)
179
180template <typename Layout>
181bool QMainWindowLayoutSeparatorHelper<Layout>::windowEvent(QEvent *event)
182{
183 QWidget *w = window();
184 switch (event->type()) {
185 case QEvent::Paint: {
186 QPainter p(w);
187 QRegion r = static_cast<QPaintEvent *>(event)->region();
188 layout()->dockAreaLayoutInfo()->paintSeparators(&p, w, r, hoverPos);
189 break;
190 }
191
192#if QT_CONFIG(cursor)
193 case QEvent::HoverMove: {
194 adjustCursor(pos: static_cast<QHoverEvent *>(event)->position().toPoint());
195 break;
196 }
197
198 // We don't want QWidget to call update() on the entire QMainWindow
199 // on HoverEnter and HoverLeave, hence accept the event (return true).
200 case QEvent::HoverEnter:
201 return true;
202 case QEvent::HoverLeave:
203 adjustCursor(pos: QPoint(0, 0));
204 return true;
205 case QEvent::ShortcutOverride: // when a menu pops up
206 adjustCursor(pos: QPoint(0, 0));
207 break;
208#endif // QT_CONFIG(cursor)
209
210 case QEvent::MouseButtonPress: {
211 QMouseEvent *e = static_cast<QMouseEvent *>(event);
212 if (e->button() == Qt::LeftButton && startSeparatorMove(pos: e->position().toPoint())) {
213 // The click was on a separator, eat this event
214 e->accept();
215 return true;
216 }
217 break;
218 }
219
220 case QEvent::MouseMove: {
221 QMouseEvent *e = static_cast<QMouseEvent *>(event);
222
223#if QT_CONFIG(cursor)
224 adjustCursor(pos: e->position().toPoint());
225#endif
226 if (e->buttons() & Qt::LeftButton) {
227 if (separatorMove(pos: e->position().toPoint())) {
228 // We're moving a separator, eat this event
229 e->accept();
230 return true;
231 }
232 }
233
234 break;
235 }
236
237 case QEvent::MouseButtonRelease: {
238 QMouseEvent *e = static_cast<QMouseEvent *>(event);
239 if (endSeparatorMove(pos: e->position().toPoint())) {
240 // We've released a separator, eat this event
241 e->accept();
242 return true;
243 }
244 break;
245 }
246
247#if QT_CONFIG(cursor)
248 case QEvent::CursorChange:
249 // CursorChange events are triggered as mouse moves to new widgets even
250 // if the cursor doesn't actually change, so do not change oldCursor if
251 // the "changed" cursor has same shape as adjusted cursor.
252 if (cursorAdjusted && adjustedCursor.shape() != w->cursor().shape()) {
253 oldCursor = w->cursor();
254 hasOldCursor = w->testAttribute(attribute: Qt::WA_SetCursor);
255
256 // Ensure our adjusted cursor stays visible
257 w->setCursor(adjustedCursor);
258 }
259 break;
260#endif // QT_CONFIG(cursor)
261 case QEvent::Timer:
262 if (static_cast<QTimerEvent *>(event)->timerId() == separatorMoveTimer.timerId()) {
263 // let's move the separators
264 separatorMoveTimer.stop();
265 if (movingSeparator.isEmpty())
266 return true;
267 if (movingSeparatorOrigin == movingSeparatorPos)
268 return true;
269
270 // when moving the separator, we need to update the previous position
271 window()->update(layout()->dockAreaLayoutInfo()->separatorRegion());
272
273 layout()->layoutState = layout()->savedState;
274 layout()->dockAreaLayoutInfo()->separatorMove(movingSeparator, movingSeparatorOrigin,
275 movingSeparatorPos);
276 movingSeparatorPos = movingSeparatorOrigin;
277 return true;
278 }
279 break;
280 default:
281 break;
282 }
283 return false;
284}
285
286template <typename Layout>
287QList<int> QMainWindowLayoutSeparatorHelper<Layout>::findSeparator(const QPoint &pos) const
288{
289 Layout *layout = const_cast<Layout*>(this->layout());
290#if QT_CONFIG(toolbar)
291 QToolBarAreaLayout *toolBarAreaLayout = layout->toolBarAreaLayout();
292 if (toolBarAreaLayout && !toolBarAreaLayout->isEmpty()) {
293 // We might have a toolbar that is currently expanded, covering
294 // parts of the dock area, in which case we don't want the dock
295 // area layout to treat mouse events for the expanded toolbar as
296 // hitting a separator.
297 const QWidget *widget = layout->window();
298 QWidget *childWidget = widget->childAt(p: pos);
299 while (childWidget && childWidget != widget) {
300 if (auto *toolBar = qobject_cast<QToolBar*>(object: childWidget)) {
301 if (!toolBarAreaLayout->indexOf(toolBar).isEmpty())
302 return {};
303 }
304 childWidget = childWidget->parentWidget();
305 }
306 }
307#endif
308 return layout->dockAreaLayoutInfo()->findSeparator(pos);
309}
310
311template <typename Layout>
312bool QMainWindowLayoutSeparatorHelper<Layout>::startSeparatorMove(const QPoint &pos)
313{
314 movingSeparator = findSeparator(pos);
315
316 if (movingSeparator.isEmpty())
317 return false;
318
319 layout()->savedState = layout()->layoutState;
320 movingSeparatorPos = movingSeparatorOrigin = pos;
321
322 return true;
323}
324template <typename Layout>
325bool QMainWindowLayoutSeparatorHelper<Layout>::separatorMove(const QPoint &pos)
326{
327 if (movingSeparator.isEmpty())
328 return false;
329 movingSeparatorPos = pos;
330 separatorMoveTimer.start(0, window());
331 return true;
332}
333template <typename Layout>
334bool QMainWindowLayoutSeparatorHelper<Layout>::endSeparatorMove(const QPoint &)
335{
336 if (movingSeparator.isEmpty())
337 return false;
338 movingSeparator.clear();
339 layout()->savedState.clear();
340 return true;
341}
342
343class Q_AUTOTEST_EXPORT QDockWidgetGroupWindow : public QWidget
344{
345 Q_OBJECT
346public:
347 explicit QDockWidgetGroupWindow(QWidget *parent = nullptr, Qt::WindowFlags f = {})
348 : QWidget(parent, f)
349 {
350 }
351 QDockAreaLayoutInfo *layoutInfo() const;
352#if QT_CONFIG(tabbar)
353 const QDockAreaLayoutInfo *tabLayoutInfo() const;
354 QDockWidget *activeTabbedDockWidget() const;
355#endif
356 void destroyOrHideIfEmpty();
357 bool hasVisibleDockWidgets() const;
358 void adjustFlags();
359 bool hasNativeDecos() const;
360
361 bool hover(QLayoutItem *widgetItem, const QPoint &mousePos);
362 void updateCurrentGapRect();
363 void restore();
364 void apply();
365 void childEvent(QChildEvent *event) override;
366 void reparentToMainWindow(QDockWidget *dockWidget);
367 void destroyIfSingleItemLeft();
368 QList<QDockWidget *> dockWidgets() const { return findChildren<QDockWidget *>(); }
369
370 QRect currentGapRect;
371 QList<int> currentGapPos;
372
373signals:
374 void resized();
375
376protected:
377 bool event(QEvent *) override;
378 bool eventFilter(QObject *obj, QEvent *event) override;
379 void paintEvent(QPaintEvent*) override;
380
381private:
382 QSize m_removedFrameSize;
383};
384
385// This item will be used in the layout for the gap item. We cannot use QWidgetItem directly
386// because QWidgetItem functions return an empty size for widgets that are floating.
387class QDockWidgetGroupWindowItem : public QWidgetItem
388{
389public:
390 explicit QDockWidgetGroupWindowItem(QDockWidgetGroupWindow *parent) : QWidgetItem(parent) {}
391
392 // when the item contains a dock widget, obtain its size (to prevent infinite loop)
393 // ask the layout otherwise
394 QSize minimumSize() const override
395 {
396 if (auto dw = widget()->findChild<QDockWidget *>())
397 return dw->minimumSize();
398 return lay()->minimumSize();
399 }
400 QSize maximumSize() const override
401 {
402 auto dw = widget()->findChild<QDockWidget *>();
403 if (dw)
404 return dw->maximumSize();
405 return lay()->maximumSize();
406 }
407 QSize sizeHint() const override
408 {
409 auto dw = widget()->findChild<QDockWidget *>();
410 if (dw)
411 return dw->sizeHint();
412 return lay()->sizeHint();
413 }
414 QWidget* widget() const override { return wid; }
415
416private:
417 QLayout *lay() const { return const_cast<QDockWidgetGroupWindowItem *>(this)->widget()->layout(); }
418};
419#endif // QT_CONFIG(dockwidget)
420
421/* This data structure represents the state of all the tool-bars and dock-widgets. It's value based
422 so it can be easily copied into a temporary variable. All operations are performed without moving
423 any widgets. Only when we are sure we have the desired state, we call apply(), which moves the
424 widgets.
425*/
426
427class Q_AUTOTEST_EXPORT QMainWindowLayoutState
428{
429public:
430 QRect rect;
431 QMainWindow *mainWindow;
432
433 QMainWindowLayoutState(QMainWindow *win);
434
435#if QT_CONFIG(toolbar)
436 QToolBarAreaLayout toolBarAreaLayout;
437#endif
438
439#if QT_CONFIG(dockwidget)
440 QDockAreaLayout dockAreaLayout;
441#else
442 QLayoutItem *centralWidgetItem;
443 QRect centralWidgetRect;
444#endif
445
446 void apply(bool animated);
447 void deleteAllLayoutItems();
448 void deleteCentralWidgetItem();
449
450 QSize sizeHint() const;
451 QSize minimumSize() const;
452 bool fits() const;
453 void fitLayout();
454
455 QLayoutItem *itemAt(int index, int *x) const;
456 QLayoutItem *takeAt(int index, int *x);
457 QList<int> indexOf(QWidget *widget) const;
458 QLayoutItem *item(const QList<int> &path);
459 QRect itemRect(const QList<int> &path) const;
460 QRect gapRect(const QList<int> &path) const; // ### get rid of this, use itemRect() instead
461
462 bool contains(QWidget *widget) const;
463
464 void setCentralWidget(QWidget *widget);
465 QWidget *centralWidget() const;
466
467 QList<int> gapIndex(QWidget *widget, const QPoint &pos) const;
468 bool insertGap(const QList<int> &path, QLayoutItem *item);
469 void remove(const QList<int> &path);
470 void remove(QLayoutItem *item);
471 void clear();
472 bool isValid() const;
473
474 QLayoutItem *plug(const QList<int> &path);
475 QLayoutItem *unplug(const QList<int> &path, QMainWindowLayoutState *savedState = nullptr);
476
477 void saveState(QDataStream &stream) const;
478 bool checkFormat(QDataStream &stream);
479 bool restoreState(QDataStream &stream, const QMainWindowLayoutState &oldState);
480};
481
482class QMainWindowTabBar;
483class Q_AUTOTEST_EXPORT QMainWindowLayout
484 : public QLayout,
485 public QMainWindowLayoutSeparatorHelper<QMainWindowLayout>
486{
487 Q_OBJECT
488
489public:
490 QMainWindowLayoutState layoutState, savedState;
491 std::unique_ptr<QMainWindowLayoutState> restoredState;
492
493 QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLayout);
494 ~QMainWindowLayout();
495
496 QMainWindow::DockOptions dockOptions;
497 void setDockOptions(QMainWindow::DockOptions opts);
498
499 QLayoutItem *statusbar;
500
501 // status bar
502#if QT_CONFIG(statusbar)
503 QStatusBar *statusBar() const;
504 void setStatusBar(QStatusBar *sb);
505#endif
506
507 // central widget
508 QWidget *centralWidget() const;
509 void setCentralWidget(QWidget *cw);
510
511 // toolbars
512#if QT_CONFIG(toolbar)
513 void addToolBarBreak(Qt::ToolBarArea area);
514 void insertToolBarBreak(QToolBar *before);
515 void removeToolBarBreak(QToolBar *before);
516
517 void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar, bool needAddChildWidget = true);
518 void insertToolBar(QToolBar *before, QToolBar *toolbar);
519 Qt::ToolBarArea toolBarArea(const QToolBar *toolbar) const;
520 bool toolBarBreak(QToolBar *toolBar) const;
521 void getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const;
522 void removeToolBar(QToolBar *toolbar);
523 void toggleToolBarsVisible();
524 void moveToolBar(QToolBar *toolbar, int pos);
525 QToolBarAreaLayout *toolBarAreaLayout() { return &layoutState.toolBarAreaLayout; }
526#endif
527
528 // dock widgets
529#if QT_CONFIG(dockwidget)
530 void setCorner(Qt::Corner corner, Qt::DockWidgetArea area);
531 Qt::DockWidgetArea corner(Qt::Corner corner) const;
532 enum DockWidgetAreaSize {Visible, Maximum};
533 QRect dockWidgetAreaRect(Qt::DockWidgetArea area, DockWidgetAreaSize size = Maximum) const;
534 void addDockWidget(Qt::DockWidgetArea area,
535 QDockWidget *dockwidget,
536 Qt::Orientation orientation);
537 void splitDockWidget(QDockWidget *after,
538 QDockWidget *dockwidget,
539 Qt::Orientation orientation);
540 Qt::DockWidgetArea dockWidgetArea(const QWidget* widget) const;
541 bool restoreDockWidget(QDockWidget *dockwidget);
542#if QT_CONFIG(tabbar)
543 void tabifyDockWidget(QDockWidget *first, QDockWidget *second);
544 void raise(QDockWidget *widget);
545 void setVerticalTabsEnabled(bool enabled);
546
547 QDockAreaLayoutInfo *dockInfo(QWidget *w);
548 bool _documentMode;
549 bool documentMode() const;
550 void setDocumentMode(bool enabled);
551
552 QTabBar *getTabBar();
553 void unuseTabBar(QTabBar *bar);
554 QSet<QTabBar*> usedTabBars;
555 bool verticalTabsEnabled;
556
557 QWidget *getSeparatorWidget();
558 QSet<QWidget*> usedSeparatorWidgets;
559 int sep; // separator extent
560
561#if QT_CONFIG(tabwidget)
562 QTabWidget::TabPosition tabPositions[QInternal::DockCount];
563 QTabWidget::TabShape _tabShape;
564
565 QTabWidget::TabShape tabShape() const;
566 void setTabShape(QTabWidget::TabShape tabShape);
567 QTabWidget::TabPosition tabPosition(Qt::DockWidgetArea area) const;
568 void setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition);
569
570 QDockWidgetGroupWindow *createTabbedDockWindow();
571#endif // QT_CONFIG(tabwidget)
572#endif // QT_CONFIG(tabbar)
573
574 QDockAreaLayout *dockAreaLayoutInfo() { return &layoutState.dockAreaLayout; }
575 void keepSize(QDockWidget *w);
576#endif // QT_CONFIG(dockwidget)
577
578 // save/restore
579 enum VersionMarkers { // sentinel values used to validate state data
580 VersionMarker = 0xff
581 };
582 void saveState(QDataStream &stream) const;
583 bool restoreState(QDataStream &stream);
584 QBasicTimer discardRestoredStateTimer;
585
586 // QLayout interface
587 void addItem(QLayoutItem *item) override;
588 void setGeometry(const QRect &r) override;
589 QLayoutItem *itemAt(int index) const override;
590 QLayoutItem *takeAt(int index) override;
591 int count() const override;
592
593 QSize sizeHint() const override;
594 QSize minimumSize() const override;
595 mutable QSize szHint;
596 mutable QSize minSize;
597 void invalidate() override;
598
599 // animations
600 QWidgetAnimator widgetAnimator;
601 QList<int> currentGapPos;
602 QRect currentGapRect;
603 QWidget *pluggingWidget;
604#if QT_CONFIG(rubberband)
605 QPointer<QRubberBand> gapIndicator;
606#endif
607#if QT_CONFIG(dockwidget)
608 QPointer<QDockWidgetGroupWindow> currentHoveredFloat; // set when dragging over a floating dock widget
609 void setCurrentHoveredFloat(QDockWidgetGroupWindow *w);
610#if QT_CONFIG(tabbar)
611 bool isDockWidgetTabbed(const QDockWidget *dockWidget) const;
612 QList<QDockWidget *> tabifiedDockWidgets(const QDockWidget *dockWidget) const;
613 QMainWindowTabBar *findTabBar(const QDockWidget *dockWidget) const;
614#endif
615#endif
616 bool isInApplyState = false;
617
618 void hover(QLayoutItem *hoverTarget, const QPoint &mousePos);
619 bool plug(QLayoutItem *widgetItem);
620 QLayoutItem *unplug(QWidget *widget, QDockWidgetPrivate::DragScope scope);
621 void revert(QLayoutItem *widgetItem);
622 void applyState(QMainWindowLayoutState &newState, bool animate = true);
623 void applyRestoredState();
624 void restore(bool keepSavedState = false);
625 void animationFinished(QWidget *widget);
626
627#if QT_CONFIG(draganddrop)
628 static bool needsPlatformDrag();
629 Qt::DropAction performPlatformWidgetDrag(QLayoutItem *widgetItem, const QPoint &pressPosition);
630 QLayoutItem *draggingWidget = nullptr;
631#endif
632
633protected:
634 void timerEvent(QTimerEvent *e) override;
635
636private Q_SLOTS:
637 void updateGapIndicator();
638#if QT_CONFIG(dockwidget)
639#if QT_CONFIG(tabbar)
640 void tabChanged();
641 void tabMoved(int from, int to);
642#endif
643#endif
644private:
645#if QT_CONFIG(tabbar)
646 void updateTabBarShapes();
647#endif
648 bool isInRestoreState = false;
649};
650
651#if QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG_STREAM)
652class QDebug;
653QDebug operator<<(QDebug debug, const QDockAreaLayout &layout);
654QDebug operator<<(QDebug debug, const QMainWindowLayout *layout);
655#endif
656
657QT_END_NAMESPACE
658
659#endif // QMAINWINDOWLAYOUT_P_H
660

source code of qtbase/src/widgets/widgets/qmainwindowlayout_p.h