summaryrefslogtreecommitdiffstats
path: root/src/widgets/widgets/qmainwindowlayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/widgets/qmainwindowlayout.cpp')
-rw-r--r--src/widgets/widgets/qmainwindowlayout.cpp574
1 files changed, 428 insertions, 146 deletions
diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp
index 4a199e0b18..db17e50d5c 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;
@@ -106,7 +112,7 @@ static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QSt
indent += " "_L1;
- for (int i = 0; i < layout.item_list.count(); ++i) {
+ for (int i = 0; i < layout.item_list.size(); ++i) {
qout << indent << "Item: " << i << '\n';
dumpLayout(qout, layout.item_list.at(i), indent + " "_L1);
}
@@ -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;
}
@@ -366,7 +412,7 @@ QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
}
}
if (!dw) {
- for (int i = 0; !dw && i < info->item_list.count(); ++i) {
+ for (int i = 0; !dw && i < info->item_list.size(); ++i) {
const QDockAreaLayoutItem &item = info->item_list.at(i);
if (item.skip())
continue;
@@ -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,10 +1634,9 @@ 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.count() > 1), "isAreaAllowed", "invalid path size");
+ Q_ASSERT_X((path.size() > 1), "isAreaAllowed", "invalid path size");
const Qt::DockWidgetArea area = toDockWidgetArea(path[1]);
// Read permissions directly from a single dock widget
@@ -1485,7 +1651,7 @@ static bool isAreaAllowed(QWidget *widget, const QList<int> &path)
if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
const QList<QDockWidget *> children = dwgw->findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly);
- if (children.count() == 1) {
+ if (children.size() == 1) {
// Group window has a single child => read its permissions
const bool allowed = children.at(0)->isAreaAllowed(area);
if (!allowed)
@@ -1493,7 +1659,7 @@ static bool isAreaAllowed(QWidget *widget, const QList<int> &path)
return allowed;
} else {
// Group window has more than one or no children => dock it anywhere
- qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "has" << children.count() << "children:";
+ qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "has" << children.size() << "children:";
qCDebug(lcQpaDockWidgets) << children;
qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "can dock at" << area << "and anywhere else.";
return true;
@@ -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));
@@ -1588,9 +1752,9 @@ void QMainWindowLayout::setDocumentMode(bool enabled)
_documentMode = enabled;
// Update the document mode for all tab bars
- for (QTabBar *bar : qAsConst(usedTabBars))
+ for (QTabBar *bar : std::as_const(usedTabBars))
bar->setDocumentMode(_documentMode);
- for (QTabBar *bar : qAsConst(unusedTabBars))
+ for (QTabBar *bar : std::as_const(unusedTabBars))
bar->setDocumentMode(_documentMode);
}
@@ -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,55 @@ 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;
+}
+
+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);
+ Q_ASSERT(itemIndex >= 0 && itemIndex < info->item_list.count());
+ const QDockAreaLayoutItem &item = info->item_list.at(itemIndex);
+ return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr;
+}
+
void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
{
// The QTabBar handles the moving (reordering) of tabs.
@@ -1756,13 +1957,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 +1971,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 +1989,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 +2034,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 +2230,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 +2796,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 +2822,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 +2844,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,6 +2852,36 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
} else
#endif // QT_CONFIG(tabwidget)
{
+ // 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.setHeight(r.height() - sep);
+ break;
+ case Qt::TopDockWidgetArea:
+ case Qt::BottomDockWidgetArea:
+ 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);
}
}
@@ -2665,7 +2893,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);
@@ -2733,6 +2961,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))) {
@@ -2749,7 +2978,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;
@@ -2787,18 +3016,28 @@ 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" << w << "into floating tabs" << floatingTabs;
+ qCDebug(lcQpaDockWidgets) << "Wrapping" << widget << "into floating tabs" << floatingTabs;
w = floatingTabs;
}
@@ -2809,15 +3048,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)
@@ -2942,6 +3187,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;
@@ -2977,7 +3223,7 @@ bool QMainWindowLayout::restoreState(QDataStream &stream)
#if QT_CONFIG(dockwidget)
if (parentWidget()->isVisible()) {
#if QT_CONFIG(tabbar)
- for (QTabBar *tab_bar : qAsConst(usedTabBars))
+ for (QTabBar *tab_bar : std::as_const(usedTabBars))
tab_bar->show();
#endif
@@ -2987,6 +3233,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"