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