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