diff options
Diffstat (limited to 'src/widgets/widgets/qmainwindowlayout.cpp')
-rw-r--r-- | src/widgets/widgets/qmainwindowlayout.cpp | 559 |
1 files changed, 418 insertions, 141 deletions
diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 12d8876fcc..85c58fd70f 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -24,6 +24,10 @@ #endif #include <qapplication.h> +#if QT_CONFIG(draganddrop) +#include <qdrag.h> +#endif +#include <qmimedata.h> #if QT_CONFIG(statusbar) #include <qstatusbar.h> #endif @@ -46,6 +50,8 @@ #include <private/qlayoutengine_p.h> #include <private/qwidgetresizehandler_p.h> +#include <QScopedValueRollback> + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -145,18 +151,57 @@ QDebug operator<<(QDebug debug, const QMainWindowLayout *layout) return debug; } +// Use this to dump item lists of all populated main window docks. +// Use DUMP macro inside QMainWindowLayout +#if 0 +static void dumpItemLists(const QMainWindowLayout *layout, const char *function, const char *comment) +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const auto &list = layout->layoutState.dockAreaLayout.docks[i].item_list; + if (list.isEmpty()) + continue; + qDebug() << function << comment << "Dock" << i << list; + } +} +#define DUMP(comment) dumpItemLists(this, __FUNCTION__, comment) +#endif // 0 + #endif // QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG) -/****************************************************************************** - ** QDockWidgetGroupWindow - */ -// 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 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. + +/*! + \internal + QDockWidgetGroupWindow is a floating window, containing several QDockWidgets floating together. + This requires QMainWindow::GroupedDragging to be enabled. + QDockWidgets floating jointly in a QDockWidgetGroupWindow are considered to be docked. + Their \c isFloating property is \c false. + QDockWidget children of a QDockWidgetGroupWindow are either: + \list + \li tabbed (as long as Qt is compiled with the \c tabbar feature), or + \li arranged next to each other, equivalent to the default on a main window dock. + \endlist + + QDockWidgetGroupWindow uses QDockWidgetGroupLayout to lay out its QDockWidget children. + It stores layout information in a QDockAreaLayoutInfo, including temporary spacer items + and rubber bands. + + If its QDockWidget children are tabbed, the QDockWidgetGroupWindow shows the active QDockWidget's + title as its own window title. + + QDockWidgetGroupWindow is designed to hold more than one QDockWidget. + A QDockWidgetGroupWindow with only one QDockWidget child may occur only temporarily + \list + \li in its construction phase, or + \li during a hover: While QDockWidget A is hovered over B, B is converted into a QDockWidgetGroupWindow. + \endlist + + A QDockWidgetGroupWindow with only one QDockWidget child must never get focus, be dragged or dropped. + To enforce this restriction, QDockWidgetGrouWindow will remove itself after its second QDockWidget + child has been removed. It will make its last QDockWidget child a single, floating QDockWidget. + Eventually, the empty QDockWidgetGroupWindow will call deleteLater() on itself. +*/ + + #if QT_CONFIG(dockwidget) class QDockWidgetGroupLayout : public QLayout, public QMainWindowLayoutSeparatorHelper<QDockWidgetGroupLayout> @@ -293,6 +338,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e) case QEvent::Resize: updateCurrentGapRect(); emit resized(); + break; default: break; } @@ -397,8 +443,8 @@ void QDockWidgetGroupWindow::destroyOrHideIfEmpty() } // Make sure to reparent the possibly floating or hidden QDockWidgets to the parent - const auto dockWidgets = findChildren<QDockWidget *>(Qt::FindDirectChildrenOnly); - for (QDockWidget *dw : dockWidgets) { + const auto dockWidgetsList = dockWidgets(); + for (QDockWidget *dw : dockWidgetsList) { const bool wasFloating = dw->isFloating(); const bool wasHidden = dw->isHidden(); dw->setParent(parentWidget()); @@ -418,14 +464,28 @@ void QDockWidgetGroupWindow::destroyOrHideIfEmpty() if (!wasHidden) dw->show(); } -#if QT_CONFIG(tabbar) - const auto tabBars = findChildren<QTabBar *>(Qt::FindDirectChildrenOnly); - for (QTabBar *tb : tabBars) - tb->setParent(parentWidget()); -#endif deleteLater(); } +/*! + \internal + \return \c true if the group window has at least one visible QDockWidget child, + otherwise false. + */ +bool QDockWidgetGroupWindow::hasVisibleDockWidgets() const +{ + const auto &children = findChildren<QDockWidget *>(Qt::FindChildrenRecursively); + for (auto child : children) { + // WA_WState_Visible is set on the dock widget, associated to the active tab + // and unset on all others. + // WA_WState_Hidden is set if the dock widgets have been explicitly hidden. + // This is the relevant information to check (equivalent to !child->isHidden()). + if (!child->testAttribute(Qt::WA_WState_Hidden)) + return true; + } + return false; +} + /*! \internal Sets the flags of this window in accordance to the capabilities of the dock widgets */ @@ -474,7 +534,7 @@ void QDockWidgetGroupWindow::adjustFlags() m_removedFrameSize = QSize(); } - show(); // setWindowFlags hides the window + setVisible(hasVisibleDockWidgets()); } QWidget *titleBarOf = top ? top : parentWidget(); @@ -535,8 +595,12 @@ bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mouseP auto newGapPos = newState.gapIndex(mousePos, nestingEnabled, tabMode); Q_ASSERT(!newGapPos.isEmpty()); - if (newGapPos == currentGapPos) - return false; // gap is already there + + // Do not insert a new gap item, if the current position already is a gap, + // or if the group window contains one + if (newGapPos == currentGapPos || newState.hasGapItem(newGapPos)) + return false; + currentGapPos = newGapPos; newState.insertGap(currentGapPos, widgetItem); newState.fitItems(); @@ -583,6 +647,108 @@ void QDockWidgetGroupWindow::apply() layoutInfo()->apply(false); } +void QDockWidgetGroupWindow::childEvent(QChildEvent *event) +{ + switch (event->type()) { + case QEvent::ChildRemoved: + if (auto *dockWidget = qobject_cast<QDockWidget *>(event->child())) + dockWidget->removeEventFilter(this); + destroyIfSingleItemLeft(); + break; + case QEvent::ChildAdded: + if (auto *dockWidget = qobject_cast<QDockWidget *>(event->child())) + dockWidget->installEventFilter(this); + break; + default: + break; + } +} + +bool QDockWidgetGroupWindow::eventFilter(QObject *obj, QEvent *event) +{ + auto *dockWidget = qobject_cast<QDockWidget *>(obj); + if (!dockWidget) + return QWidget::eventFilter(obj, event); + + switch (event->type()) { + case QEvent::Close: + // We don't want closed dock widgets in a floating tab + // => dock it to the main dock, before closing; + reparent(dockWidget); + dockWidget->setFloating(false); + break; + + case QEvent::Hide: + // if the dock widget is not an active tab, it is hidden anyway. + // if it is the active tab, hide the whole group. + if (dockWidget->isVisible()) + hide(); + break; + + default: + break; + } + return QWidget::eventFilter(obj, event); +} + +void QDockWidgetGroupWindow::destroyIfSingleItemLeft() +{ + const auto &dockWidgets = this->dockWidgets(); + + // Handle only the last dock + if (dockWidgets.count() != 1) + return; + + auto *lastDockWidget = dockWidgets.at(0); + + // If the last remaining dock widget is not in the group window's item_list, + // a group window is being docked on a main window docking area. + // => don't interfere + if (layoutInfo()->indexOf(lastDockWidget).isEmpty()) + return; + + auto *mainWindow = qobject_cast<QMainWindow *>(parentWidget()); + QMainWindowLayout *mwLayout = qt_mainwindow_layout(mainWindow); + + // Unplug the last remaining dock widget and hide the group window, to avoid flickering + mwLayout->unplug(lastDockWidget, QDockWidgetPrivate::DragScope::Widget); + lastDockWidget->setGeometry(geometry()); + hide(); + + // Get the layout info for the main window dock, where dock widgets need to go + QDockAreaLayoutInfo &parentInfo = mwLayout->layoutState.dockAreaLayout.docks[layoutInfo()->dockPos]; + + // Re-parent last dock widget + reparent(lastDockWidget); + + // the group window could still have placeholder items => clear everything + layoutInfo()->item_list.clear(); + + // remove the group window and the dock's item_list pointing to it. + parentInfo.remove(this); + destroyOrHideIfEmpty(); +} + +void QDockWidgetGroupWindow::reparent(QDockWidget *dockWidget) +{ + // reparent a dockWidget to the main window + // - remove it from the floating dock's layout info + // - insert it to the main dock's layout info + // Finally, set draggingDock to nullptr, since the drag is finished. + auto *mainWindow = qobject_cast<QMainWindow *>(parentWidget()); + Q_ASSERT(mainWindow); + QMainWindowLayout *mwLayout = qt_mainwindow_layout(mainWindow); + Q_ASSERT(mwLayout); + QDockAreaLayoutInfo &parentInfo = mwLayout->layoutState.dockAreaLayout.docks[layoutInfo()->dockPos]; + dockWidget->removeEventFilter(this); + parentInfo.add(dockWidget); + layoutInfo()->remove(dockWidget); + const bool wasFloating = dockWidget->isFloating(); + const bool wasVisible = dockWidget->isVisible(); + dockWidget->setParent(mainWindow); + dockWidget->setFloating(wasFloating); + dockWidget->setVisible(wasVisible); +} #endif /****************************************************************************** @@ -1195,7 +1361,7 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream, if (info == nullptr) { continue; } - info->item_list.append(QDockAreaLayoutItem(new QDockWidgetItem(w))); + info->add(w); } } } @@ -1265,17 +1431,18 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream, #if QT_CONFIG(toolbar) -static inline void validateToolBarArea(Qt::ToolBarArea &area) +static constexpr Qt::ToolBarArea validateToolBarArea(Qt::ToolBarArea area) { switch (area) { case Qt::LeftToolBarArea: case Qt::RightToolBarArea: case Qt::TopToolBarArea: case Qt::BottomToolBarArea: - break; + return area; default: - area = Qt::TopToolBarArea; + break; } + return Qt::TopToolBarArea; } static QInternal::DockPosition toDockPos(Qt::ToolBarArea area) @@ -1311,7 +1478,7 @@ static inline Qt::ToolBarArea toToolBarArea(int pos) void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area) { - validateToolBarArea(area); + area = validateToolBarArea(area); layoutState.toolBarAreaLayout.addToolBarBreak(toDockPos(area)); if (savedState.isValid()) @@ -1365,7 +1532,7 @@ void QMainWindowLayout::addToolBar(Qt::ToolBarArea area, QToolBar *toolbar, bool) { - validateToolBarArea(area); + area = validateToolBarArea(area); // let's add the toolbar to the layout addChildWidget(toolbar); QLayoutItem *item = layoutState.toolBarAreaLayout.addToolBar(toDockPos(area), toolbar); @@ -1467,7 +1634,6 @@ inline static Qt::DockWidgetArea toDockWidgetArea(int pos) // Checks if QDockWidgetGroupWindow or QDockWidget can be plugged the area indicated by path. // Returns false if called with invalid widget type or if compiled without dockwidget support. -#if QT_CONFIG(dockwidget) static bool isAreaAllowed(QWidget *widget, const QList<int> &path) { Q_ASSERT_X((path.size() > 1), "isAreaAllowed", "invalid path size"); @@ -1502,7 +1668,6 @@ static bool isAreaAllowed(QWidget *widget, const QList<int> &path) qCDebug(lcQpaDockWidgets) << "Docking requested for invalid widget type (coding error)." << widget << area; return false; } -#endif void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area) { @@ -1522,7 +1687,6 @@ Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const // Returns the rectangle of a dockWidgetArea // if max is true, the maximum possible rectangle for dropping is returned // the current visible rectangle otherwise -#if QT_CONFIG(dockwidget) QRect QMainWindowLayout::dockWidgetAreaRect(const Qt::DockWidgetArea area, DockWidgetAreaSize size) const { const QInternal::DockPosition dockPosition = toDockPos(area); @@ -1538,7 +1702,6 @@ QRect QMainWindowLayout::dockWidgetAreaRect(const Qt::DockWidgetArea area, DockW // Return maximum or visible rectangle return (size == Maximum) ? dl.gapRect(dockPosition) : dl.docks[dockPosition].rect; } -#endif void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, @@ -1569,6 +1732,7 @@ bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget) #if QT_CONFIG(tabbar) void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second) { + applyRestoredState(); addChildWidget(second); layoutState.dockAreaLayout.tabifyDockWidget(first, second); emit second->dockLocationChanged(dockWidgetArea(first)); @@ -1651,19 +1815,7 @@ void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::Ta updateTabBarShapes(); } -static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position) -{ - const bool rounded = (shape == QTabWidget::Rounded); - if (position == QTabWidget::North) - return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth; - if (position == QTabWidget::South) - return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth; - if (position == QTabWidget::East) - return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast; - if (position == QTabWidget::West) - return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest; - return QTabBar::RoundedNorth; -} +QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position); #endif // QT_CONFIG(tabwidget) void QMainWindowLayout::updateTabBarShapes() @@ -1689,7 +1841,7 @@ void QMainWindowLayout::updateTabBarShapes() for (int i = 0; i < QInternal::DockCount; ++i) { #if QT_CONFIG(tabwidget) QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i]; - QTabBar::Shape shape = tabBarShapeFrom(_tabShape, pos); + QTabBar::Shape shape = _q_tb_tabBarShapeFrom(_tabShape, pos); #else QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth; #endif @@ -1702,6 +1854,7 @@ void QMainWindowLayout::splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, Qt::Orientation orientation) { + applyRestoredState(); addChildWidget(dockwidget); layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation); emit dockwidget->dockLocationChanged(dockWidgetArea(after)); @@ -1726,10 +1879,15 @@ void QMainWindowLayout::keepSize(QDockWidget *w) // Handle custom tooltip, and allow to drag tabs away. class QMainWindowTabBar : public QTabBar { + Q_OBJECT QMainWindow *mainWindow; QPointer<QDockWidget> draggingDock; // Currently dragging (detached) dock widget public: QMainWindowTabBar(QMainWindow *parent); + ~QMainWindowTabBar(); + QDockWidget *dockAt(int index) const; + QList<QDockWidget *> dockWidgets() const; + bool contains(const QDockWidget *dockWidget) const; protected: bool event(QEvent *e) override; void mouseReleaseEvent(QMouseEvent*) override; @@ -1737,12 +1895,65 @@ protected: }; +QMainWindowTabBar *QMainWindowLayout::findTabBar(const QDockWidget *dockWidget) const +{ + for (auto *bar : usedTabBars) { + Q_ASSERT(qobject_cast<QMainWindowTabBar *>(bar)); + auto *tabBar = static_cast<QMainWindowTabBar *>(bar); + if (tabBar->contains(dockWidget)) + return tabBar; + } + return nullptr; +} + QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent) : QTabBar(parent), mainWindow(parent) { setExpanding(false); } +QList<QDockWidget *> QMainWindowTabBar::dockWidgets() const +{ + QList<QDockWidget *> docks; + for (int i = 0; i < count(); ++i) { + if (QDockWidget *dock = dockAt(i)) + docks << dock; + } + return docks; +} + +bool QMainWindowTabBar::contains(const QDockWidget *dockWidget) const +{ + for (int i = 0; i < count(); ++i) { + if (dockAt(i) == dockWidget) + return true; + } + return false; +} + +// When a dock widget is removed from a floating tab, +// Events need to be processed for the tab bar to realize that the dock widget is gone. +// In this case count() counts the dock widget in transition and accesses dockAt +// with an out-of-bounds index. +// => return nullptr in contrast to other xxxxxAt() functions +QDockWidget *QMainWindowTabBar::dockAt(int index) const +{ + QMainWindowTabBar *that = const_cast<QMainWindowTabBar *>(this); + QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow); + QDockAreaLayoutInfo *info = mlayout->dockInfo(that); + if (!info) + return nullptr; + + const int itemIndex = info->tabIndexToListIndex(index); + if (itemIndex >= 0) { + Q_ASSERT(itemIndex < info->item_list.count()); + const QDockAreaLayoutItem &item = info->item_list.at(itemIndex); + return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr; + } + + return nullptr; +} + void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) { // The QTabBar handles the moving (reordering) of tabs. @@ -1756,13 +1967,8 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) offset *= 3; QRect r = rect().adjusted(-offset, -offset, offset, offset); if (d->dragInProgress && !r.contains(e->position().toPoint()) && d->validIndex(d->pressedIndex)) { - QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow); - QDockAreaLayoutInfo *info = mlayout->dockInfo(this); - Q_ASSERT(info); - int idx = info->tabIndexToListIndex(d->pressedIndex); - const QDockAreaLayoutItem &item = info->item_list.at(idx); - if (item.widgetItem - && (draggingDock = qobject_cast<QDockWidget *>(item.widgetItem->widget()))) { + draggingDock = dockAt(d->pressedIndex); + if (draggingDock) { // We should drag this QDockWidget away by unpluging it. // First cancel the QTabBar's internal move d->moveTabFinished(d->pressedIndex); @@ -1775,7 +1981,7 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout()); dockPriv->initDrag(dwlayout->titleArea().center(), true); - dockPriv->startDrag(false); + dockPriv->startDrag(QDockWidgetPrivate::DragScope::Widget); if (dockPriv->state) dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier; } @@ -1793,13 +1999,28 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) QTabBar::mouseMoveEvent(e); } +QMainWindowTabBar::~QMainWindowTabBar() +{ + if (!mainWindow || mainWindow == parentWidget()) + return; + + // tab bar is not parented to the main window + // => can only be a dock widget group window + // => remove itself from used and unused tab bar containers + auto *mwLayout = qt_mainwindow_layout(mainWindow); + if (!mwLayout) + return; + mwLayout->unusedTabBars.removeOne(this); + mwLayout->usedTabBars.remove(this); +} + void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e) { if (draggingDock && e->button() == Qt::LeftButton) { QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); - if (dockPriv->state && dockPriv->state->dragging) { - dockPriv->endDrag(); - } + if (dockPriv->state && dockPriv->state->dragging) + dockPriv->endDrag(QDockWidgetPrivate::EndDragMode::LocationChange); + draggingDock = nullptr; } QTabBar::mouseReleaseEvent(e); @@ -1823,9 +2044,31 @@ bool QMainWindowTabBar::event(QEvent *e) return true; } +QList<QDockWidget *> QMainWindowLayout::tabifiedDockWidgets(const QDockWidget *dockWidget) const +{ + const auto *bar = findTabBar(dockWidget); + if (!bar) + return {}; + + QList<QDockWidget *> buddies = bar->dockWidgets(); + // Return only other dock widgets associated with dockWidget in a tab bar. + // If dockWidget is alone in a tab bar, return an empty list. + buddies.removeOne(dockWidget); + return buddies; +} + +bool QMainWindowLayout::isDockWidgetTabbed(const QDockWidget *dockWidget) const +{ + // A single dock widget in a tab bar is not considered to be tabbed. + // This is to make sure, we don't drag an empty QDockWidgetGroupWindow around. + // => only consider tab bars with two or more tabs. + const auto *bar = findTabBar(dockWidget); + return bar && bar->count() > 1; +} + QTabBar *QMainWindowLayout::getTabBar() { - if (!usedTabBars.isEmpty()) { + if (!usedTabBars.isEmpty() && !isInRestoreState) { /* If dock widgets have been removed and added while the main window was hidden, then the layout hasn't been activated yet, and tab bars from empty @@ -1997,9 +2240,37 @@ QLayoutItem *QMainWindowLayout::takeAt(int index) return nullptr; } + +/*! + \internal + + restoredState stores what we earlier read from storage, but it couldn't + be applied as the mainwindow wasn't large enough (yet) to fit the state. + Usually, the restored state would be applied lazily in setGeometry below. + However, if the mainwindow's layout is modified (e.g. by a call to tabify or + splitDockWidgets), then we have to forget the restored state as it might contain + dangling pointers (QDockWidgetLayoutItem has a copy constructor that copies the + layout item pointer, and splitting or tabify might have to delete some of those + layout structures). + + Functions that might result in the QMainWindowLayoutState storing dangling pointers + have to call this function first, so that the restoredState becomes the actual state + first, and is forgotten afterwards. +*/ +void QMainWindowLayout::applyRestoredState() +{ + if (restoredState) { + layoutState = *restoredState; + restoredState.reset(); + discardRestoredStateTimer.stop(); + } +} + void QMainWindowLayout::setGeometry(const QRect &_r) { - if (savedState.isValid()) + // Check if the state is valid, and avoid replacing it again if it is currently used + // in applyState + if (savedState.isValid() || (restoredState && isInApplyState)) return; QRect r = _r; @@ -2535,25 +2806,25 @@ static QTabBar::Shape tabwidgetPositionToTabBarShape(QWidget *w) 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) +QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, QDockWidgetPrivate::DragScope scope) { #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget()); if (!widget->isWindow() && groupWindow) { - if (group && groupWindow->tabLayoutInfo()) { + if (scope == QDockWidgetPrivate::DragScope::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 if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) { QList<int> groupWindowPath = info->indexOf(widget->parentWidget()); return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem; } - qCDebug(lcQpaDockWidgets) << "Drag only:" << widget << "Group:" << group; + qCDebug(lcQpaDockWidgets) << "Drag only:" << widget << "Group:" << (scope == QDockWidgetPrivate::DragScope::Group); return nullptr; } QList<int> path = groupWindow->layoutInfo()->indexOf(widget); QDockAreaLayoutItem parentItem = groupWindow->layoutInfo()->item(path); QLayoutItem *item = parentItem.widgetItem; - if (group && path.size() > 1 + if (scope == QDockWidgetPrivate::DragScope::Group && path.size() > 1 && unplugGroup(this, &item, parentItem)) { qCDebug(lcQpaDockWidgets) << "Unplugging:" << widget << "from" << item; return item; @@ -2561,73 +2832,10 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) // We are unplugging a single dock widget from a floating window. QDockWidget *dockWidget = qobject_cast<QDockWidget *>(widget); Q_ASSERT(dockWidget); // cannot be a QDockWidgetGroupWindow because it's not floating. - - // unplug the widget first dockWidget->d_func()->unplug(widget->geometry()); - // Create a floating tab, copy properties and generate layout info - QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); - const QInternal::DockPosition dockPos = groupWindow->layoutInfo()->dockPos; - QDockAreaLayoutInfo *info = floatingTabs->layoutInfo(); - - const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dockWidget); - - // Populate newly created DockAreaLayoutInfo of floating tabs - *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPos, - Qt::Horizontal, shape, - layoutState.mainWindow); - - // Create tab and hide it as group window contains only one widget - info->tabbed = true; - info->tabBar = getTabBar(); - info->tabBar->hide(); - updateGapIndicator(); - - // Reparent it to a QDockWidgetGroupLayout - floatingTabs->setGeometry(dockWidget->geometry()); - - // Append reference to floatingTabs to the dock's item_list - parentItem.widgetItem = new QDockWidgetGroupWindowItem(floatingTabs); - layoutState.dockAreaLayout.docks[dockPos].item_list.append(parentItem); - - // use populated parentItem to set reference to dockWidget as the first item in own list - parentItem.widgetItem = new QDockWidgetItem(dockWidget); - info->item_list = {parentItem}; - - // Add non-gap items of the dock to the tab bar - for (const auto &listItem : layoutState.dockAreaLayout.docks[dockPos].item_list) { - if (listItem.GapItem || !listItem.widgetItem) - continue; - info->tabBar->addTab(listItem.widgetItem->widget()->objectName()); - } - - // Re-parent and fit - floatingTabs->setParent(layoutState.mainWindow); - floatingTabs->layoutInfo()->fitItems(); - floatingTabs->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks); - groupWindow->layoutInfo()->fitItems(); - groupWindow->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks); - dockWidget->d_func()->tabPosition = layoutState.mainWindow->tabPosition(toDockWidgetArea(dockPos)); - info->reparentWidgets(floatingTabs); - dockWidget->setParent(floatingTabs); - info->updateTabBar(); - - // Show the new item - const QList<int> path = layoutState.indexOf(floatingTabs); - QRect r = layoutState.itemRect(path); - savedState = layoutState; - savedState.fitLayout(); - - // Update gap, fix orientation, raise and show - currentGapPos = path; - currentGapRect = r; - updateGapIndicator(); - fixToolBarOrientation(parentItem.widgetItem, currentGapPos.at(1)); - floatingTabs->show(); - floatingTabs->raise(); - qCDebug(lcQpaDockWidgets) << "Unplugged from floating dock:" << widget << "from" << parentItem.widgetItem; - return parentItem.widgetItem; + return item; } } #endif @@ -2646,7 +2854,7 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) { Q_ASSERT(path.constFirst() == 1); #if QT_CONFIG(tabwidget) - if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3 + if (scope == QDockWidgetPrivate::DragScope::Group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3 && unplugGroup(this, &item, layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) { path.removeLast(); @@ -2654,21 +2862,36 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) } else #endif // QT_CONFIG(tabwidget) { - // Dock widget is unplugged from the main window - // => geometry needs to be adjusted by separator size + // Dock widget is unplugged from a main window dock + // => height or width need to be decreased by separator size switch (dockWidgetArea(dw)) { case Qt::LeftDockWidgetArea: case Qt::RightDockWidgetArea: - r.adjust(0, 0, 0, -sep); + r.setHeight(r.height() - sep); break; case Qt::TopDockWidgetArea: case Qt::BottomDockWidgetArea: - r.adjust(0, 0, -sep, 0); + r.setWidth(r.width() - sep); break; case Qt::NoDockWidgetArea: case Qt::DockWidgetArea_Mask: break; } + + // Depending on the title bar layout (vertical / horizontal), + // width and height have to provide minimum space for window handles + // and mouse dragging. + // Assuming horizontal title bar, if the dock widget does not have a layout. + const auto *layout = qobject_cast<QDockWidgetLayout *>(dw->layout()); + const bool verticalTitleBar = layout ? layout->verticalTitleBar : false; + const int tbHeight = QApplication::style() + ? QApplication::style()->pixelMetric(QStyle::PixelMetric::PM_TitleBarHeight, nullptr, dw) + : 20; + const int minHeight = verticalTitleBar ? 2 * tbHeight : tbHeight; + const int minWidth = verticalTitleBar ? tbHeight : 2 * tbHeight; + r.setSize(r.size().expandedTo(QSize(minWidth, minHeight))); + qCDebug(lcQpaDockWidgets) << dw << "will be unplugged with size" << r.size(); + dw->d_func()->unplug(r); } } @@ -2680,7 +2903,7 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group) #endif #if !QT_CONFIG(dockwidget) || !QT_CONFIG(tabbar) - Q_UNUSED(group); + Q_UNUSED(scope); #endif layoutState.unplug(path ,&savedState); @@ -2748,6 +2971,7 @@ void QMainWindowLayout::hover(QLayoutItem *hoverTarget, QWidget *widget = hoverTarget->widget(); #if QT_CONFIG(dockwidget) + widget->raise(); if ((dockOptions & QMainWindow::GroupedDragging) && (qobject_cast<QDockWidget*>(widget) || qobject_cast<QDockWidgetGroupWindow *>(widget))) { @@ -2764,7 +2988,7 @@ void QMainWindowLayout::hover(QLayoutItem *hoverTarget, continue; // Check permission to dock on another dock widget or floating dock - // FIXME in 6.4 + // FIXME in Qt 7 if (w != widget && w->isWindow() && w->isVisible() && !w->isMinimized()) candidates << w; @@ -2802,16 +3026,26 @@ void QMainWindowLayout::hover(QLayoutItem *hoverTarget, floatingTabs->setGeometry(dropTo->geometry()); QDockAreaLayoutInfo *info = floatingTabs->layoutInfo(); const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dropTo); - const QInternal::DockPosition dockPosition = toDockPos(dockWidgetArea(dropTo)); + + // dropTo and widget may be in a state where they transition + // from being a group window child to a single floating dock widget. + // In that case, their path to a main window dock may not have been + // updated yet. + // => ask both and fall back to dock 1 (right dock) + QInternal::DockPosition dockPosition = toDockPos(dockWidgetArea(dropTo)); + if (dockPosition == QInternal::DockPosition::DockCount) + dockPosition = toDockPos(dockWidgetArea(widget)); + if (dockPosition == QInternal::DockPosition::DockCount) + dockPosition = QInternal::DockPosition::RightDock; + *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPosition, Qt::Horizontal, shape, static_cast<QMainWindow *>(parentWidget())); info->tabBar = getTabBar(); info->tabbed = true; - QLayout *parentLayout = dropTo->parentWidget()->layout(); - info->item_list.append( - QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(dropTo)))); - + info->add(dropTo); + QDockAreaLayoutInfo &parentInfo = layoutState.dockAreaLayout.docks[dockPosition]; + parentInfo.add(floatingTabs); dropTo->setParent(floatingTabs); qCDebug(lcQpaDockWidgets) << "Wrapping" << widget << "into floating tabs" << floatingTabs; w = floatingTabs; @@ -2824,15 +3058,21 @@ void QMainWindowLayout::hover(QLayoutItem *hoverTarget, qCDebug(lcQpaDockWidgets) << "Raising" << widget; } #endif - auto group = qobject_cast<QDockWidgetGroupWindow *>(w); - Q_ASSERT(group); - if (group->hover(hoverTarget, group->mapFromGlobal(mousePos))) { - setCurrentHoveredFloat(group); + auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(w); + Q_ASSERT(groupWindow); + if (groupWindow->hover(hoverTarget, groupWindow->mapFromGlobal(mousePos))) { + setCurrentHoveredFloat(groupWindow); applyState(layoutState); // update the tabbars } return; } } + + // If a temporary group window has been created during a hover, + // remove it, if it has only one dockwidget child + if (currentHoveredFloat) + currentHoveredFloat->destroyIfSingleItemLeft(); + setCurrentHoveredFloat(nullptr); layoutState.dockAreaLayout.fallbackToSizeHints = false; #endif // QT_CONFIG(dockwidget) @@ -2957,6 +3197,7 @@ void QMainWindowLayout::saveState(QDataStream &stream) const bool QMainWindowLayout::restoreState(QDataStream &stream) { + QScopedValueRollback<bool> guard(isInRestoreState, true); savedState = layoutState; layoutState.clear(); layoutState.rect = savedState.rect; @@ -3002,6 +3243,42 @@ bool QMainWindowLayout::restoreState(QDataStream &stream) return true; } +#if QT_CONFIG(draganddrop) +bool QMainWindowLayout::needsPlatformDrag() +{ + static const bool wayland = + QGuiApplication::platformName().startsWith("wayland"_L1, Qt::CaseInsensitive); + return wayland; +} + +Qt::DropAction QMainWindowLayout::performPlatformWidgetDrag(QLayoutItem *widgetItem, + const QPoint &pressPosition) +{ + draggingWidget = widgetItem; + QWidget *widget = widgetItem->widget(); + auto drag = QDrag(widget); + auto mimeData = new QMimeData(); + auto window = widgetItem->widget()->windowHandle(); + + auto serialize = [](const auto &object) { + QByteArray data; + QDataStream dataStream(&data, QIODevice::WriteOnly); + dataStream << object; + return data; + }; + mimeData->setData("application/x-qt-mainwindowdrag-window"_L1, + serialize(reinterpret_cast<qintptr>(window))); + mimeData->setData("application/x-qt-mainwindowdrag-position"_L1, serialize(pressPosition)); + drag.setMimeData(mimeData); + + auto result = drag.exec(); + + draggingWidget = nullptr; + return result; +} +#endif + QT_END_NAMESPACE +#include "qmainwindowlayout.moc" #include "moc_qmainwindowlayout_p.cpp" |