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
76 QT_REQUIRE_CONFIG(mainwindow);
77
78 QT_BEGIN_NAMESPACE
79
80 class QToolBar;
81 class QRubberBand;
82
83 template <typename Layout> // Make use of the "Curiously recurring template pattern"
84 class QMainWindowLayoutSeparatorHelper
85 {
layout()86 Layout *layout() { return static_cast<Layout *>(this); }
layout()87 const Layout *layout() const { return static_cast<const Layout *>(this); }
window()88 QWidget *window() { return layout()->parentWidget(); }
89
90 public:
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)
125 template <typename Layout>
separatorCursor(const QList<int> & path)126 QCursor 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
149 template <typename Layout>
adjustCursor(const QPoint & pos)150 void 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(Qt::WA_SetCursor);
189 }
190 adjustedCursor = separatorCursor(hoverSeparator);
191 w->setCursor(adjustedCursor);
192 cursorAdjusted = true;
193 }
194 }
195 }
196 }
197 #endif // QT_CONFIG(cursor)
198
199 template <typename Layout>
windowEvent(QEvent * event)200 bool 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(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(QPoint(0, 0));
223 return true;
224 case QEvent::ShortcutOverride: // when a menu pops up
225 adjustCursor(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(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(e->pos());
244 #endif
245 if (e->buttons() & Qt::LeftButton) {
246 if (separatorMove(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(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(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
305 template <typename Layout>
startSeparatorMove(const QPoint & pos)306 bool 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 }
318 template <typename Layout>
separatorMove(const QPoint & pos)319 bool 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 }
327 template <typename Layout>
endSeparatorMove(const QPoint &)328 bool 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
337 class QDockWidgetGroupWindow : public QWidget
338 {
339 Q_OBJECT
340 public:
341 explicit QDockWidgetGroupWindow(QWidget* parent = nullptr, Qt::WindowFlags f = { })
QWidget(parent,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
360 signals:
361 void resized();
362
363 protected:
364 bool event(QEvent *) override;
365 void paintEvent(QPaintEvent*) override;
366
367 private:
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.
373 class QDockWidgetGroupWindowItem : public QWidgetItem
374 {
375 public:
QDockWidgetGroupWindowItem(QDockWidgetGroupWindow * parent)376 explicit QDockWidgetGroupWindowItem(QDockWidgetGroupWindow *parent) : QWidgetItem(parent) {}
minimumSize()377 QSize minimumSize() const override { return lay()->minimumSize(); }
maximumSize()378 QSize maximumSize() const override { return lay()->maximumSize(); }
sizeHint()379 QSize sizeHint() const override { return lay()->sizeHint(); }
380
381 private:
lay()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
392 class QMainWindowLayoutState
393 {
394 public:
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
446 class Q_AUTOTEST_EXPORT QMainWindowLayout
447 : public QLayout,
448 public QMainWindowLayoutSeparatorHelper<QMainWindowLayout>
449 {
450 Q_OBJECT
451
452 public:
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
dockAreaLayoutInfo()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
587 private 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
595 private:
596 #if QT_CONFIG(tabbar)
597 void updateTabBarShapes();
598 #endif
599 };
600
601 #if QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG_STREAM)
602 class QDebug;
603 QDebug operator<<(QDebug debug, const QDockAreaLayout &layout);
604 QDebug operator<<(QDebug debug, const QMainWindowLayout *layout);
605 #endif
606
607 QT_END_NAMESPACE
608
609 #endif // QDYNAMICMAINWINDOWLAYOUT_P_H
610