From ac1b6c01454b8969e59becf066bba14f9ed0d310 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 5 Mar 2015 11:00:53 +0100 Subject: Add QMainWindow::GroupedDragging DockOption If this setting is enabled, the entire group of docked tabs will be draggable by the title bar of the group and and individual dock can be dragged by dragging the tab. When tabs are detached, the docks that are contained are reparented to a QDockWidgetGroupWindow. [ChangeLog][QtWidgets][QMainWindow] Added GroupedDragging as a DockOption which allow users to drag all the tabs together when dragging the title of a QDockWidget which is tabbed with others. Change-Id: I5285685b129770498eb3e4fd5f4556e41225a595 Reviewed-by: Friedemann Kleint Reviewed-by: Paul Olav Tvete --- .../widgets/mainwindows/mainwindow/colorswatch.cpp | 8 +- .../widgets/mainwindows/mainwindow/colorswatch.h | 4 +- .../widgets/mainwindows/mainwindow/mainwindow.cpp | 7 + src/widgets/widgets/qdockarealayout.cpp | 82 +++- src/widgets/widgets/qdockarealayout_p.h | 4 +- src/widgets/widgets/qdockwidget.cpp | 153 ++++-- src/widgets/widgets/qdockwidget_p.h | 3 +- src/widgets/widgets/qmainwindow.cpp | 7 + src/widgets/widgets/qmainwindow.h | 3 +- src/widgets/widgets/qmainwindowlayout.cpp | 519 ++++++++++++++++++++- src/widgets/widgets/qmainwindowlayout_p.h | 24 +- 11 files changed, 717 insertions(+), 97 deletions(-) diff --git a/examples/widgets/mainwindows/mainwindow/colorswatch.cpp b/examples/widgets/mainwindows/mainwindow/colorswatch.cpp index 408f6c4b0f..7499389dc3 100644 --- a/examples/widgets/mainwindows/mainwindow/colorswatch.cpp +++ b/examples/widgets/mainwindows/mainwindow/colorswatch.cpp @@ -248,8 +248,8 @@ void ColorDock::setCustomSizeHint(const QSize &size) updateGeometry(); } -ColorSwatch::ColorSwatch(const QString &colorName, QWidget *parent, Qt::WindowFlags flags) - : QDockWidget(parent, flags) +ColorSwatch::ColorSwatch(const QString &colorName, QMainWindow *parent, Qt::WindowFlags flags) + : QDockWidget(parent, flags), mainWindow(parent) { setObjectName(colorName + QLatin1String(" Dock Widget")); setWindowTitle(objectName() + QLatin1String(" [*]")); @@ -390,7 +390,6 @@ ColorSwatch::ColorSwatch(const QString &colorName, QWidget *parent, Qt::WindowFl void ColorSwatch::updateContextMenu() { - QMainWindow *mainWindow = qobject_cast(parentWidget()); const Qt::DockWidgetArea area = mainWindow->dockWidgetArea(this); const Qt::DockWidgetAreas areas = allowedAreas(); @@ -458,7 +457,6 @@ void ColorSwatch::updateContextMenu() void ColorSwatch::splitInto(QAction *action) { - QMainWindow *mainWindow = qobject_cast(parentWidget()); QList dock_list = mainWindow->findChildren(); ColorSwatch *target = 0; foreach (ColorSwatch *dock, dock_list) { @@ -477,7 +475,6 @@ void ColorSwatch::splitInto(QAction *action) void ColorSwatch::tabInto(QAction *action) { - QMainWindow *mainWindow = qobject_cast(parentWidget()); QList dock_list = mainWindow->findChildren(); ColorSwatch *target = 0; foreach (ColorSwatch *dock, dock_list) { @@ -525,7 +522,6 @@ void ColorSwatch::place(Qt::DockWidgetArea area, bool p) { if (!p) return; - QMainWindow *mainWindow = qobject_cast(parentWidget()); mainWindow->addDockWidget(area, this); if (allowedAreasActions->isEnabled()) { diff --git a/examples/widgets/mainwindows/mainwindow/colorswatch.h b/examples/widgets/mainwindows/mainwindow/colorswatch.h index 6d02592b22..78f267c320 100644 --- a/examples/widgets/mainwindows/mainwindow/colorswatch.h +++ b/examples/widgets/mainwindows/mainwindow/colorswatch.h @@ -70,8 +70,10 @@ class ColorSwatch : public QDockWidget QAction *windowModifiedAction; + QMainWindow *mainWindow; + public: - explicit ColorSwatch(const QString &colorName, QWidget *parent = 0, Qt::WindowFlags flags = 0); + explicit ColorSwatch(const QString &colorName, QMainWindow *parent = 0, Qt::WindowFlags flags = 0); QMenu *menu; void setCustomSizeHint(const QSize &size); diff --git a/examples/widgets/mainwindows/mainwindow/mainwindow.cpp b/examples/widgets/mainwindows/mainwindow/mainwindow.cpp index 70a8644b47..ea557c7aa9 100644 --- a/examples/widgets/mainwindows/mainwindow/mainwindow.cpp +++ b/examples/widgets/mainwindows/mainwindow/mainwindow.cpp @@ -156,6 +156,11 @@ void MainWindow::setupMenuBar() action->setChecked(dockOptions() & VerticalTabs); connect(action, SIGNAL(toggled(bool)), this, SLOT(setDockOptions())); + action = mainWindowMenu->addAction(tr("Grouped dragging")); + action->setCheckable(true); + action->setChecked(dockOptions() & GroupedDragging); + connect(action, SIGNAL(toggled(bool)), this, SLOT(setDockOptions())); + QMenu *toolBarMenu = menuBar()->addMenu(tr("Tool bars")); for (int i = 0; i < toolBars.count(); ++i) toolBarMenu->addMenu(toolBars.at(i)->menu); @@ -187,6 +192,8 @@ void MainWindow::setDockOptions() opts |= ForceTabbedDocks; if (actions.at(4)->isChecked()) opts |= VerticalTabs; + if (actions.at(5)->isChecked()) + opts |= GroupedDragging; QMainWindow::setDockOptions(opts); } 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 ** 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 &widgets, bool testing) { uchar marker; @@ -1958,11 +1945,7 @@ bool QDockAreaLayoutInfo::restoreState(QDataStream &stream, QList #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 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 &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(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(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(layout()); - option->initFrom(this); + QDockWidgetGroupWindow *floatingTab = qobject_cast(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(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(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(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(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(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(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(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(parent) == 0 || + (qobject_cast(parent) == 0 && !floatingTab) || isAnimating() || state != 0) { return false; } @@ -853,7 +900,7 @@ bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event) QDockWidgetLayout *dwlayout = qobject_cast(layout); - QMainWindowLayout *mwlayout = qt_mainwindow_layout(qobject_cast(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(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(parent) == 0) + if (qobject_cast(parent) == 0 && qobject_cast(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(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(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(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(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(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(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(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(parent())) + p->adjustFlags(); } #endif // QT_NO_TABBAR break; @@ -1380,7 +1444,7 @@ bool QDockWidget::event(QEvent *event) Q_D(QDockWidget); QMainWindow *win = qobject_cast(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(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 ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -49,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +63,7 @@ #include #include +#include #ifdef Q_DEAD_CODE_FROM_QT4_MAC # include # include @@ -68,9 +71,7 @@ QT_BEGIN_NAMESPACE -#ifdef QT_NO_DOCKWIDGET extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); -#endif /****************************************************************************** ** debug @@ -164,6 +165,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(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(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(QObjectPrivate::get(dw))->moveEvent(static_cast(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(QObjectPrivate::get(dw))->nonClientAreaMouseEvent(static_cast(e)); + return true; + case QEvent::ChildAdded: + if (qobject_cast(static_cast(e)->child())) + adjustFlags(); + break; + default: + break; + } + return QWidget::event(e); +} + +void QDockWidgetGroupWindow::paintEvent(QPaintEvent *) +{ + QDockWidgetGroupLayout *lay = static_cast(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(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(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(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(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(parentWidget())); + Qt::DockWidgetArea area = ml->dockWidgetArea(this); + if (area == Qt::NoDockWidgetArea) + area = Qt::LeftDockWidgetArea; + static_cast(parentWidget())->addDockWidget(area, dw); + } + if (!wasHidden) + dw->show(); + } + foreach (QTabBar *tb, findChildren(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(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 QMainWindowLayoutState::indexOf(QWidget *widget) const #ifndef QT_NO_DOCKWIDGET // is it a dock widget? - if (QDockWidget *dockWidget = qobject_cast(widget)) { - result = dockAreaLayout.indexOf(dockWidget); + if (qobject_cast(widget) || qobject_cast(widget)) { + result = dockAreaLayout.indexOf(widget); if (!result.isEmpty()) result.prepend(1); return result; @@ -413,7 +650,8 @@ QList QMainWindowLayoutState::gapIndex(QWidget *widget, #ifndef QT_NO_DOCKWIDGET // is it a dock widget? - if (qobject_cast(widget) != 0) { + if (qobject_cast(widget) != 0 + || qobject_cast(widget)) { result = dockAreaLayout.gapIndex(pos); if (!result.isEmpty()) result.prepend(1); @@ -440,7 +678,7 @@ bool QMainWindowLayoutState::insertGap(const QList &path, QLayoutItem *item #ifndef QT_NO_DOCKWIDGET if (i == 1) { - Q_ASSERT(qobject_cast(item->widget()) != 0); + Q_ASSERT(qobject_cast(item->widget()) || qobject_cast(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 floatingTabs = + mainWindow->findChildren(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 dockWidgets = findChildrenHelper(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper(mainWindow)) + dockWidgets += findChildrenHelper(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 dockWidgets = findChildrenHelper(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper(mainWindow)) + dockWidgets += findChildrenHelper(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 dockWidgets = findChildrenHelper(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper(mainWindow)) + dockWidgets += findChildrenHelper(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 dockWidgets = findChildrenHelper(mainWindow); + foreach (QDockWidgetGroupWindow *floating, findChildrenHelper(mainWindow)) + dockWidgets += findChildrenHelper(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 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(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(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(QObjectPrivate::get(draggingDock)); + QDockWidgetLayout *dwlayout = static_cast(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(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(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(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(QString(), Qt::FindDirectChildrenOnly)) { + info = dwgw->layoutInfo()->info(widget); + if (info) + return info; + } + return 0; +} + void QMainWindowLayout::tabChanged() { QTabBar *tb = qobject_cast(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(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(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 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(widget)) dw->d_func()->plug(currentGapRect); #endif @@ -1633,7 +2044,7 @@ void QMainWindowLayout::animationFinished(QWidget *widget) if (qobject_cast(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(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 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(widget)) { + dw->d_func()->unplug(widget->geometry()); + return 0; + } + } +#endif QList 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(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(widget)) { tb->d_func()->unplug(r); @@ -1841,6 +2303,9 @@ QList QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mouse #ifndef QT_NO_DOCKWIDGET if (QDockWidget *dw = qobject_cast(widget)) allowed = dw->isAreaAllowed(toDockWidgetArea(path.at(1))); + + if (qobject_cast(widget)) + allowed = true; #endif #ifndef QT_NO_TOOLBAR if (QToolBar *tb = qobject_cast(widget)) @@ -1893,11 +2358,23 @@ QList 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 used = newState.dockAreaLayout.usedTabBars(); + foreach (QDockWidgetGroupWindow *dwgw, + parent()->findChildren(QString(), Qt::FindDirectChildrenOnly)) { + used += dwgw->layoutInfo()->usedTabBars(); + } + QSet 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 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); -- cgit v1.2.3