From 4b0d6704d9e44b19c14958c031186ea17370755d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 22 Feb 2017 12:21:46 +0100 Subject: DockWidgets: Allow to have floating dockwidgets side by side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on what MS VisualStudio can do, allow to have. We can now drop the QDockWidgets on floating ones so that they are next to eachother This is activated when QMainWindow::GroupedDragging and QMainWindow::AllowNestedDocks are both set. [ChangeLog][QtWidgets][QDockWidget] When QMainWindow::GrouppedDragging and QMainWindow::AllowNestedDocks are both enabled, floating dock widgets acquired the ability to be dropped together side by side. Change-Id: I48765c72ef80ef15aa31b91ae582cbb01dea41d6 Reviewed-by: Sérgio Martins --- src/widgets/widgets/qdockarealayout.cpp | 30 +- src/widgets/widgets/qdockarealayout_p.h | 3 +- src/widgets/widgets/qdockwidget.cpp | 7 +- src/widgets/widgets/qmainwindowlayout.cpp | 461 ++++++++++++++++++++---------- src/widgets/widgets/qmainwindowlayout_p.h | 14 +- 5 files changed, 354 insertions(+), 161 deletions(-) (limited to 'src/widgets/widgets') diff --git a/src/widgets/widgets/qdockarealayout.cpp b/src/widgets/widgets/qdockarealayout.cpp index 27ecc33dba..60f6f6d26d 100644 --- a/src/widgets/widgets/qdockarealayout.cpp +++ b/src/widgets/widgets/qdockarealayout.cpp @@ -2418,20 +2418,21 @@ QList QDockAreaLayout::indexOf(QWidget *dockWidget) const return QList(); } -QList QDockAreaLayout::gapIndex(const QPoint &pos) const +QList QDockAreaLayout::gapIndex(const QPoint &pos, bool disallowTabs) const { QMainWindow::DockOptions opts = mainWindow->dockOptions(); bool nestingEnabled = opts & QMainWindow::AllowNestedDocks; QDockAreaLayoutInfo::TabMode tabMode = QDockAreaLayoutInfo::NoTabs; #ifndef QT_NO_TABBAR - if (opts & QMainWindow::AllowTabbedDocks - || opts & QMainWindow::VerticalTabs) - tabMode = QDockAreaLayoutInfo::AllowTabs; - if (opts & QMainWindow::ForceTabbedDocks) - tabMode = QDockAreaLayoutInfo::ForceTabs; + if (!disallowTabs) { + if (opts & QMainWindow::AllowTabbedDocks || opts & QMainWindow::VerticalTabs) + tabMode = QDockAreaLayoutInfo::AllowTabs; + if (opts & QMainWindow::ForceTabbedDocks) + tabMode = QDockAreaLayoutInfo::ForceTabs; - if (tabMode == QDockAreaLayoutInfo::ForceTabs) - nestingEnabled = false; + if (tabMode == QDockAreaLayoutInfo::ForceTabs) + nestingEnabled = false; + } #endif @@ -3303,6 +3304,19 @@ int QDockAreaLayout::separatorMove(const QList &separator, const QPoint &or return delta; } +int QDockAreaLayoutInfo::separatorMove(const QList &separator, const QPoint &origin, + const QPoint &dest) +{ + int delta = 0; + int index = separator.last(); + QDockAreaLayoutInfo *info = this->info(separator); + delta = pick(info->o, dest - origin); + if (delta != 0) + delta = info->separatorMove(index, delta); + info->apply(false); + return delta; +} + #ifndef QT_NO_TABBAR // Sets the correct positions for the separator widgets // Allocates new sepearator widgets with getSeparatorWidget diff --git a/src/widgets/widgets/qdockarealayout_p.h b/src/widgets/widgets/qdockarealayout_p.h index e9bd661cbd..e4cdc9f296 100644 --- a/src/widgets/widgets/qdockarealayout_p.h +++ b/src/widgets/widgets/qdockarealayout_p.h @@ -182,6 +182,7 @@ public: const QPoint &mouse) const; QRegion separatorRegion() const; int separatorMove(int index, int delta); + int separatorMove(const QList &separator, const QPoint &origin, const QPoint &dest); QLayoutItem *itemAt(int *x, int index) const; QLayoutItem *takeAt(int *x, int index); @@ -248,7 +249,7 @@ public: QList indexOfPlaceHolder(const QString &objectName) const; QList indexOf(QWidget *dockWidget) const; - QList gapIndex(const QPoint &pos) const; + QList gapIndex(const QPoint &pos, bool disallowTabs) const; QList findSeparator(const QPoint &pos) const; QDockAreaLayoutItem &item(const QList &path); diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp index 2928d51d20..d25f4d922e 100644 --- a/src/widgets/widgets/qdockwidget.cpp +++ b/src/widgets/widgets/qdockwidget.cpp @@ -214,10 +214,9 @@ QDockWidgetLayout::~QDockWidgetLayout() bool QDockWidgetLayout::nativeWindowDeco() const { bool floating = parentWidget()->isWindow(); - if (!floating) { - if (auto groupWindow = qobject_cast(parentWidget()->parentWidget())) - return groupWindow->hasNativeDecos(); - } + if (auto groupWindow = + qobject_cast(parentWidget()->parentWidget())) + floating = floating || groupWindow->tabLayoutInfo(); return nativeWindowDeco(floating); } diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 30217733cf..e0269a3f02 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -173,13 +173,17 @@ QDebug operator<<(QDebug debug, const QMainWindowLayout *layout) /****************************************************************************** ** QDockWidgetGroupWindow */ -// QDockWidgetGroupWindow is the floating window containing the tabbed dockwidgets in case several -// dockwidgets are dragged together (QMainWindow::GroupedDragging feature). +// QDockWidgetGroupWindow is the floating window containing several QDockWidgets floating together. +// (QMainWindow::GroupedDragging feature) // QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout -// the tabs inside it. +// the QDockWidgets inside it. +// If there is only one QDockWidgets, or all QDockWidgets are tabbed together, it is equivalent +// of a floating QDockWidget (the title of the QDockWidget is the title of the window). But if there +// are nested QDockWidget, an additional title bar is there. #ifndef QT_NO_DOCKWIDGET -class QDockWidgetGroupLayout : public QLayout { - QDockAreaLayoutInfo info; +class QDockWidgetGroupLayout : public QLayout, + public QMainWindowLayoutSeparatorHelper +{ QWidgetResizeHandler *resizer; public: QDockWidgetGroupLayout(QDockWidgetGroupWindow* parent) : QLayout(parent) { @@ -188,7 +192,7 @@ public: resizer->setMovingEnabled(false); } ~QDockWidgetGroupLayout() { - info.deleteAllLayoutItems(); + layoutState.deleteAllLayoutItems(); } void addItem(QLayoutItem*) Q_DECL_OVERRIDE { Q_UNREACHABLE(); } @@ -196,32 +200,43 @@ public: QLayoutItem* itemAt(int index) const Q_DECL_OVERRIDE { int x = 0; - return info.itemAt(&x, index); + return layoutState.itemAt(&x, index); } QLayoutItem* takeAt(int index) Q_DECL_OVERRIDE { int x = 0; - return info.takeAt(&x, index); + QLayoutItem *ret = layoutState.takeAt(&x, index); + if (savedState.rect.isValid() && ret->widget()) { + // we need to remove the item also from the saved state to prevent crash + QList path = savedState.indexOf(ret->widget()); + if (!path.isEmpty()) + savedState.remove(path); + // Also, the item may be contained several times as a gap item. + path = layoutState.indexOf(ret->widget()); + if (!path.isEmpty()) + layoutState.remove(path); + } + return ret; } QSize sizeHint() const Q_DECL_OVERRIDE { int fw = frameWidth(); - return info.sizeHint() + QSize(fw, fw); + return layoutState.sizeHint() + QSize(fw, fw); } QSize minimumSize() const Q_DECL_OVERRIDE { int fw = frameWidth(); - return info.minimumSize() + QSize(fw, fw); + return layoutState.minimumSize() + QSize(fw, fw); } QSize maximumSize() const Q_DECL_OVERRIDE { int fw = frameWidth(); - return info.maximumSize() + QSize(fw, fw); + return layoutState.maximumSize() + QSize(fw, fw); } void setGeometry(const QRect&r) Q_DECL_OVERRIDE { groupWindow()->destroyOrHideIfEmpty(); - QDockAreaLayoutInfo *li = layoutInfo(); + QDockAreaLayoutInfo *li = dockAreaLayoutInfo(); if (li->isEmpty()) return; int fw = frameWidth(); @@ -231,12 +246,12 @@ public: li->rect = r.adjusted(fw, fw, -fw, -fw); li->fitItems(); li->apply(false); + if (savedState.rect.isValid()) + savedState.rect = li->rect; resizer->setActive(QWidgetResizeHandler::Resize, !nativeWindowDeco()); } - QDockAreaLayoutInfo *layoutInfo() { - return &info; - } + QDockAreaLayoutInfo *dockAreaLayoutInfo() { return &layoutState; } bool nativeWindowDeco() const { @@ -253,14 +268,21 @@ public: { return static_cast(parent()); } + + QDockAreaLayoutInfo layoutState; + QDockAreaLayoutInfo savedState; }; bool QDockWidgetGroupWindow::event(QEvent *e) { + auto lay = static_cast(layout()); + if (lay && lay->windowEvent(e)) + return true; + switch (e->type()) { case QEvent::Close: // Forward the close to the QDockWidget just as if its close button was pressed - if (QDockWidget *dw = topDockWidget()) { + if (QDockWidget *dw = activeTabbedDockWidget()) { e->ignore(); dw->close(); adjustFlags(); @@ -268,7 +290,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e) return true; case QEvent::Move: // Let QDockWidgetPrivate::moseEvent handle the dragging - if (QDockWidget *dw = topDockWidget()) + if (QDockWidget *dw = activeTabbedDockWidget()) static_cast(QObjectPrivate::get(dw))->moveEvent(static_cast(e)); return true; case QEvent::NonClientAreaMouseMove: @@ -276,7 +298,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e) case QEvent::NonClientAreaMouseButtonRelease: case QEvent::NonClientAreaMouseButtonDblClick: // Let the QDockWidgetPrivate of the currently visible dock widget handle the drag and drop - if (QDockWidget *dw = topDockWidget()) + if (QDockWidget *dw = activeTabbedDockWidget()) static_cast(QObjectPrivate::get(dw))->nonClientAreaMouseEvent(static_cast(e)); return true; case QEvent::ChildAdded: @@ -308,17 +330,49 @@ void QDockWidgetGroupWindow::paintEvent(QPaintEvent *) QDockAreaLayoutInfo *QDockWidgetGroupWindow::layoutInfo() const { - return static_cast(layout())->layoutInfo(); + return static_cast(layout())->dockAreaLayoutInfo(); } /*! \internal - Returns the currently active QDockWidget. + If this is a floating tab bar returns the currently the QDockWidgetGroupWindow that contains + tab, otherwise, return nullptr; + \note: if there is only one QDockWidget, it's still considered as a floating tab */ -QDockWidget *QDockWidgetGroupWindow::topDockWidget() const +const QDockAreaLayoutInfo *QDockWidgetGroupWindow::tabLayoutInfo() const +{ + const QDockAreaLayoutInfo *info = layoutInfo(); + while (info && !info->tabbed) { + // There should be only one tabbed subinfo otherwise we are not a floating tab but a real + // window + const QDockAreaLayoutInfo *next = nullptr; + bool isSingle = false; + for (const auto &item : info->item_list) { + if (item.skip() || (item.flags & QDockAreaLayoutItem::GapItem)) + continue; + if (next || isSingle) // Two visible things + return nullptr; + if (item.subinfo) + next = item.subinfo; + else if (item.widgetItem) + isSingle = true; + } + if (isSingle) + return info; + info = next; + } + return info; +} + +/*! \internal + If this is a floating tab bar returns the currently active QDockWidget, otherwise nullptr + */ +QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const { - QDockAreaLayoutInfo *info = layoutInfo(); - QDockWidget *dw = 0; + QDockWidget *dw = nullptr; #if QT_CONFIG(tabbar) + const QDockAreaLayoutInfo *info = tabLayoutInfo(); + if (!info) + return nullptr; if (info->tabBar && info->tabBar->currentIndex() >= 0) { int i = info->tabIndexToListIndex(info->tabBar->currentIndex()); if (i >= 0) { @@ -327,7 +381,6 @@ QDockWidget *QDockWidgetGroupWindow::topDockWidget() const dw = qobject_cast(item.widgetItem->widget()); } } -#endif if (!dw) { for (int i = 0; !dw && i < info->item_list.count(); ++i) { const QDockAreaLayoutItem &item = info->item_list.at(i); @@ -338,6 +391,7 @@ QDockWidget *QDockWidgetGroupWindow::topDockWidget() const dw = qobject_cast(item.widgetItem->widget()); } } +#endif return dw; } @@ -384,18 +438,18 @@ void QDockWidgetGroupWindow::destroyOrHideIfEmpty() } /*! \internal - Sets the flags of this window in accordence to the capabilities of the dock widgets + Sets the flags of this window in accordance 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) { + + QDockWidget *top = activeTabbedDockWidget(); + if (!top) { // nested tabs, show window decoration + flags = + ((oldFlags & ~Qt::FramelessWindowHint) | Qt::CustomizeWindowHint | Qt::WindowTitleHint); + } else if (static_cast(layout())->nativeWindowDeco()) { flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint; flags.setFlag(Qt::WindowCloseButtonHint, top->features() & QDockWidget::DockWidgetClosable); flags &= ~Qt::FramelessWindowHint; @@ -428,21 +482,99 @@ void QDockWidgetGroupWindow::adjustFlags() show(); // setWindowFlags hides the window } - setWindowTitle(top->windowTitle()); - setWindowIcon(top->windowIcon()); + QWidget *titleBarOf = top ? top : parentWidget(); + setWindowTitle(titleBarOf->windowTitle()); + setWindowIcon(titleBarOf->windowIcon()); } bool QDockWidgetGroupWindow::hasNativeDecos() const { + QDockWidget *dw = activeTabbedDockWidget(); + if (!dw) // We have a group of nested QDockWidgets (not just floating tabs) + return true; + if (!QDockWidgetLayout::wmSupportsNativeWindowDeco()) return false; - if (QDockWidget *dw = topDockWidget()) - return dw->titleBarWidget() == nullptr; + return dw->titleBarWidget() == nullptr; +} +/* + The given widget is hovered over this floating group. + This function will save the state and create a gap in the actual state. + currentGapRect and currentGapPos will be set. + One must call restore() or apply() after this function. + Returns true if there was any change in the currentGapPos + */ +bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mousePos) +{ + QDockAreaLayoutInfo &savedState = static_cast(layout())->savedState; + if (savedState.isEmpty()) + savedState = *layoutInfo(); + + QMainWindow::DockOptions opts = static_cast(parentWidget())->dockOptions(); + bool nestingEnabled = + (opts & QMainWindow::AllowNestedDocks) && !(opts & QMainWindow::ForceTabbedDocks); + QDockAreaLayoutInfo::TabMode tabMode = + nestingEnabled ? QDockAreaLayoutInfo::AllowTabs : QDockAreaLayoutInfo::ForceTabs; + if (auto group = qobject_cast(widgetItem->widget())) { + if (!group->tabLayoutInfo()) + tabMode = QDockAreaLayoutInfo::NoTabs; + } + + QDockAreaLayoutInfo newState = savedState; + if (newState.tabbed) { + // insertion into a top-level tab + newState.item_list = { QDockAreaLayoutItem(new QDockAreaLayoutInfo(newState)) }; + newState.item_list.first().size = pick(savedState.o, savedState.rect.size()); + newState.tabbed = false; + newState.tabBar = nullptr; + } + + auto newGapPos = newState.gapIndex(mousePos, nestingEnabled, tabMode); + Q_ASSERT(!newGapPos.isEmpty()); + if (newGapPos == currentGapPos) + return false; // gap is already there + currentGapPos = newGapPos; + newState.insertGap(currentGapPos, widgetItem); + newState.fitItems(); + currentGapRect = newState.info(currentGapPos)->itemRect(currentGapPos.last(), true); + *layoutInfo() = std::move(newState); + layoutInfo()->apply(opts & QMainWindow::AnimatedDocks); return true; } +/* + Remove the gap that was created by hover() + */ +void QDockWidgetGroupWindow::restore() +{ + QDockAreaLayoutInfo &savedState = static_cast(layout())->savedState; + if (!savedState.isEmpty()) { + *layoutInfo() = savedState; + savedState = QDockAreaLayoutInfo(); + } + currentGapRect = QRect(); + currentGapPos.clear(); + adjustFlags(); + layoutInfo()->fitItems(); + layoutInfo()->apply(static_cast(parentWidget())->dockOptions() + & QMainWindow::AnimatedDocks); +} + +/* + Apply the state that was created by hover + */ +void QDockWidgetGroupWindow::apply() +{ + static_cast(layout())->savedState.clear(); + currentGapRect = QRect(); + layoutInfo()->plug(currentGapPos); + currentGapPos.clear(); + adjustFlags(); + layoutInfo()->apply(false); +} + #endif /****************************************************************************** @@ -696,7 +828,14 @@ QList QMainWindowLayoutState::gapIndex(QWidget *widget, // is it a dock widget? if (qobject_cast(widget) != 0 || qobject_cast(widget)) { - result = dockAreaLayout.gapIndex(pos); + bool disallowTabs = false; +#if QT_CONFIG(tabbar) + if (auto *group = qobject_cast(widget)) { + if (!group->tabLayoutInfo()) // Disallow to drop nested docks as a tab + disallowTabs = true; + } +#endif + result = dockAreaLayout.gapIndex(pos, disallowTabs); if (!result.isEmpty()) result.prepend(1); return result; @@ -1824,12 +1963,16 @@ void QMainWindowLayout::invalidate() } #ifndef QT_NO_DOCKWIDGET -void QMainWindowLayout::setCurrentHoveredFloat(QWidget *w) +void QMainWindowLayout::setCurrentHoveredFloat(QDockWidgetGroupWindow *w) { if (currentHoveredFloat != w) { if (currentHoveredFloat) { disconnect(currentHoveredFloat.data(), &QObject::destroyed, this, &QMainWindowLayout::updateGapIndicator); + if (currentHoveredFloat) + currentHoveredFloat->restore(); + } else if (w) { + restore(true); } currentHoveredFloat = w; @@ -1900,52 +2043,30 @@ void QMainWindowLayout::revert(QLayoutItem *widgetItem) bool QMainWindowLayout::plug(QLayoutItem *widgetItem) { -#ifndef QT_NO_DOCKWIDGET +#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) && QT_CONFIG(tabbar) if (currentHoveredFloat) { QWidget *widget = widgetItem->widget(); QList previousPath = layoutState.indexOf(widget); if (!previousPath.isEmpty()) layoutState.remove(previousPath); + previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget); // Let's remove the widget from any possible group window foreach (QDockWidgetGroupWindow *dwgw, parent()->findChildren(QString(), Qt::FindDirectChildrenOnly)) { + if (dwgw == currentHoveredFloat) + continue; QList path = dwgw->layoutInfo()->indexOf(widget); if (!path.isEmpty()) dwgw->layoutInfo()->remove(path); } currentGapRect = QRect(); -#if QT_CONFIG(tabwidget) - if (QDockWidget *dropTo = qobject_cast(currentHoveredFloat)) { - //dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two tabs - QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); - floatingTabs->setGeometry(dropTo->geometry()); - QDockAreaLayoutInfo *info = floatingTabs->layoutInfo(); - *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock, - Qt::Horizontal, QTabBar::RoundedSouth, - static_cast(parentWidget())); - info->tabbed = true; - QLayout *parentLayout = currentHoveredFloat->parentWidget()->layout(); - info->item_list.append(QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(currentHoveredFloat)))); - - dropTo->setParent(floatingTabs); - dropTo->show(); - dropTo->d_func()->plug(QRect()); - setCurrentHoveredFloat(floatingTabs); - } -#endif // QT_CONFIG(tabwidget) -#if QT_CONFIG(tabbar) - QDockWidgetGroupWindow *dwgw = qobject_cast(currentHoveredFloat); - Q_ASSERT(dwgw); - Q_ASSERT(dwgw->layoutInfo()->tabbed); // because floating group should always be tabbed - previousPath = dwgw->layoutInfo()->indexOf(widget); + currentHoveredFloat->apply(); if (!previousPath.isEmpty()) - dwgw->layoutInfo()->remove(previousPath); - dwgw->layoutInfo()->tab(0, widgetItem); - QRect globalRect = dwgw->layoutInfo()->tabContentRect(); - globalRect.moveTopLeft(dwgw->mapToGlobal(globalRect.topLeft())); + currentHoveredFloat->layoutInfo()->remove(previousPath); + QRect globalRect = currentHoveredFloat->currentGapRect; + globalRect.moveTopLeft(currentHoveredFloat->mapToGlobal(globalRect.topLeft())); pluggingWidget = widget; widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks); -#endif // QT_CONFIG(tabbar) return true; } #endif @@ -2014,57 +2135,55 @@ void QMainWindowLayout::animationFinished(QWidget *widget) if (widget == pluggingWidget) { #ifndef QT_NO_DOCKWIDGET +#if QT_CONFIG(tabbar) 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(); - QDockAreaLayoutInfo* parentInfo; - QList path; - - if (QDockWidgetGroupWindow *dropTo = qobject_cast(currentHoveredFloat)) { - parentInfo = dropTo->layoutInfo(); -#if QT_CONFIG(tabbar) - Q_ASSERT(parentInfo->tabbed); -#endif - path = parentInfo->indexOf(widget); - Q_ASSERT(path.size() == 1); + QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo(); + const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo(); + QDockAreaLayoutInfo *dstParentInfo; + QList dstPath; + + if (currentHoveredFloat) { + dstPath = currentHoveredFloat->layoutInfo()->indexOf(widget); + Q_ASSERT(dstPath.size() >= 1); + dstParentInfo = currentHoveredFloat->layoutInfo()->info(dstPath); } else { - path = layoutState.dockAreaLayout.indexOf(widget); - Q_ASSERT(path.size() >= 2); - parentInfo = layoutState.dockAreaLayout.info(path); - Q_ASSERT(parentInfo); + dstPath = layoutState.dockAreaLayout.indexOf(widget); + Q_ASSERT(dstPath.size() >= 2); + dstParentInfo = layoutState.dockAreaLayout.info(dstPath); } -#if QT_CONFIG(tabbar) - if (parentInfo->tabbed) { + Q_ASSERT(dstParentInfo); + int idx = dstPath.constLast(); + Q_ASSERT(dstParentInfo->item_list[idx].widgetItem->widget() == dwgw); + if (dstParentInfo->tabbed && srcTabInfo) { // merge the two tab widgets - int idx = path.constLast(); - 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(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget()); - parentInfo->updateTabBar(); - parentInfo->setCurrentTabId(currentId); - } else -#endif // QT_CONFIG(tabbar) - { - QDockAreaLayoutItem &item = layoutState.dockAreaLayout.item(path); + delete dstParentInfo->item_list[idx].widgetItem; + dstParentInfo->item_list.removeAt(idx); + std::copy(srcTabInfo->item_list.cbegin(), srcTabInfo->item_list.cend(), + std::inserter(dstParentInfo->item_list, + dstParentInfo->item_list.begin() + idx)); + quintptr currentId = srcTabInfo->currentTabId(); + *srcInfo = QDockAreaLayoutInfo(); + dstParentInfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data() + : parentWidget()); + dstParentInfo->updateTabBar(); + dstParentInfo->setCurrentTabId(currentId); + } else { + QDockAreaLayoutItem &item = dstParentInfo->item_list[idx]; Q_ASSERT(item.widgetItem->widget() == dwgw); delete item.widgetItem; item.widgetItem = 0; - item.subinfo = new QDockAreaLayoutInfo(qMove(*info)); - *info = QDockAreaLayoutInfo(); -#if QT_CONFIG(tabbar) - item.subinfo->reparentWidgets(parentWidget()); - item.subinfo->setTabBarShape(parentInfo->tabBarShape); -#endif + item.subinfo = new QDockAreaLayoutInfo(std::move(*srcInfo)); + *srcInfo = QDockAreaLayoutInfo(); + item.subinfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data() + : parentWidget()); + item.subinfo->setTabBarShape(dstParentInfo->tabBarShape); } dwgw->destroyOrHideIfEmpty(); } +#endif if (QDockWidget *dw = qobject_cast(widget)) { dw->setParent(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget()); @@ -2220,6 +2339,36 @@ void QMainWindowLayout::setCentralWidget(QWidget *widget) invalidate(); } +#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) +/*! \internal + This helper function is called by QMainWindowLayout::unplug if QMainWindow::GroupedDragging is + set and we are dragging the title bar of a non-floating QDockWidget. + If one should unplug the whole group, do so and return true, otherwise return false. + \a item is pointing to the QLayoutItem that holds the QDockWidget, but will be updated to the + QLayoutItem that holds the new QDockWidgetGroupWindow if the group is unplugged. +*/ +static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item, + QDockAreaLayoutItem &parentItem) +{ + if (!parentItem.subinfo || !parentItem.subinfo->tabbed) + return false; + + // The QDockWidget is part of a group of tab and we need to unplug them all. + + QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow(); + QDockAreaLayoutInfo *info = floatingTabs->layoutInfo(); + *info = std::move(*parentItem.subinfo); + delete parentItem.subinfo; + parentItem.subinfo = nullptr; + floatingTabs->setGeometry(info->rect.translated(layout->parentWidget()->pos())); + floatingTabs->show(); + floatingTabs->raise(); + *item = new QDockWidgetGroupWindowItem(floatingTabs); + parentItem.widgetItem = *item; + return true; +} +#endif + /*! \internal Unplug \a widget (QDockWidget or QToolBar) from it's parent container. @@ -2233,19 +2382,31 @@ void QMainWindowLayout::setCentralWidget(QWidget *widget) QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) { #if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_TABBAR) - if (!widget->isWindow() && qobject_cast(widget->parentWidget())) { - if (group) { + auto *groupWindow = qobject_cast(widget->parentWidget()); + if (!widget->isWindow() && groupWindow) { + if (group && groupWindow->tabLayoutInfo()) { // 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); + if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) { + QList groupWindowPath = info->indexOf(widget->parentWidget()); + return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem; + } + return nullptr; + } + QList path = groupWindow->layoutInfo()->indexOf(widget); + QLayoutItem *item = groupWindow->layoutInfo()->item(path).widgetItem; + if (group && path.size() > 1 + && unplugGroup(this, &item, + groupWindow->layoutInfo()->item(path.mid(0, path.size() - 1)))) { + return item; } else { // We are unplugging a dock widget from a floating window. - if (QDockWidget *dw = qobject_cast(widget)) { - dw->d_func()->unplug(widget->geometry()); - int index = widget->parentWidget()->layout()->indexOf(widget); - return widget->parentWidget()->layout()->itemAt(index); - } + QDockWidget *dw = qobject_cast(widget); + Q_ASSERT(dw); // cannot be a QDockWidgetGroupWindow because it's not floating. + dw->d_func()->unplug(widget->geometry()); + groupWindow->layoutInfo()->fitItems(); + groupWindow->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks); + return item; } } #endif @@ -2263,30 +2424,15 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) #ifndef QT_NO_DOCKWIDGET if (QDockWidget *dw = qobject_cast(widget)) { Q_ASSERT(path.constFirst() == 1); - bool actualGroup = false; #if QT_CONFIG(tabwidget) - 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; - } - } + if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3 + && unplugGroup(this, &item, + layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) { + path.removeLast(); + savedState = layoutState; + } else #endif // QT_CONFIG(tabwidget) - if (!actualGroup) { + { dw->d_func()->unplug(r); } } @@ -2315,11 +2461,11 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) void QMainWindowLayout::updateGapIndicator() { #ifndef QT_NO_RUBBERBAND - if ((!widgetAnimator.animating() && !currentGapPos.isEmpty()) + if (!widgetAnimator.animating() && (!currentGapPos.isEmpty() #if QT_CONFIG(dockwidget) - || currentHoveredFloat + || currentHoveredFloat #endif - ) { + )) { QWidget *expectedParent = #if QT_CONFIG(dockwidget) currentHoveredFloat ? currentHoveredFloat.data() : @@ -2332,11 +2478,13 @@ void QMainWindowLayout::updateGapIndicator() } else if (gapIndicator->parent() != expectedParent) { gapIndicator->setParent(expectedParent); } - gapIndicator->setGeometry( + #if QT_CONFIG(dockwidget) - currentHoveredFloat ? currentHoveredFloat->rect() : + if (currentHoveredFloat) + gapIndicator->setGeometry(currentHoveredFloat->currentGapRect); + else #endif - currentGapRect); + gapIndicator->setGeometry(currentGapRect); gapIndicator->show(); gapIndicator->raise(); } else if (gapIndicator) { @@ -2385,8 +2533,31 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos) if (!w->geometry().contains(mousePos)) continue; - setCurrentHoveredFloat(w); - restore(true); + if (auto dropTo = qobject_cast(w)) { + // dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two + // tabs + QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); // FIXME + floatingTabs->setGeometry(dropTo->geometry()); + QDockAreaLayoutInfo *info = floatingTabs->layoutInfo(); + *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock, + Qt::Horizontal, QTabBar::RoundedSouth, + static_cast(parentWidget())); + info->tabbed = true; + QLayout *parentLayout = dropTo->parentWidget()->layout(); + info->item_list.append( + QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(dropTo)))); + + dropTo->setParent(floatingTabs); + dropTo->show(); + dropTo->d_func()->plug(QRect()); + w = floatingTabs; + } + Q_ASSERT(qobject_cast(w)); + auto group = static_cast(w); + if (group->hover(widgetItem, group->mapFromGlobal(mousePos))) { + setCurrentHoveredFloat(group); + applyState(layoutState); // update the tabbars + } return; } } @@ -2454,7 +2625,7 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos) #ifndef QT_NO_DOCKWIDGET parentWidget()->update(layoutState.dockAreaLayout.separatorRegion()); #endif - layoutState = newState; + layoutState = std::move(newState); applyState(layoutState); updateGapIndicator(); diff --git a/src/widgets/widgets/qmainwindowlayout_p.h b/src/widgets/widgets/qmainwindowlayout_p.h index dcd94e3617..cc3b4e9261 100644 --- a/src/widgets/widgets/qmainwindowlayout_p.h +++ b/src/widgets/widgets/qmainwindowlayout_p.h @@ -327,11 +327,19 @@ public: explicit QDockWidgetGroupWindow(QWidget* parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f) {} QDockAreaLayoutInfo *layoutInfo() const; - QDockWidget *topDockWidget() const; + const QDockAreaLayoutInfo *tabLayoutInfo() const; + QDockWidget *activeTabbedDockWidget() const; void destroyOrHideIfEmpty(); void adjustFlags(); bool hasNativeDecos() const; + bool hover(QLayoutItem *widgetItem, const QPoint &mousePos); + void restore(); + void apply(); + + QRect currentGapRect; + QList currentGapPos; + protected: bool event(QEvent *) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent*) Q_DECL_OVERRIDE; @@ -543,8 +551,8 @@ public: QPointer gapIndicator; #endif #ifndef QT_NO_DOCKWIDGET - QPointer currentHoveredFloat; // set when dragging over a floating dock widget - void setCurrentHoveredFloat(QWidget *w); + QPointer currentHoveredFloat; // set when dragging over a floating dock widget + void setCurrentHoveredFloat(QDockWidgetGroupWindow *w); #endif void hover(QLayoutItem *widgetItem, const QPoint &mousePos); -- cgit v1.2.3