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 { - QDockAreaLayoutInfo *info = layoutInfo(); - QDockWidget *dw = 0; + 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 +{ + 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; + QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo(); + const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo(); + QDockAreaLayoutInfo *dstParentInfo; + QList dstPath; - 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); + 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);