diff options
Diffstat (limited to 'src/widgets')
-rw-r--r-- | src/widgets/widgets/qdockarealayout.cpp | 82 | ||||
-rw-r--r-- | src/widgets/widgets/qdockarealayout_p.h | 4 | ||||
-rw-r--r-- | src/widgets/widgets/qdockwidget.cpp | 153 | ||||
-rw-r--r-- | src/widgets/widgets/qdockwidget_p.h | 3 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindow.cpp | 7 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindow.h | 3 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindowlayout.cpp | 519 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindowlayout_p.h | 24 |
8 files changed, 705 insertions, 90 deletions
diff --git a/src/widgets/widgets/qdockarealayout.cpp b/src/widgets/widgets/qdockarealayout.cpp index af0f2515fe..bc2259ce90 100644 --- a/src/widgets/widgets/qdockarealayout.cpp +++ b/src/widgets/widgets/qdockarealayout.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com> ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -1854,20 +1855,6 @@ static Qt::DockWidgetArea toDockWidgetArea(QInternal::DockPosition pos) return Qt::NoDockWidgetArea; } -static QRect constrainedRect(QRect rect, const QRect &desktop) -{ - if (desktop.isValid()) { - rect.setWidth(qMin(rect.width(), desktop.width())); - rect.setHeight(qMin(rect.height(), desktop.height())); - rect.moveLeft(qMax(rect.left(), desktop.left())); - rect.moveTop(qMax(rect.top(), desktop.top())); - rect.moveRight(qMin(rect.right(), desktop.right())); - rect.moveBottom(qMin(rect.bottom(), desktop.bottom())); - } - - return rect; -} - bool QDockAreaLayoutInfo::restoreState(QDataStream &stream, QList<QDockWidget*> &widgets, bool testing) { uchar marker; @@ -1958,11 +1945,7 @@ bool QDockAreaLayoutInfo::restoreState(QDataStream &stream, QList<QDockWidget*> #endif if (!testing) { QRect r(x, y, w, h); - QDesktopWidget *desktop = QApplication::desktop(); - if (desktop->isVirtualDesktop()) - r = constrainedRect(r, desktop->screenGeometry(desktop->screenNumber(r.topLeft()))); - else - r = constrainedRect(r, desktop->screenGeometry(widget)); + r = QDockAreaLayout::constrainedRect(r, widget); widget->move(r.topLeft()); widget->resize(r.size()); } @@ -2075,6 +2058,36 @@ void QDockAreaLayoutInfo::updateSeparatorWidgets() const #endif //QT_NO_TABBAR #ifndef QT_NO_TABBAR +/*! \internal + reparent all the widgets contained in this layout portion to the + specified parent. This is used to reparent dock widgets and tabbars + to the floating window or the main window + */ +void QDockAreaLayoutInfo::reparentWidgets(QWidget *parent) +{ + if (tabBar) + tabBar->setParent(parent); + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.flags & QDockAreaLayoutItem::GapItem) + continue; + if (item.skip()) + continue; + if (item.subinfo) + item.subinfo->reparentWidgets(parent); + if (item.widgetItem) { + QWidget *w = item.widgetItem->widget(); + if (w->parent() != parent) { + bool hidden = w->isHidden(); + w->setParent(parent); + if (!hidden) + w->show(); + } + } + } +} + //returns whether the tabbar is visible or not bool QDockAreaLayoutInfo::updateTabBar() const { @@ -2981,6 +2994,33 @@ QSize QDockAreaLayout::minimumSize() const return QSize(qMax(row1, row2, row3), qMax(col1, col2, col3)); } +/*! \internal + Try to fit the given rectangle \a rect on the screen which contains + the window \a widget. + Used to compute the geometry of a dragged a dock widget that should + be shown with \a rect, but needs to be visible on the screen + */ +QRect QDockAreaLayout::constrainedRect(QRect rect, QWidget* widget) +{ + QRect desktop; + QDesktopWidget *desktopW = QApplication::desktop(); + if (desktopW->isVirtualDesktop()) + desktop = desktopW->screenGeometry(rect.topLeft()); + else + desktop = desktopW->screenGeometry(widget); + + if (desktop.isValid()) { + rect.setWidth(qMin(rect.width(), desktop.width())); + rect.setHeight(qMin(rect.height(), desktop.height())); + rect.moveLeft(qMax(rect.left(), desktop.left())); + rect.moveTop(qMax(rect.top(), desktop.top())); + rect.moveRight(qMin(rect.right(), desktop.right())); + rect.moveBottom(qMin(rect.bottom(), desktop.bottom())); + } + + return rect; +} + bool QDockAreaLayout::restoreDockWidget(QDockWidget *dockWidget) { QList<int> index = indexOfPlaceHolder(dockWidget->objectName()); @@ -2994,9 +3034,7 @@ bool QDockAreaLayout::restoreDockWidget(QDockWidget *dockWidget) item.widgetItem = new QDockWidgetItem(dockWidget); if (placeHolder->window) { - const QRect screenGeometry = - QApplication::desktop()->screenGeometry(placeHolder->topLevelRect.center()); - const QRect r = constrainedRect(placeHolder->topLevelRect, screenGeometry); + const QRect r = constrainedRect(placeHolder->topLevelRect, dockWidget); dockWidget->d_func()->setWindowState(true, true, r); } dockWidget->setVisible(!placeHolder->hidden); diff --git a/src/widgets/widgets/qdockarealayout_p.h b/src/widgets/widgets/qdockarealayout_p.h index 64fae7ebf6..93b005f64f 100644 --- a/src/widgets/widgets/qdockarealayout_p.h +++ b/src/widgets/widgets/qdockarealayout_p.h @@ -202,6 +202,7 @@ public: QTabBar *tabBar; int tabBarShape; + void reparentWidgets(QWidget *p); bool updateTabBar() const; void setTabBarShape(int shape); QSize tabBarMinimumSize() const; @@ -232,7 +233,8 @@ public: bool isValid() const; - enum { DockWidgetStateMarker = 0xfd }; + enum { DockWidgetStateMarker = 0xfd, FloatingDockWidgetTabMarker = 0xf9 }; + static QRect constrainedRect(QRect rect, QWidget *widget); void saveState(QDataStream &stream) const; bool restoreState(QDataStream &stream, const QList<QDockWidget*> &widgets, bool testing = false); diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp index 21b0904cc0..1b7473fbd7 100644 --- a/src/widgets/widgets/qdockwidget.cpp +++ b/src/widgets/widgets/qdockwidget.cpp @@ -65,6 +65,18 @@ extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); / // qmainwindow.cpp extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); +static inline QMainWindowLayout *qt_mainwindow_layout_from_dock(const QDockWidget *dock) +{ + const QWidget *p = dock->parentWidget(); + while (p) { + const QMainWindow *window = qobject_cast<const QMainWindow*>(p); + if (window) + return qt_mainwindow_layout(window); + p = p->parentWidget(); + } + return Q_NULLPTR; +} + static inline bool hasFeature(const QDockWidgetPrivate *priv, QDockWidget::DockWidgetFeature feature) { return (priv->features & feature) == feature; } @@ -194,25 +206,40 @@ QDockWidgetLayout::~QDockWidgetLayout() qDeleteAll(item_list); } +/*! \internal + Returns true if the dock widget managed by this layout should have a native + window decoration or if Qt needs to draw it. + */ bool QDockWidgetLayout::nativeWindowDeco() const { - return nativeWindowDeco(parentWidget()->isWindow()); + bool floating = parentWidget()->isWindow(); + if (!floating && qobject_cast<QDockWidgetGroupWindow*>(parentWidget()->parentWidget())) + return wmSupportsNativeWindowDeco(); + return nativeWindowDeco(floating); } -static bool isXcb() +/*! \internal + Returns true if the window manager can draw natively the windows decoration + of a dock widget + */ +bool QDockWidgetLayout::wmSupportsNativeWindowDeco() { +#if defined(Q_OS_WINCE) || defined(Q_OS_ANDROID) + return false; +#else static const bool xcb = !QGuiApplication::platformName().compare(QLatin1String("xcb"), Qt::CaseInsensitive); - return xcb; + return !xcb; +#endif } +/*! \internal + Returns true if the dock widget managed by this layout should have a native + window decoration or if Qt needs to draw it. The \a floating parameter + overrides the floating current state of the dock widget. + */ bool QDockWidgetLayout::nativeWindowDeco(bool floating) const { -#if defined(Q_OS_WINCE) || defined(Q_OS_ANDROID) - Q_UNUSED(floating) - return false; -#else - return !isXcb() && (floating && item_list[QDockWidgetLayout::TitleBar] == 0); -#endif + return wmSupportsNativeWindowDeco() && floating && item_list.at(QDockWidgetLayout::TitleBar) == 0; } @@ -611,7 +638,10 @@ void QDockWidget::initStyleOption(QStyleOptionDockWidget *option) const return; QDockWidgetLayout *dwlayout = qobject_cast<QDockWidgetLayout*>(layout()); - option->initFrom(this); + QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(parent()); + // If we are in a floating tab, init from the parent because the attributes and the geometry + // of the title bar should be taken from the floating window. + option->initFrom(floatingTab && !isFloating() ? parentWidget() : this); option->rect = dwlayout->titleArea(); option->title = d->fixedWindowTitle; option->closable = hasFeature(this, QDockWidget::DockWidgetClosable); @@ -681,14 +711,20 @@ void QDockWidgetPrivate::_q_toggleTopLevel() q->setFloating(!q->isFloating()); } +/*! \internal + Initialize the drag state structure and remember the position of the click. + This is called when the mouse is pressed, but the dock is not yet dragged out. + + \a nca specify that the event comes from NonClientAreaMouseButtonPress + */ void QDockWidgetPrivate::initDrag(const QPoint &pos, bool nca) { + Q_Q(QDockWidget); + if (state != 0) return; - QMainWindow *win = qobject_cast<QMainWindow*>(parent); - Q_ASSERT(win != 0); - QMainWindowLayout *layout = qt_mainwindow_layout(win); + QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(q); Q_ASSERT(layout != 0); if (layout->pluggingWidget != 0) // the main window is animating a docking operation return; @@ -702,17 +738,23 @@ void QDockWidgetPrivate::initDrag(const QPoint &pos, bool nca) state->ctrlDrag = false; } -void QDockWidgetPrivate::startDrag() +/*! \internal + Actually start the drag and detach the dockwidget. + The \a group parameter is true when we should potentially drag a group of + tabbed widgets, and false if the dock widget should always be dragged + alone. + */ +void QDockWidgetPrivate::startDrag(bool group) { Q_Q(QDockWidget); if (state == 0 || state->dragging) return; - QMainWindowLayout *layout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); + QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(q); Q_ASSERT(layout != 0); - state->widgetItem = layout->unplug(q); + state->widgetItem = layout->unplug(q, group); if (state->widgetItem == 0) { /* I have a QMainWindow parent, but I was never inserted with QMainWindow::addDockWidget, so the QMainWindowLayout has no @@ -728,6 +770,11 @@ void QDockWidgetPrivate::startDrag() state->dragging = true; } +/*! \internal + Ends the drag end drop operation of the QDockWidget. + The \a abort parameter specifies that it ends because of programmatic state + reset rather than mouse release event. + */ void QDockWidgetPrivate::endDrag(bool abort) { Q_Q(QDockWidget); @@ -736,29 +783,31 @@ void QDockWidgetPrivate::endDrag(bool abort) q->releaseMouse(); if (state->dragging) { - QMainWindowLayout *mwLayout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); + QMainWindowLayout *mwLayout = qt_mainwindow_layout_from_dock(q); Q_ASSERT(mwLayout != 0); if (abort || !mwLayout->plug(state->widgetItem)) { if (hasFeature(this, QDockWidget::DockWidgetFloatable)) { + // This QDockWidget will now stay in the floating state. if (state->ownWidgetItem) delete state->widgetItem; mwLayout->restore(); - if (isXcb()) { + QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(layout); + if (!dwLayout->nativeWindowDeco()) { // get rid of the X11BypassWindowManager window flag and activate the resizer Qt::WindowFlags flags = q->windowFlags(); flags &= ~Qt::X11BypassWindowManagerHint; q->setWindowFlags(flags); - setResizerActive(true); + setResizerActive(q->isFloating()); q->show(); } else { - QDockWidgetLayout *myLayout - = qobject_cast<QDockWidgetLayout*>(layout); - setResizerActive(myLayout->widgetForRole(QDockWidgetLayout::TitleBar) != 0); + setResizerActive(false); } undockedGeometry = q->geometry(); q->activateWindow(); } else { + // The tab was not plugged back in the QMainWindow but the QDockWidget cannot + // stay floating, revert to the previous state. mwLayout->revert(state->widgetItem); } } @@ -782,11 +831,7 @@ bool QDockWidgetPrivate::isAnimating() const { Q_Q(const QDockWidget); - QMainWindow *mainWin = qobject_cast<QMainWindow*>(parent); - if (mainWin == 0) - return false; - - QMainWindowLayout *mainWinLayout = qt_mainwindow_layout(mainWin); + QMainWindowLayout *mainWinLayout = qt_mainwindow_layout_from_dock(q); if (mainWinLayout == 0) return false; @@ -804,12 +849,14 @@ bool QDockWidgetPrivate::mousePressEvent(QMouseEvent *event) if (!dwLayout->nativeWindowDeco()) { QRect titleArea = dwLayout->titleArea(); + QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(parent); + if (event->button() != Qt::LeftButton || !titleArea.contains(event->pos()) || // check if the tool window is movable... do nothing if it // is not (but allow moving if the window is floating) (!hasFeature(this, QDockWidget::DockWidgetMovable) && !q->isFloating()) || - qobject_cast<QMainWindow*>(parent) == 0 || + (qobject_cast<QMainWindow*>(parent) == 0 && !floatingTab) || isAnimating() || state != 0) { return false; } @@ -853,7 +900,7 @@ bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event) QDockWidgetLayout *dwlayout = qobject_cast<QDockWidgetLayout *>(layout); - QMainWindowLayout *mwlayout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); + QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q); if (!dwlayout->nativeWindowDeco()) { if (!state->dragging && mwlayout->pluggingWidget == 0 @@ -871,7 +918,12 @@ bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event) if (state->dragging && !state->nca) { QPoint pos = event->globalPos() - state->pressPos; - q->move(pos); + + QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(parent); + if (floatingTab && !q->isFloating()) + floatingTab->move(pos); + else + q->move(pos); if (state && !state->ctrlDrag) mwlayout->hover(state->widgetItem, event->globalPos()); @@ -902,8 +954,9 @@ void QDockWidgetPrivate::nonClientAreaMouseEvent(QMouseEvent *event) int fw = q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q); - QRect geo = q->geometry(); - QRect titleRect = q->frameGeometry(); + QWidget *tl = q->topLevelWidget(); + QRect geo = tl->geometry(); + QRect titleRect = tl->frameGeometry(); #ifdef Q_DEAD_CODE_FROM_QT4_MAC if ((features & QDockWidget::DockWidgetVerticalTitleBar)) { titleRect.setTop(geo.top()); @@ -924,7 +977,7 @@ void QDockWidgetPrivate::nonClientAreaMouseEvent(QMouseEvent *event) break; if (state != 0) break; - if (qobject_cast<QMainWindow*>(parent) == 0) + if (qobject_cast<QMainWindow*>(parent) == 0 && qobject_cast<QDockWidgetGroupWindow*>(parent) == 0) break; if (isAnimating()) break; @@ -958,11 +1011,17 @@ void QDockWidgetPrivate::nonClientAreaMouseEvent(QMouseEvent *event) } } +/*! \internal + Called when the QDockWidget or the QDockWidgetGroupWindow is moved + */ void QDockWidgetPrivate::moveEvent(QMoveEvent *event) { Q_Q(QDockWidget); - if (state == 0 || !state->dragging || !state->nca || !q->isWindow()) + if (state == 0 || !state->dragging || !state->nca) + return; + + if (!q->isWindow() && qobject_cast<QDockWidgetGroupWindow*>(parent) == 0) return; // When the native window frame is being dragged, all we get is these mouse @@ -971,7 +1030,7 @@ void QDockWidgetPrivate::moveEvent(QMoveEvent *event) if (state->ctrlDrag) return; - QMainWindowLayout *layout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); + QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(q); Q_ASSERT(layout != 0); QPoint globalMousePos = event->pos() + state->pressPos; @@ -999,8 +1058,9 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect Q_Q(QDockWidget); if (!floating && parent) { - QMainWindowLayout *mwlayout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); - if (mwlayout && mwlayout->dockWidgetArea(q) == Qt::NoDockWidgetArea) + QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q); + if (mwlayout && mwlayout->dockWidgetArea(q) == Qt::NoDockWidgetArea + && !qobject_cast<QDockWidgetGroupWindow *>(parent)) return; // this dockwidget can't be redocked } @@ -1048,7 +1108,7 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect if (floating != wasFloating) { emit q->topLevelChanged(floating); if (!floating && parent) { - QMainWindowLayout *mwlayout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q->parentWidget())); + QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q); if (mwlayout) emit q->dockLocationChanged(mwlayout->dockWidgetArea(q)); } @@ -1230,8 +1290,11 @@ void QDockWidget::setFeatures(QDockWidget::DockWidgetFeatures features) emit featuresChanged(d->features); update(); if (closableChanged && layout->nativeWindowDeco()) { - //this ensures the native decoration is drawn - d->setWindowState(true /*floating*/, true /*unplug*/); + QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow *>(parent()); + if (floatingTab && !isFloating()) + floatingTab->adjustFlags(); + else + d->setWindowState(true /*floating*/, true /*unplug*/); //this ensures the native decoration is drawn } } @@ -1323,11 +1386,12 @@ void QDockWidget::changeEvent(QEvent *event) #endif #ifndef QT_NO_TABBAR { - QMainWindow *win = qobject_cast<QMainWindow*>(parentWidget()); - if (QMainWindowLayout *winLayout = qt_mainwindow_layout(win)) { + if (QMainWindowLayout *winLayout = qt_mainwindow_layout_from_dock(this)) { if (QDockAreaLayoutInfo *info = winLayout->layoutState.dockAreaLayout.info(this)) info->updateTabBar(); } + if (QDockWidgetGroupWindow *p = qobject_cast<QDockWidgetGroupWindow *>(parent())) + p->adjustFlags(); } #endif // QT_NO_TABBAR break; @@ -1380,7 +1444,7 @@ bool QDockWidget::event(QEvent *event) Q_D(QDockWidget); QMainWindow *win = qobject_cast<QMainWindow*>(parentWidget()); - QMainWindowLayout *layout = qt_mainwindow_layout(win); + QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(this); switch (event->type()) { #ifndef QT_NO_ACTION @@ -1417,6 +1481,9 @@ bool QDockWidget::event(QEvent *event) } if (!isFloating() && layout != 0 && onTop) layout->raise(this); + if (QDockWidgetGroupWindow *p = qobject_cast<QDockWidgetGroupWindow *>(parent())) + p->adjustFlags(); + break; } case QEvent::WindowActivate: diff --git a/src/widgets/widgets/qdockwidget_p.h b/src/widgets/widgets/qdockwidget_p.h index 752d6519e5..9c68bc5fb1 100644 --- a/src/widgets/widgets/qdockwidget_p.h +++ b/src/widgets/widgets/qdockwidget_p.h @@ -108,7 +108,7 @@ public: void setWindowState(bool floating, bool unplug = false, const QRect &rect = QRect()); void nonClientAreaMouseEvent(QMouseEvent *event); void initDrag(const QPoint &pos, bool nca); - void startDrag(); + void startDrag(bool group = true); void endDrag(bool abort = false); void moveEvent(QMoveEvent *event); @@ -151,6 +151,7 @@ public: int minimumTitleWidth() const; int titleHeight() const; void updateMaxSize(); + static bool wmSupportsNativeWindowDeco(); bool nativeWindowDeco() const; bool nativeWindowDeco(bool floating) const; diff --git a/src/widgets/widgets/qmainwindow.cpp b/src/widgets/widgets/qmainwindow.cpp index 2a1e8428ab..8eb9050528 100644 --- a/src/widgets/widgets/qmainwindow.cpp +++ b/src/widgets/widgets/qmainwindow.cpp @@ -420,6 +420,13 @@ QMainWindow::~QMainWindow() at the bottom. Implies AllowTabbedDocks. See also \l setTabPosition(). + \value GroupedDragging When dragging the titlebar of a dock, all the tabs + that are tabbed with it are going to be dragged. + Implies AllowTabbedDocks. Does not work well if + some QDockWidgets have restrictions in which area + they are allowed. (This enum value was added in Qt + 5.6.) + These options only control how dock widgets may be dropped in a QMainWindow. They do not re-arrange the dock widgets to conform with the specified options. For this reason they should be set before any dock widgets diff --git a/src/widgets/widgets/qmainwindow.h b/src/widgets/widgets/qmainwindow.h index cbbea74b9b..0bd70c87a6 100644 --- a/src/widgets/widgets/qmainwindow.h +++ b/src/widgets/widgets/qmainwindow.h @@ -77,7 +77,8 @@ public: AllowNestedDocks = 0x02, AllowTabbedDocks = 0x04, ForceTabbedDocks = 0x08, // implies AllowTabbedDocks, !AllowNestedDocks - VerticalTabs = 0x10 // implies AllowTabbedDocks + VerticalTabs = 0x10, // implies AllowTabbedDocks + GroupedDragging = 0x20 // implies AllowTabbedDocks }; Q_ENUM(DockOption) Q_DECLARE_FLAGS(DockOptions, DockOption) diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 3620aa04fc..d41f3bad19 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com> ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -49,6 +50,7 @@ #include <qstatusbar.h> #include <qstring.h> #include <qstyle.h> +#include <qstylepainter.h> #include <qvarlengtharray.h> #include <qstack.h> #include <qmap.h> @@ -61,6 +63,7 @@ #include <private/qapplication_p.h> #include <private/qlayoutengine_p.h> +#include <private/qwidgetresizehandler_p.h> #ifdef Q_DEAD_CODE_FROM_QT4_MAC # include <private/qcore_mac_p.h> # include <private/qt_cocoa_helpers_mac_p.h> @@ -68,9 +71,7 @@ QT_BEGIN_NAMESPACE -#ifdef QT_NO_DOCKWIDGET extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); -#endif /****************************************************************************** ** debug @@ -165,6 +166,242 @@ QDebug operator<<(QDebug debug, const QMainWindowLayout *layout) #endif // !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_DEBUG) /****************************************************************************** + ** QDockWidgetGroupWindow + */ +// QDockWidgetGroupWindow is the floating window containing the tabbed dockwidgets in case several +// dockwidgets are dragged together (QMainWindow::GroupedDragging feature). +// QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout +// the tabs inside it. +#ifndef QT_NO_DOCKWIDGET +class QDockWidgetGroupLayout : public QLayout { + QDockAreaLayoutInfo info; + QWidgetResizeHandler *resizer; +public: + QDockWidgetGroupLayout(QWidget* parent) : QLayout(parent) { + setSizeConstraint(QLayout::SetMinAndMaxSize); + resizer = new QWidgetResizeHandler(parent); + resizer->setMovingEnabled(false); + } + ~QDockWidgetGroupLayout() { + info.deleteAllLayoutItems(); + } + + void addItem(QLayoutItem*) Q_DECL_OVERRIDE { Q_UNREACHABLE(); } + int count() const Q_DECL_OVERRIDE { return 0; } + QLayoutItem* itemAt(int index) const Q_DECL_OVERRIDE + { + int x = 0; + return info.itemAt(&x, index); + } + QLayoutItem* takeAt(int index) Q_DECL_OVERRIDE + { + int x = 0; + return info.takeAt(&x, index); + } + QSize sizeHint() const Q_DECL_OVERRIDE + { + int fw = frameWidth(); + return info.sizeHint() + QSize(fw, fw); + } + QSize minimumSize() const Q_DECL_OVERRIDE + { + int fw = frameWidth(); + return info.minimumSize() + QSize(fw, fw); + } + QSize maximumSize() const Q_DECL_OVERRIDE + { + int fw = frameWidth(); + return info.maximumSize() + QSize(fw, fw); + } + void setGeometry(const QRect&r) Q_DECL_OVERRIDE + { + QDockAreaLayoutInfo *li = layoutInfo(); + if (li->isEmpty()) { + static_cast<QDockWidgetGroupWindow *>(parent())->destroyIfEmpty(); + return; + } + int fw = frameWidth(); + li->reparentWidgets(parentWidget()); + li->rect = r.adjusted(fw, fw, -fw, -fw); + li->fitItems(); + li->apply(false); + resizer->setActive(QWidgetResizeHandler::Resize, !nativeWindowDeco()); + } + + QDockAreaLayoutInfo *layoutInfo() { + return &info; + } + + bool nativeWindowDeco() const + { + return QDockWidgetLayout::wmSupportsNativeWindowDeco(); + } + + int frameWidth() const + { + return nativeWindowDeco() ? 0 : + parentWidget()->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, parentWidget()); + } +}; + +// This item will be used in the layout for the gap item. We cannot use QWidgetItem directly +// because QWidgetItem functions return an empty size for widgets are are floating. +class QDockWidgetGroupWindowItem : public QWidgetItem +{ +public: + QDockWidgetGroupWindowItem(QDockWidgetGroupWindow *parent) : QWidgetItem(parent) {} + QSize minimumSize() const Q_DECL_OVERRIDE { return lay()->minimumSize(); } + QSize maximumSize() const Q_DECL_OVERRIDE { return lay()->maximumSize(); } + QSize sizeHint() const Q_DECL_OVERRIDE { return lay()->sizeHint(); } + +private: + QLayout *lay() const { return const_cast<QDockWidgetGroupWindowItem *>(this)->widget()->layout(); } +}; + +bool QDockWidgetGroupWindow::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::Close: + // Forward the close to the QDockWidget just as if its close button was pressed + if (QDockWidget *dw = topDockWidget()) { + e->ignore(); + dw->close(); + adjustFlags(); + } + return true; + case QEvent::Move: + // Let QDockWidgetPrivate::moseEvent handle the dragging + if (QDockWidget *dw = topDockWidget()) + static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->moveEvent(static_cast<QMoveEvent*>(e)); + return true; + case QEvent::NonClientAreaMouseMove: + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::NonClientAreaMouseButtonRelease: + case QEvent::NonClientAreaMouseButtonDblClick: + // Let the QDockWidgetPrivate of the currently visible dock widget handle the drag and drop + if (QDockWidget *dw = topDockWidget()) + static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(e)); + return true; + case QEvent::ChildAdded: + if (qobject_cast<QDockWidget *>(static_cast<QChildEvent*>(e)->child())) + adjustFlags(); + break; + default: + break; + } + return QWidget::event(e); +} + +void QDockWidgetGroupWindow::paintEvent(QPaintEvent *) +{ + QDockWidgetGroupLayout *lay = static_cast<QDockWidgetGroupLayout *>(layout()); + bool nativeDeco = lay->nativeWindowDeco(); + + if (!nativeDeco) { + QStyleOptionFrame framOpt; + framOpt.init(this); + QStylePainter p(this); + p.drawPrimitive(QStyle::PE_FrameDockWidget, framOpt); + } +} + +QDockAreaLayoutInfo *QDockWidgetGroupWindow::layoutInfo() const +{ + return static_cast<QDockWidgetGroupLayout*>(layout())->layoutInfo(); +} + +/*! \internal + \returns the currently active QDockWidget + */ +QDockWidget *QDockWidgetGroupWindow::topDockWidget() const +{ + QDockAreaLayoutInfo *info = layoutInfo(); + QDockWidget *dw = 0; + if (info->tabBar && info->tabBar->currentIndex() >= 0) { + int i = info->tabIndexToListIndex(info->tabBar->currentIndex()); + if (i >= 0) { + const QDockAreaLayoutItem &item = info->item_list.at(i); + if (item.widgetItem) + dw = qobject_cast<QDockWidget *>(item.widgetItem->widget()); + } + } + if (!dw) { + for (int i = 0; !dw && i < info->item_list.count(); ++i) { + const QDockAreaLayoutItem &item = info->item_list.at(i); + if (item.skip()) + continue; + if (!item.widgetItem) + continue; + dw = qobject_cast<QDockWidget *>(item.widgetItem->widget()); + } + } + return dw; +} + +/*! \internal + Destroy this window if there is no more QDockWidget in it. + */ +void QDockWidgetGroupWindow::destroyIfEmpty() +{ + if (layoutInfo()->isEmpty()) { + // Make sure to reparent the possibly floating or hidden QDockWidgets to the parent + foreach (QDockWidget *dw, + findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly)) { + bool wasFloating = dw->isFloating(); + bool wasHidden = dw->isHidden(); + dw->setParent(parentWidget()); + if (wasFloating) { + dw->setFloating(true); + } else { + // maybe it was hidden, we still have to put it back in the main layout. + QMainWindowLayout *ml = qt_mainwindow_layout(static_cast<QMainWindow*>(parentWidget())); + Qt::DockWidgetArea area = ml->dockWidgetArea(this); + if (area == Qt::NoDockWidgetArea) + area = Qt::LeftDockWidgetArea; + static_cast<QMainWindow*>(parentWidget())->addDockWidget(area, dw); + } + if (!wasHidden) + dw->show(); + } + foreach (QTabBar *tb, findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly)) + tb->setParent(parentWidget()); + deleteLater(); + } +} + +/*! \internal + Sets the flags of this window in accordence to the capabilities of the dock widgets + */ +void QDockWidgetGroupWindow::adjustFlags() +{ + QDockWidget *top = topDockWidget(); + if (!top) + return; + const bool nativeDeco = static_cast<QDockWidgetGroupLayout *>(layout())->nativeWindowDeco(); + + Qt::WindowFlags oldFlags = windowFlags(); + Qt::WindowFlags flags = oldFlags; + if (nativeDeco) { + flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint; + if (top->features() & QDockWidget::DockWidgetClosable) + flags |= Qt::WindowCloseButtonHint; + else + flags &= ~Qt::WindowCloseButtonHint; + flags &= ~Qt::FramelessWindowHint; + } else { + flags |= Qt::FramelessWindowHint; + } + if (oldFlags != flags) { + setWindowFlags(flags); + show(); // setWindowFlags hides the window + } + + setWindowTitle(top->windowTitle()); + setWindowIcon(top->windowIcon()); +} +#endif + +/****************************************************************************** ** QMainWindowLayoutState */ @@ -335,8 +572,8 @@ QList<int> QMainWindowLayoutState::indexOf(QWidget *widget) const #ifndef QT_NO_DOCKWIDGET // is it a dock widget? - if (QDockWidget *dockWidget = qobject_cast<QDockWidget *>(widget)) { - result = dockAreaLayout.indexOf(dockWidget); + if (qobject_cast<QDockWidget *>(widget) || qobject_cast<QDockWidgetGroupWindow *>(widget)) { + result = dockAreaLayout.indexOf(widget); if (!result.isEmpty()) result.prepend(1); return result; @@ -413,7 +650,8 @@ QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget, #ifndef QT_NO_DOCKWIDGET // is it a dock widget? - if (qobject_cast<QDockWidget *>(widget) != 0) { + if (qobject_cast<QDockWidget *>(widget) != 0 + || qobject_cast<QDockWidgetGroupWindow *>(widget)) { result = dockAreaLayout.gapIndex(pos); if (!result.isEmpty()) result.prepend(1); @@ -440,7 +678,7 @@ bool QMainWindowLayoutState::insertGap(const QList<int> &path, QLayoutItem *item #ifndef QT_NO_DOCKWIDGET if (i == 1) { - Q_ASSERT(qobject_cast<QDockWidget*>(item->widget()) != 0); + Q_ASSERT(qobject_cast<QDockWidget*>(item->widget()) || qobject_cast<QDockWidgetGroupWindow*>(item->widget())); return dockAreaLayout.insertGap(path.mid(1), item); } #endif //QT_NO_DOCKWIDGET @@ -593,6 +831,17 @@ void QMainWindowLayoutState::saveState(QDataStream &stream) const { #ifndef QT_NO_DOCKWIDGET dockAreaLayout.saveState(stream); +#ifndef QT_NO_TABBAR + QList<QDockWidgetGroupWindow *> floatingTabs = + mainWindow->findChildren<QDockWidgetGroupWindow *>(QString(), Qt::FindDirectChildrenOnly); + + foreach (QDockWidgetGroupWindow *floating, floatingTabs) { + if (floating->layoutInfo()->isEmpty()) + continue; + stream << uchar(QDockAreaLayout::FloatingDockWidgetTabMarker) << floating->geometry(); + floating->layoutInfo()->saveState(stream); + } +#endif #endif #ifndef QT_NO_TOOLBAR toolBarAreaLayout.saveState(stream); @@ -638,12 +887,28 @@ bool QMainWindowLayoutState::checkFormat(QDataStream &stream) case QDockAreaLayout::DockWidgetStateMarker: { QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper<QDockWidgetGroupWindow*>(mainWindow)) + dockWidgets += findChildrenHelper<QDockWidget*>(floating); if (!dockAreaLayout.restoreState(stream, dockWidgets, true /*testing*/)) { return false; } } break; -#endif +#ifndef QT_NO_TABBAR + case QDockAreaLayout::FloatingDockWidgetTabMarker: + { + QRect geom; + stream >> geom; + QDockAreaLayoutInfo info; + QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper<QDockWidgetGroupWindow*>(mainWindow)) + dockWidgets += findChildrenHelper<QDockWidget*>(floating); + if (!info.restoreState(stream, dockWidgets, true /* testing*/)) + return false; + } + break; +#endif // QT_NO_TABBAR +#endif // QT_NO_DOCKWIDGET default: //there was an error during the parsing return false; @@ -682,6 +947,8 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream, case QDockAreaLayout::DockWidgetStateMarker: { QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper<QDockWidgetGroupWindow*>(mainWindow)) + dockWidgets += findChildrenHelper<QDockWidget*>(floating); if (!dockAreaLayout.restoreState(stream, dockWidgets)) return false; @@ -702,6 +969,26 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream, } } break; +#ifndef QT_NO_TABBAR + case QDockAreaLayout::FloatingDockWidgetTabMarker: + { + QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper<QDockWidgetGroupWindow*>(mainWindow)) + dockWidgets += findChildrenHelper<QDockWidget*>(floating); + QDockWidgetGroupWindow* floatingTab = qt_mainwindow_layout(mainWindow)->createTabbedDockWindow(); + *floatingTab->layoutInfo() = QDockAreaLayoutInfo(&dockAreaLayout.sep, QInternal::LeftDock, + Qt::Horizontal, QTabBar::RoundedSouth, mainWindow); + QRect geometry; + stream >> geometry; + if (!floatingTab->layoutInfo()->restoreState(stream, dockWidgets, false)) + return false; + geometry = QDockAreaLayout::constrainedRect(geometry, floatingTab); + floatingTab->move(geometry.topLeft()); + floatingTab->resize(geometry.size()); + floatingTab->show(); + } + break; +#endif // QT_NO_TABBAR #endif // QT_NO_DOCKWIDGET #ifndef QT_NO_TOOLBAR @@ -1223,7 +1510,7 @@ void QMainWindowLayout::splitDockWidget(QDockWidget *after, invalidate(); } -Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(QDockWidget *widget) const +Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(QWidget *widget) const { QList<int> pathToWidget = layoutState.dockAreaLayout.indexOf(widget); if (pathToWidget.isEmpty()) @@ -1238,20 +1525,88 @@ void QMainWindowLayout::keepSize(QDockWidget *w) #ifndef QT_NO_TABBAR +// Handle custom tooltip, and allow to drag tabs away. class QMainWindowTabBar : public QTabBar { + QMainWindow *mainWindow; + QDockWidget *draggingDock; // Currently dragging (detached) dock widget public: - QMainWindowTabBar(QWidget *parent); + QMainWindowTabBar(QMainWindow *parent); protected: bool event(QEvent *e) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent*) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent*) Q_DECL_OVERRIDE; + }; -QMainWindowTabBar::QMainWindowTabBar(QWidget *parent) - : QTabBar(parent) +QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent) + : QTabBar(parent), mainWindow(parent), draggingDock(0) { setExpanding(false); } +void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) +{ + // The QTabBar handles the moving (reordering) of tabs. + // When QTabBarPrivate::dragInProgress is true, and that the mouse is outside of a region + // around the QTabBar, we will consider the user wants to drag that QDockWidget away from this + // tab area. + + QTabBarPrivate *d = static_cast<QTabBarPrivate*>(d_ptr.data()); + if (!draggingDock && (mainWindow->dockOptions() & QMainWindow::GroupedDragging)) { + int offset = QApplication::startDragDistance() + 1; + offset *= 3; + QRect r = rect().adjusted(-offset, -offset, offset, offset); + if (d->dragInProgress && !r.contains(e->pos()) && d->validIndex(d->pressedIndex)) { + QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow); + QDockAreaLayoutInfo *info = mlayout->dockInfo(this); + Q_ASSERT(info); + int idx = info->tabIndexToListIndex(d->pressedIndex); + const QDockAreaLayoutItem &item = info->item_list.at(idx); + if (item.widgetItem + && (draggingDock = qobject_cast<QDockWidget *>(item.widgetItem->widget()))) { + // We should drag this QDockWidget away by unpluging it. + // First cancel the QTabBar's internal move + d->moveTabFinished(d->pressedIndex); + d->pressedIndex = -1; + if (d->movingTab) + d->movingTab->setVisible(false); + d->dragStartPosition = QPoint(); + + // Then starts the drag using QDockWidgetPrivate's API + QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); + QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout()); + dockPriv->initDrag(dwlayout->titleArea().center(), true); + dockPriv->startDrag(false); + if (dockPriv->state) + dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier; + } + } + } + + if (draggingDock) { + QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); + if (dockPriv->state && dockPriv->state->dragging) { + QPoint pos = e->globalPos() - dockPriv->state->pressPos; + draggingDock->move(pos); + // move will call QMainWindowLayout::hover + } + } + QTabBar::mouseMoveEvent(e); +} + +void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e) +{ + if (draggingDock && e->button() == Qt::LeftButton) { + QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); + if (dockPriv->state && dockPriv->state->dragging) { + dockPriv->endDrag(); + } + draggingDock = 0; + } + QTabBar::mouseReleaseEvent(e); +} + bool QMainWindowTabBar::event(QEvent *e) { // show the tooltip if tab is too small to fit label @@ -1276,7 +1631,7 @@ QTabBar *QMainWindowLayout::getTabBar() if (!unusedTabBars.isEmpty()) { result = unusedTabBars.takeLast(); } else { - result = new QMainWindowTabBar(parentWidget()); + result = new QMainWindowTabBar(static_cast<QMainWindow *>(parentWidget())); result->setDrawBase(true); result->setElideMode(Qt::ElideRight); result->setDocumentMode(_documentMode); @@ -1305,12 +1660,30 @@ QWidget *QMainWindowLayout::getSeparatorWidget() return result; } +/*! \internal + Returns a pointer QDockAreaLayoutInfo which contains this \a widget directly + (in its internal list) + */ +QDockAreaLayoutInfo *QMainWindowLayout::dockInfo(QWidget *widget) +{ + QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget); + if (info) + return info; + foreach (QDockWidgetGroupWindow *dwgw, + parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly)) { + info = dwgw->layoutInfo()->info(widget); + if (info) + return info; + } + return 0; +} + void QMainWindowLayout::tabChanged() { QTabBar *tb = qobject_cast<QTabBar*>(sender()); if (tb == 0) return; - QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(tb); + QDockAreaLayoutInfo *info = dockInfo(tb); if (info == 0) return; info->apply(false); @@ -1323,7 +1696,7 @@ void QMainWindowLayout::tabMoved(int from, int to) { QTabBar *tb = qobject_cast<QTabBar*>(sender()); Q_ASSERT(tb); - QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(tb); + QDockAreaLayoutInfo *info = dockInfo(tb); Q_ASSERT(info); info->moveTab(from, to); @@ -1365,7 +1738,7 @@ bool QMainWindowLayout::endSeparatorMove(const QPoint&) void QMainWindowLayout::raise(QDockWidget *widget) { - QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget); + QDockAreaLayoutInfo *info = dockInfo(widget); if (info == 0) return; #ifndef QT_NO_TABBAR @@ -1549,6 +1922,8 @@ void QMainWindowLayout::revert(QLayoutItem *widgetItem) QWidget *widget = widgetItem->widget(); layoutState = savedState; currentGapPos = layoutState.indexOf(widget); + if (currentGapPos.isEmpty()) + return; fixToolBarOrientation(widgetItem, currentGapPos.at(1)); layoutState.unplug(currentGapPos); layoutState.fitLayout(); @@ -1613,6 +1988,42 @@ void QMainWindowLayout::animationFinished(QWidget *widget) if (widget == pluggingWidget) { #ifndef QT_NO_DOCKWIDGET + if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) { + // When the animated widget was a QDockWidgetGroupWindow, it means each of the + // embedded QDockWidget needs to be plugged back into the QMainWindow layout. + savedState.clear(); + QDockAreaLayoutInfo* info = dwgw->layoutInfo(); + QList<int> path = layoutState.dockAreaLayout.indexOf(widget); + Q_ASSERT(path.size() >= 2); + + QDockAreaLayoutInfo* parentInfo = layoutState.dockAreaLayout.info(path); + Q_ASSERT(parentInfo); + if (parentInfo->tabbed) { + // merge the two tab widgets + int idx = path.last(); + Q_ASSERT(parentInfo->item_list[idx].widgetItem->widget() == dwgw); + delete parentInfo->item_list[idx].widgetItem; + parentInfo->item_list.removeAt(idx); + std::copy(info->item_list.cbegin(), info->item_list.cend(), + std::inserter(parentInfo->item_list, parentInfo->item_list.begin() + idx)); + quintptr currentId = info->currentTabId(); + *info = QDockAreaLayoutInfo(); + parentInfo->reparentWidgets(parentWidget()); + parentInfo->updateTabBar(); + parentInfo->setCurrentTabId(currentId); + } else { + QDockAreaLayoutItem &item = layoutState.dockAreaLayout.item(path); + Q_ASSERT(item.widgetItem->widget() == dwgw); + delete item.widgetItem; + item.widgetItem = 0; + item.subinfo = new QDockAreaLayoutInfo(qMove(*info)); + *info = QDockAreaLayoutInfo(); + item.subinfo->reparentWidgets(parentWidget()); + item.subinfo->setTabBarShape(parentInfo->tabBarShape); + } + dwgw->destroyIfEmpty(); + } + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) dw->d_func()->plug(currentGapRect); #endif @@ -1633,7 +2044,7 @@ void QMainWindowLayout::animationFinished(QWidget *widget) if (qobject_cast<QDockWidget*>(widget) != 0) { // info() might return null if the widget is destroyed while // animating but before the animationFinished signal is received. - if (QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget)) + if (QDockAreaLayoutInfo *info = dockInfo(widget)) info->setCurrentTab(widget); } #endif @@ -1777,8 +2188,33 @@ void QMainWindowLayout::setCentralWidget(QWidget *widget) invalidate(); } -QLayoutItem *QMainWindowLayout::unplug(QWidget *widget) -{ +/*! \internal + Unplug \a widget (QDockWidget or QToolBar) from it's parent container. + + If \a group is true we might actually unplug the group of tabs this + widget is part if QMainWindow::GroupedDragging is set. When \a group + is false, the widget itself is always unplugged alone + + \returns the QLayoutItem of the dragged element. + The layout item is kept in the layout but set as a gap item. + */ +QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) +{ +#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_TABBAR) + QDockWidgetGroupWindow *floatingParent = qobject_cast<QDockWidgetGroupWindow *>(widget->parentWidget()); + if (group && floatingParent && !widget->isWindow()) { + // We are just dragging a floating window as it, not need to do anything, we just have to + // look up the corresponding QWidgetItem* if it exists + QList<int> tabbedWindowPath = layoutState.indexOf(widget->parentWidget()); + return tabbedWindowPath.isEmpty() ? 0 : layoutState.item(tabbedWindowPath); + } else if (floatingParent) { + // We are unplugging a dock widget from a floating window. + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) { + dw->d_func()->unplug(widget->geometry()); + return 0; + } + } +#endif QList<int> path = layoutState.indexOf(widget); if (path.isEmpty()) return 0; @@ -1792,9 +2228,35 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget) #ifndef QT_NO_DOCKWIDGET if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) { - dw->d_func()->unplug(r); + Q_ASSERT(path.first() == 1); + bool actualGroup = false; +#ifndef QT_NO_TABBAR + if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3) { + QDockAreaLayoutItem &parentItem = layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)); + if (parentItem.subinfo && parentItem.subinfo->tabbed) { + // The QDockWidget is part of a group of tab and we need to unplug them all. + actualGroup = true; + path.removeLast(); + + QDockWidgetGroupWindow* floatingTabs = createTabbedDockWindow(); + QDockAreaLayoutInfo* info = floatingTabs->layoutInfo(); + *info = qMove(*parentItem.subinfo); + delete parentItem.subinfo; + parentItem.subinfo = 0; + floatingTabs->setGeometry(info->rect.translated(parentWidget()->pos())); + floatingTabs->show(); + floatingTabs->raise(); + item = new QDockWidgetGroupWindowItem(floatingTabs); + parentItem.widgetItem = item; + savedState = layoutState; + } + } +#endif // QT_NO_TABBAR + if (!actualGroup) { + dw->d_func()->unplug(r); + } } -#endif +#endif // QT_NO_DOCKWIDGET #ifndef QT_NO_TOOLBAR if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) { tb->d_func()->unplug(r); @@ -1841,6 +2303,9 @@ QList<int> QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mouse #ifndef QT_NO_DOCKWIDGET if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) allowed = dw->isAreaAllowed(toDockWidgetArea(path.at(1))); + + if (qobject_cast<QDockWidgetGroupWindow *>(widget)) + allowed = true; #endif #ifndef QT_NO_TOOLBAR if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) @@ -1893,11 +2358,23 @@ QList<int> QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mouse return path; } +QDockWidgetGroupWindow *QMainWindowLayout::createTabbedDockWindow() +{ + QDockWidgetGroupWindow* f = new QDockWidgetGroupWindow(parentWidget(), Qt::Tool); + new QDockWidgetGroupLayout(f); + return f; +} + void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate) { #ifndef QT_NO_DOCKWIDGET #ifndef QT_NO_TABBAR QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars(); + foreach (QDockWidgetGroupWindow *dwgw, + parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly)) { + used += dwgw->layoutInfo()->usedTabBars(); + } + QSet<QTabBar*> retired = usedTabBars - used; usedTabBars = used; foreach (QTabBar *tab_bar, retired) { @@ -1916,6 +2393,8 @@ void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animat } } + for (int i = 0; i < QInternal::DockCount; ++i) + newState.dockAreaLayout.docks[i].reparentWidgets(parentWidget()); #endif // QT_NO_TABBAR #endif // QT_NO_DOCKWIDGET diff --git a/src/widgets/widgets/qmainwindowlayout_p.h b/src/widgets/widgets/qmainwindowlayout_p.h index 8ccb4d303f..9155c5fb23 100644 --- a/src/widgets/widgets/qmainwindowlayout_p.h +++ b/src/widgets/widgets/qmainwindowlayout_p.h @@ -77,6 +77,23 @@ QT_BEGIN_NAMESPACE class QToolBar; class QRubberBand; +#ifndef QT_NO_DOCKWIDGET +class QDockWidgetGroupWindow : public QWidget +{ + Q_OBJECT +public: + explicit QDockWidgetGroupWindow(QWidget* parent = 0, Qt::WindowFlags f = 0) + : QWidget(parent, f) {} + QDockAreaLayoutInfo *layoutInfo() const; + QDockWidget *topDockWidget() const; + void destroyIfEmpty(); + void adjustFlags(); +protected: + bool event(QEvent *) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent*) Q_DECL_OVERRIDE; +}; +#endif + /* This data structure represents the state of all the tool-bars and dock-widgets. It's value based so it can be easilly copied into a temporary variable. All operations are performed without moving any widgets. Only when we are sure we have the desired state, we call apply(), which moves the @@ -196,10 +213,11 @@ public: QDockWidget *dockwidget, Qt::Orientation orientation); void tabifyDockWidget(QDockWidget *first, QDockWidget *second); - Qt::DockWidgetArea dockWidgetArea(QDockWidget *dockwidget) const; + Qt::DockWidgetArea dockWidgetArea(QWidget* widget) const; void raise(QDockWidget *widget); void setVerticalTabsEnabled(bool enabled); bool restoreDockWidget(QDockWidget *dockwidget); + QDockAreaLayoutInfo *dockInfo(QWidget *w); #ifndef QT_NO_TABBAR bool _documentMode; @@ -224,6 +242,8 @@ public: void setTabShape(QTabWidget::TabShape tabShape); QTabWidget::TabPosition tabPosition(Qt::DockWidgetArea area) const; void setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition); + + QDockWidgetGroupWindow *createTabbedDockWindow(); #endif // QT_NO_TABWIDGET #endif // QT_NO_TABBAR @@ -273,7 +293,7 @@ public: QList<int> hover(QLayoutItem *widgetItem, const QPoint &mousePos); bool plug(QLayoutItem *widgetItem); - QLayoutItem *unplug(QWidget *widget); + QLayoutItem *unplug(QWidget *widget, bool group = false); void revert(QLayoutItem *widgetItem); void updateGapIndicator(); void paintDropIndicator(QPainter *p, QWidget *widget, const QRegion &clip); |