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

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