diff --git a/README.md b/README.md index 0ba30937..de454543 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ know it from Visual Studio. - [Windows](#windows) - [macOS](#macos) - [Linux](#linux) + - [Wayland](#wayland) - [Build](#build) - [Qt5 on Ubuntu 18.04 or 20.04](#qt5-on-ubuntu-1804-or-2004) - [Qt5 on Ubuntu 22.04](#qt5-on-ubuntu-2204) @@ -408,12 +409,11 @@ the library switches to `QWidget` based title bars. - **Kubuntu 18.04 and 19.10** - uses KWin - no native title bars - **Ubuntu 18.04, 19.10 and 20.04** - native title bars are supported -- **Ubuntu 22.04** - uses Wayland -> no native title bars +- **Ubuntu 22.04 and later** - uses Wayland -> native title bars (see [Wayland](#wayland) below) There are some requirements for the Linux distribution that have to be met: -- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95). -- Wayland is not properly supported by Qt yet. If you use Wayland, then you should set the session type to x11: `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details about this in issue [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288). +- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95). On Wayland the dock overlays are rendered as child widgets and this requirement does not apply. Screenshot Kubuntu: ![Advanced Docking on Kubuntu Linux](doc/linux_kubuntu_1804.png) @@ -421,6 +421,23 @@ Screenshot Kubuntu: Screenshot Ubuntu: ![Advanced Docking on Ubuntu Linux](doc/linux_ubuntu_1910.png) +#### Wayland + +Since Qt 6.6.3 docking is supported on Wayland. Earlier Qt versions do not implement the `xdg_toplevel_drag_v1` protocol that is required to drag a floating window with the cursor, so on those versions you should still set the session type to X11 (XWayland): `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details in issues [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288) and [#714](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/714). + +Wayland does not allow a client to move its own top level windows in screen coordinates or to query the global cursor position, so docking is implemented differently than on the other platforms. This results in a few behavioral differences on Wayland: + +- Floating dock containers always use native window decorations (the custom + `QWidget` title bar is not available), because the custom title bar cannot + move the window. +- Undocking and re-docking use a compositor driven drag (the floating window + itself follows the cursor) instead of the translucent drag preview. +- A floating window's stylesheet is taken from the dock manager when the window + is created. Changing the application stylesheet while a window is already + floating does not update that window. +- The compositor controls the window stacking, so floating windows are not + forced to stay on top of the main window. + ## Build The Linux build requires private header files. Make sure that they are installed. diff --git a/src/DockAreaTitleBar.cpp b/src/DockAreaTitleBar.cpp index 7dd261f8..40ef72d8 100644 --- a/src/DockAreaTitleBar.cpp +++ b/src/DockAreaTitleBar.cpp @@ -291,7 +291,13 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset, { QSize Size = DockArea->size(); this->DragState = DragState; - bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState); + // On Wayland, the mouse tracked drag preview cannot work because the + // global cursor position is not available. Instead, we always create a + // real floating widget and hand it to a compositor driven platform drag + bool PlatformDrag = internal::isWayland() + && (DraggingFloatingWidget == DragState); + bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState) + || PlatformDrag; CFloatingDockContainer* FloatingDockContainer = nullptr; IFloatingWidget* FloatingWidget; if (CreateFloatingDockContainer) @@ -312,7 +318,8 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset, FloatingWidget = w; } - FloatingWidget->startFloating(Offset, Size, DragState, nullptr); + FloatingWidget->startFloating(Offset, Size, + PlatformDrag ? DraggingInactive : DragState, nullptr); if (FloatingDockContainer) { auto TopLevelDockWidget = FloatingDockContainer->topLevelDockWidget(); @@ -322,6 +329,15 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset, } } + // The platform drag blocks until the drag finishes and the floating + // widget is deleted, if it was docked into a drop area + if (PlatformDrag) + { + this->DragState = DraggingInactive; + CFloatingDockContainer::startPlatformDrag(FloatingDockContainer, + _this->mapToGlobal(Offset), _this); + } + return FloatingWidget; } @@ -334,7 +350,12 @@ void DockAreaTitleBarPrivate::startFloating(const QPoint& Offset) DockArea->autoHideDockContainer()->hide(); } FloatingWidget = makeAreaFloating(Offset, DraggingFloatingWidget); - qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent)); + // On Wayland, makeAreaFloating() blocks until the platform drag has + // finished, so there is no drag to report anymore + if (!internal::isWayland()) + { + qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent)); + } } @@ -699,9 +720,24 @@ void CDockAreaTitleBar::mouseMoveEvent(QMouseEvent* ev) // sense to move it to a new floating widget and leave this one // empty if (d->DockArea->dockContainer()->isFloating() - && d->DockArea->dockContainer()->visibleDockAreaCount() == 1 + && d->DockArea->dockContainer()->visibleDockAreaCount() == 1 && !d->DockArea->isAutoHide()) { + // On Wayland, dragging the title bar of the last dock area of a + // floating widget drags the existing floating widget, so the user + // can dock it into another container + int DragDistanceWayland = (d->DragStartMousePos - ev->pos()).manhattanLength(); + if (internal::isWayland() + && DragDistanceWayland >= CDockManager::startDragDistance()) + { + auto FloatingContainer = d->DockArea->dockContainer()->floatingWidget(); + if (FloatingContainer) + { + d->DragState = DraggingInactive; + CFloatingDockContainer::startPlatformDrag(FloatingContainer, + mapToGlobal(d->DragStartMousePos), this); + } + } return; } diff --git a/src/DockAreaWidget.cpp b/src/DockAreaWidget.cpp index 1f76c71b..61c94dcc 100644 --- a/src/DockAreaWidget.cpp +++ b/src/DockAreaWidget.cpp @@ -890,7 +890,12 @@ void CDockAreaWidget::updateTitleBarVisibility() if (!CDockManager::testConfigFlag(CDockManager::AlwaysShowTabs)) { bool Hidden = false; - if (!IsAutoHide) // Titlebar must always be visible when auto hidden so it can be dragged + // Wayland: the title bar of a floating container must always stay + // visible because it is the only handle that can drag the dock + // widget back into a dock container. The window decoration is owned + // by the compositor and only moves the window + bool IsWaylandFloating = internal::isWayland() && Container->isFloating(); + if (!IsAutoHide && !IsWaylandFloating) // Titlebar must always be visible when auto hidden so it can be dragged { if (Container->isFloating() || CDockManager::testConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar)) { diff --git a/src/DockContainerWidget.cpp b/src/DockContainerWidget.cpp index 6639275a..3d61d5c1 100644 --- a/src/DockContainerWidget.cpp +++ b/src/DockContainerWidget.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include "DockManager.h" @@ -186,6 +187,13 @@ class DockContainerWidgetPrivate */ void dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area); + /** + * Shows the drop overlays for the given floating widget that is dragged + * over this container by a drag and drop operation (Wayland platform + * drag) + */ + void updateDropOverlays(const QPoint& GlobalPos, CFloatingDockContainer* FloatingWidget); + /** * Creates a new tab for a widget dropped into the center of a section */ @@ -521,6 +529,80 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float } +//============================================================================ +void DockContainerWidgetPrivate::updateDropOverlays(const QPoint& GlobalPos, + CFloatingDockContainer* FloatingWidget) +{ + if (!DockManager) + { + return; + } + + // This container received the drag move event, so it is the drop target - + // unlike the mouse tracked dragging, there is no need to search the + // container under the cursor + bool ContentPinnable = FloatingWidget->dockContainer()->features().testFlag( + CDockWidget::DockWidgetPinnable); + CDockContainerWidget::showDropOverlays(DockManager, _this, GlobalPos, ContentPinnable); +} + + +//============================================================================ +void CDockContainerWidget::showDropOverlays(CDockManager* DockManager, + CDockContainerWidget* TopContainer, const QPoint& GlobalPos, + bool ContentPinnable) +{ + auto ContainerOverlay = DockManager->containerOverlay(); + auto DockAreaOverlay = DockManager->dockAreaOverlay(); + int VisibleDockAreas = TopContainer->visibleDockAreaCount(); + DockWidgetAreas AllowedContainerAreas = (VisibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; + auto DockArea = TopContainer->dockAreaAt(GlobalPos); + // If the dock container contains only one single DockArea, then we need + // to respect the allowed areas - only the center area is relevant here because + // all other allowed areas are from the container + if (VisibleDockAreas == 1 && DockArea) + { + AllowedContainerAreas.setFlag(CenterDockWidgetArea, DockArea->allowedAreas().testFlag(CenterDockWidgetArea)); + } + + if (ContentPinnable) + { + AllowedContainerAreas |= AutoHideDockAreas; + } + + ContainerOverlay->setAllowedAreas(AllowedContainerAreas); + + DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer, GlobalPos); + ContainerOverlay->enableDropPreview(ContainerArea != InvalidDockWidgetArea); + if (DockArea && DockArea->isVisible() && VisibleDockAreas > 0) + { + DockAreaOverlay->enableDropPreview(true); + DockAreaOverlay->setAllowedAreas( + (VisibleDockAreas == 1) ? NoDockWidgetArea : DockArea->allowedAreas()); + DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea, GlobalPos); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that + // the mouse is in the title bar. If the ContainerArea is valid + // then we ignore the dock area of the dockAreaOverlay() and disable + // the drop preview + if ((Area == CenterDockWidgetArea) + && (ContainerArea != InvalidDockWidgetArea)) + { + DockAreaOverlay->enableDropPreview(false); + ContainerOverlay->enableDropPreview(true); + } + else + { + ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area); + } + } + else + { + DockAreaOverlay->hideOverlay(); + } +} + + //============================================================================ void DockContainerWidgetPrivate::dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area) { @@ -1425,6 +1507,14 @@ CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *p createRootSplitter(); createSideTabBarWidgets(); } + + // On Wayland, floating widgets are docked via drag and drop events + // because the mouse tracked dragging that is used on the other platforms + // requires the global cursor position, which Wayland does not provide + if (internal::isWayland()) + { + setAcceptDrops(true); + } } @@ -1537,6 +1627,105 @@ bool CDockContainerWidget::event(QEvent *e) } +//============================================================================ +/** + * Returns the drop position of the given drop event in global coordinates + */ +static QPoint dropEventGlobalPos(QDropEvent* e, QWidget* Widget) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return Widget->mapToGlobal(e->position().toPoint()); +#else + return Widget->mapToGlobal(e->pos()); +#endif +} + + +//============================================================================ +CFloatingDockContainer* CDockContainerWidget::floatingWidgetFromDropEvent( + QDropEvent* e, CDockManager* DockManager) +{ + auto FloatingWidget = CFloatingDockContainer::floatingWidgetFromMimeData(e->mimeData()); + if (!FloatingWidget || !DockManager + || !DockManager->floatingWidgets().contains(FloatingWidget)) + { + return nullptr; + } + + return FloatingWidget; +} + + +//============================================================================ +void CDockContainerWidget::dragEnterEvent(QDragEnterEvent* e) +{ + auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager); + if (!FloatingWidget || FloatingWidget->dockContainer() == this) + { + QFrame::dragEnterEvent(e); + return; + } + + e->acceptProposedAction(); +} + + +//============================================================================ +void CDockContainerWidget::dragMoveEvent(QDragMoveEvent* e) +{ + auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager); + if (!FloatingWidget || FloatingWidget->dockContainer() == this) + { + QFrame::dragMoveEvent(e); + return; + } + + e->acceptProposedAction(); + const QPoint GlobalPos = dropEventGlobalPos(e, this); + d->updateDropOverlays(GlobalPos, FloatingWidget); + + // Record the drop candidate so the drag source can dock the floating + // widget if the compositor does not deliver a drop event (see + // CFloatingDockContainer::startPlatformDrag) + bool ValidDropArea = + (d->DockManager->containerOverlay()->visibleDropAreaUnderCursor(GlobalPos) != InvalidDockWidgetArea) + || (d->DockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(GlobalPos) != InvalidDockWidgetArea); + CFloatingDockContainer::platformDragUpdateDropCandidate(this, GlobalPos, ValidDropArea); +} + + +//============================================================================ +void CDockContainerWidget::dragLeaveEvent(QDragLeaveEvent* e) +{ + Q_UNUSED(e) + if (!d->DockManager) + { + return; + } + + d->DockManager->containerOverlay()->hideOverlay(); + d->DockManager->dockAreaOverlay()->hideOverlay(); +} + + +//============================================================================ +void CDockContainerWidget::dropEvent(QDropEvent* e) +{ + auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager); + if (!FloatingWidget || FloatingWidget->dockContainer() == this) + { + QFrame::dropEvent(e); + return; + } + + CFloatingDockContainer::platformDragNotifyDropHandled(); + dropFloatingWidget(FloatingWidget, dropEventGlobalPos(e, this)); + d->DockManager->containerOverlay()->hideOverlay(); + d->DockManager->dockAreaOverlay()->hideOverlay(); + e->acceptProposedAction(); +} + + //============================================================================ QList CDockContainerWidget::autoHideWidgets() const { @@ -1726,7 +1915,7 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi CDockWidget* SingleDroppedDockWidget = FloatingWidget->topLevelDockWidget(); CDockWidget* SingleDockWidget = topLevelDockWidget(); auto dropArea = InvalidDockWidgetArea; - auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(); + auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(TargetPos); bool Dropped = false; CDockAreaWidget* DockArea = dockAreaAt(TargetPos); @@ -1735,7 +1924,7 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi { auto dropOverlay = d->DockManager->dockAreaOverlay(); dropOverlay->setAllowedAreas(DockArea->allowedAreas()); - dropArea = dropOverlay->showOverlay(DockArea); + dropArea = dropOverlay->showOverlay(DockArea, TargetPos); if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) { diff --git a/src/DockContainerWidget.h b/src/DockContainerWidget.h index 1667b86e..177127ce 100644 --- a/src/DockContainerWidget.h +++ b/src/DockContainerWidget.h @@ -95,6 +95,54 @@ private Q_SLOTS: */ virtual bool event(QEvent *e) override; + /** + * The drag and drop events below implement docking of floating widgets + * on Wayland, where CFloatingDockContainer::startPlatformDrag() executes + * a compositor driven drag instead of the mouse tracked dragging that is + * used on the other platforms. + * Accepts the drag, if it transports a floating widget of the same + * dock manager + */ + virtual void dragEnterEvent(QDragEnterEvent* e) override; + + /** + * Updates the drop overlays for the drag position + */ + virtual void dragMoveEvent(QDragMoveEvent* e) override; + + /** + * Hides the drop overlays, if the drag leaves this container + */ + virtual void dragLeaveEvent(QDragLeaveEvent* e) override; + + /** + * Docks the dragged floating widget into the drop area under the drop + * position + */ + virtual void dropEvent(QDropEvent* e) override; + + /** + * Returns the floating widget transported by the given platform drag + * and drop event, if the drag was started by this application and the + * floating widget belongs to the given dock manager; otherwise nullptr. + * The floating widget pointer from the mime data is only dereferenced + * after it was found in the dock manager floating widget list, so drags + * from other applications cannot inject an invalid pointer + */ + static CFloatingDockContainer* floatingWidgetFromDropEvent(QDropEvent* e, + CDockManager* DockManager); + + /** + * Configures and shows the container and dock area drop overlays for a + * drag that hovers over TopContainer at the given global position. + * Shared by the mouse tracked dragging (CFloatingDragPreview / + * CFloatingDockContainer) and the Wayland platform drag and drop. + * ContentPinnable is true, if the dragged content can be auto hidden. + */ + static void showDropOverlays(CDockManager* DockManager, + CDockContainerWidget* TopContainer, const QPoint& GlobalPos, + bool ContentPinnable); + /** * Access function for the internal root splitter */ diff --git a/src/DockManager.cpp b/src/DockManager.cpp index 111f065c..f348c20f 100644 --- a/src/DockManager.cpp +++ b/src/DockManager.cpp @@ -661,8 +661,14 @@ bool CDockManager::eventFilter(QObject *obj, QEvent *e) // Emulate Qt:Tool behaviour. // Required because on some WMs Tool windows can't be maximized. + // Wayland: skip the stays-on-top emulation. The compositor owns the + // window stacking, and changing the window flags of a shown window + // recreates its surface, which would detach a running platform drag. + // The minimize synchronization below does not change window flags and + // is kept. + // Window always on top of the MainWindow. - if (e->type() == QEvent::WindowActivate) + if (!internal::isWayland() && e->type() == QEvent::WindowActivate) { for (auto _window : d->FloatingWidgets) { @@ -684,7 +690,7 @@ bool CDockManager::eventFilter(QObject *obj, QEvent *e) } } } - else if (e->type() == QEvent::WindowDeactivate) + else if (!internal::isWayland() && e->type() == QEvent::WindowDeactivate) { for (auto _window : d->FloatingWidgets) { diff --git a/src/DockOverlay.cpp b/src/DockOverlay.cpp index 3ae43a2d..7f423ec7 100644 --- a/src/DockOverlay.cpp +++ b/src/DockOverlay.cpp @@ -65,6 +65,10 @@ struct DockOverlayPrivate CDockOverlay::eMode Mode = CDockOverlay::ModeDockAreaOverlay; QRect DropAreaRect; int TabIndex = InvalidTabIndex; + // Wayland: the global cursor position is not available, so the last drop + // position from the drag and drop events is cached here to drive the + // drop preview rectangle and the highlighted drop indicator + QPoint LastGlobalPos; /** * Private data constructor @@ -398,20 +402,36 @@ int DockOverlayPrivate::sideBarMouseZone(SideBarLocation sideBarLocation) //============================================================================ CDockOverlay::CDockOverlay(QWidget* parent, eMode Mode) : - QFrame(parent), + // Wayland: the overlay must be a child widget of the target window. Top + // level windows cannot be positioned in screen coordinates on Wayland, + // so a Qt::Tool overlay would not align over the target dock area + QFrame(internal::isWayland() && parent ? parent->window() : parent), d(new DockOverlayPrivate(this)) { d->Mode = Mode; d->Cross = new CDockOverlayCross(this); + if (internal::isWayland()) + { + // A translucent child widget would become a native window + // (wl_subsurface) that takes part in the compositors drag and drop + // target picking and steals the drop focus from the dock container. + // A child widget composites with its parent without the attribute. + // WA_TransparentForMouseEvents lets a release on a drop indicator + // fall through to it + setAttribute(Qt::WA_TransparentForMouseEvents); + } + else + { #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); #else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); #endif - setWindowOpacity(1); + setWindowOpacity(1); + setAttribute(Qt::WA_TranslucentBackground); + } setWindowTitle("DockOverlay"); setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); d->Cross->setVisible(false); setVisible(false); @@ -459,24 +479,32 @@ DockWidgetAreas CDockOverlay::allowedAreas() const //============================================================================ DockWidgetArea CDockOverlay::dropAreaUnderCursor() const { + return dropAreaUnderCursor(QCursor::pos()); +} + + +//============================================================================ +DockWidgetArea CDockOverlay::dropAreaUnderCursor(const QPoint& GlobalPos) const +{ + d->LastGlobalPos = GlobalPos; d->TabIndex = InvalidTabIndex; if (!d->TargetWidget) { return InvalidDockWidgetArea; } - DockWidgetArea Result = d->Cross->cursorLocation(); + DockWidgetArea Result = d->Cross->cursorLocation(GlobalPos); if (Result != InvalidDockWidgetArea) { return Result; } - auto CursorPos = QCursor::pos(); + auto CursorPos = GlobalPos; auto DockArea = qobject_cast(d->TargetWidget.data()); if (!DockArea && CDockManager::autoHideConfigFlags().testFlag(CDockManager::AutoHideFeatureEnabled)) { auto Rect = rect(); - const QPoint pos = mapFromGlobal(QCursor::pos()); + const QPoint pos = mapFromGlobal(GlobalPos); if ((pos.x() < d->sideBarMouseZone(SideBarLeft)) && d->AllowedAreas.testFlag(LeftAutoHideArea)) { @@ -537,6 +565,13 @@ int CDockOverlay::tabIndexUnderCursor() const //============================================================================ DockWidgetArea CDockOverlay::visibleDropAreaUnderCursor() const +{ + return visibleDropAreaUnderCursor(QCursor::pos()); +} + + +//============================================================================ +DockWidgetArea CDockOverlay::visibleDropAreaUnderCursor(const QPoint& GlobalPos) const { if (isHidden() || !d->DropPreviewEnabled) { @@ -544,7 +579,7 @@ DockWidgetArea CDockOverlay::visibleDropAreaUnderCursor() const } else { - return dropAreaUnderCursor(); + return dropAreaUnderCursor(GlobalPos); } } @@ -552,10 +587,27 @@ DockWidgetArea CDockOverlay::visibleDropAreaUnderCursor() const //============================================================================ DockWidgetArea CDockOverlay::showOverlay(QWidget* target) { + return showOverlay(target, QCursor::pos()); +} + + +//============================================================================ +DockWidgetArea CDockOverlay::showOverlay(QWidget* target, const QPoint& GlobalPos) +{ + // Wayland: a cross window drop (e.g. into a floating widget) requires the + // overlay to be a child of the target window because a widget cannot be + // positioned over a foreign window + if (internal::isWayland() && parentWidget() != target->window()) + { + setParent(target->window()); + d->Cross->setParent(target->window()); + d->TargetWidget = nullptr; + } + if (d->TargetWidget == target) { // Hint: We could update geometry of overlay here. - DockWidgetArea da = dropAreaUnderCursor(); + DockWidgetArea da = dropAreaUnderCursor(GlobalPos); if (da != d->LastLocation) { repaint(); @@ -571,11 +623,25 @@ DockWidgetArea CDockOverlay::showOverlay(QWidget* target) hide(); resize(target->size()); QPoint TopLeft = target->mapToGlobal(target->rect().topLeft()); - move(TopLeft); + // Wayland: the overlay is a child of the target window, so the target top + // left has to be mapped into the parent coordinate system + if (internal::isWayland() && parentWidget()) + { + move(parentWidget()->mapFromGlobal(TopLeft)); + } + else + { + move(TopLeft); + } show(); + if (internal::isWayland()) + { + // paint the child overlay above its sibling dock widgets + raise(); + } d->Cross->updatePosition(); d->Cross->updateOverlayIcons(); - return dropAreaUnderCursor(); + return dropAreaUnderCursor(GlobalPos); } @@ -617,7 +683,10 @@ void CDockOverlay::paintEvent(QPaintEvent* event) } QRect r = rect(); - const DockWidgetArea da = dropAreaUnderCursor(); + // Wayland: use the cached drag position because the global cursor + // position is not available during a platform drag + const DockWidgetArea da = internal::isWayland() + ? dropAreaUnderCursor(d->LastGlobalPos) : dropAreaUnderCursor(); double Factor = (CDockOverlay::ModeContainerOverlay == d->Mode) ? 3 : 2; @@ -734,17 +803,27 @@ QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) //============================================================================ CDockOverlayCross::CDockOverlayCross(CDockOverlay* overlay) : - QWidget(overlay->parentWidget()), + // Wayland: see CDockOverlay - the cross must be a child of the target + // window for the same reason as the overlay + QWidget(internal::isWayland() && overlay->parentWidget() + ? overlay->parentWidget()->window() : overlay->parentWidget()), d(new DockOverlayCrossPrivate(this)) { d->DockOverlay = overlay; + if (internal::isWayland()) + { + setAttribute(Qt::WA_TransparentForMouseEvents); + } + else + { #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); #else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); #endif + setAttribute(Qt::WA_TranslucentBackground); + } setWindowTitle("DockOverlayCross"); - setAttribute(Qt::WA_TranslucentBackground); d->GridLayout = new QGridLayout(); d->GridLayout->setSpacing(0); @@ -784,7 +863,11 @@ void CDockOverlayCross::setupOverlayCross(CDockOverlay::eMode Mode) //============================================================================ void CDockOverlayCross::updateOverlayIcons() { - if (windowHandle()->devicePixelRatio() == d->LastDevicePixelRatio) + // Wayland: the cross is a child widget and has no window handle, so the + // device pixel ratio is taken from the widget instead of the window + const qreal DevicePixelRatio = internal::isWayland() + ? devicePixelRatioF() : windowHandle()->devicePixelRatio(); + if (DevicePixelRatio == d->LastDevicePixelRatio) { return; } @@ -879,7 +962,14 @@ void CDockOverlayCross::setAreaWidgets(const QHash& wi //============================================================================ DockWidgetArea CDockOverlayCross::cursorLocation() const { - const QPoint pos = mapFromGlobal(QCursor::pos()); + return cursorLocation(QCursor::pos()); +} + + +//============================================================================ +DockWidgetArea CDockOverlayCross::cursorLocation(const QPoint& GlobalPos) const +{ + const QPoint pos = mapFromGlobal(GlobalPos); QHashIterator i(d->DropIndicatorWidgets); while (i.hasNext()) { @@ -916,6 +1006,11 @@ void CDockOverlayCross::updatePosition() (this->height() - d->DockOverlay->height()) / 2); QPoint CrossTopLeft = TopLeft - Offest; move(CrossTopLeft); + if (internal::isWayland()) + { + // paint the child cross above its sibling dock widgets + raise(); + } } diff --git a/src/DockOverlay.h b/src/DockOverlay.h index 36376f59..3dcd8e81 100644 --- a/src/DockOverlay.h +++ b/src/DockOverlay.h @@ -87,6 +87,14 @@ class ADS_EXPORT CDockOverlay : public QFrame */ DockWidgetArea dropAreaUnderCursor() const; + /** + * Returns the drop area under the given global position. + * Use this overload if the position does not come from the mouse cursor + * (e.g. from drag and drop events on Wayland, where the global cursor + * position is not available) + */ + DockWidgetArea dropAreaUnderCursor(const QPoint& GlobalPos) const; + /** * If the drop area is the CenterDockWidgetArea or a sidebar area, * then this function returns the index of the tab under cursor. @@ -105,11 +113,24 @@ class ADS_EXPORT CDockOverlay : public QFrame */ DockWidgetArea visibleDropAreaUnderCursor() const; + /** + * This function returns the same like dropAreaUnderCursor(GlobalPos) if + * this overlay is not hidden and if drop preview is enabled and returns + * InvalidDockWidgetArea if it is hidden or drop preview is disabled. + */ + DockWidgetArea visibleDropAreaUnderCursor(const QPoint& GlobalPos) const; + /** * Show the drop overly for the given target widget */ DockWidgetArea showOverlay(QWidget* target); + /** + * Show the drop overlay for the given target widget and evaluate the + * drop area for the given global position instead of the cursor position + */ + DockWidgetArea showOverlay(QWidget* target, const QPoint& GlobalPos); + /** * Hides the overlay */ @@ -238,6 +259,12 @@ class CDockOverlayCross : public QWidget */ DockWidgetArea cursorLocation() const; + /** + * The function checks, if the given global position is inside of any + * drop indicator widget and returns the corresponding DockWidgetArea. + */ + DockWidgetArea cursorLocation(const QPoint& GlobalPos) const; + /** * Sets up the overlay cross for the given overlay mode */ diff --git a/src/DockWidgetTab.cpp b/src/DockWidgetTab.cpp index 943a8c61..25cb83d7 100644 --- a/src/DockWidgetTab.cpp +++ b/src/DockWidgetTab.cpp @@ -311,13 +311,32 @@ bool DockWidgetTabPrivate::startFloating(eDragState DraggingState) && (dockContainer->visibleDockAreaCount() == 1) && (DockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) { + // On Wayland, dragging the tab of the single dock widget of a + // floating widget drags the existing floating widget, so the user + // can dock it into another container + if (internal::isWayland() && (DraggingFloatingWidget == DraggingState)) + { + auto FloatingContainer = dockContainer->floatingWidget(); + if (FloatingContainer) + { + DragState = DraggingInactive; + CFloatingDockContainer::startPlatformDrag(FloatingContainer, + GlobalDragStartMousePosition, _this); + return true; + } + } return false; } ADS_PRINT("startFloating"); DragState = DraggingState; IFloatingWidget* FloatingWidget = nullptr; - bool CreateContainer = (DraggingFloatingWidget != DraggingState); + // On Wayland, the mouse tracked drag preview cannot work because the + // global cursor position is not available. Instead, we always create a + // real floating widget and hand it to a compositor driven platform drag + bool PlatformDrag = internal::isWayland() + && (DraggingFloatingWidget == DraggingState); + bool CreateContainer = (DraggingFloatingWidget != DraggingState) || PlatformDrag; // If section widget has multiple tabs, we take only one tab // If it has only one single tab, we can move the complete @@ -334,7 +353,15 @@ bool DockWidgetTabPrivate::startFloating(eDragState DraggingState) Size = DockArea->size(); } - if (DraggingFloatingWidget == DraggingState) + if (PlatformDrag) + { + FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingInactive, nullptr); + DragState = DraggingInactive; + CFloatingDockContainer::startPlatformDrag( + static_cast(FloatingWidget), + GlobalDragStartMousePosition, _this); + } + else if (DraggingFloatingWidget == DraggingState) { FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingFloatingWidget, _this); auto DockManager = DockWidget->dockManager(); @@ -478,10 +505,14 @@ void CDockWidgetTab::mouseMoveEvent(QMouseEvent* ev) { // If this is the last dock area in a dock container with only // one single dock widget it does not make sense to move it to a new - // floating widget and leave this one empty + // floating widget and leave this one empty. + // On Wayland we fall through, because dragging the tab drags the + // existing floating widget so the user can dock it into another + // container if (d->DockArea->dockContainer()->isFloating() && d->DockArea->openDockWidgetsCount() == 1 - && d->DockArea->dockContainer()->visibleDockAreaCount() == 1) + && d->DockArea->dockContainer()->visibleDockAreaCount() == 1 + && !internal::isWayland()) { return; } diff --git a/src/FloatingDockContainer.cpp b/src/FloatingDockContainer.cpp index 408bbbf0..39eb4295 100644 --- a/src/FloatingDockContainer.cpp +++ b/src/FloatingDockContainer.cpp @@ -39,6 +39,10 @@ #include #include #include +#include +#include +#include +#include #include "DockContainerWidget.h" #include "DockAreaWidget.h" @@ -583,62 +587,15 @@ void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &GlobalPos) } DropContainer = TopContainer; - auto ContainerOverlay = DockManager->containerOverlay(); - auto DockAreaOverlay = DockManager->dockAreaOverlay(); - if (!TopContainer) { - ContainerOverlay->hideOverlay(); - DockAreaOverlay->hideOverlay(); + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); return; } - int VisibleDockAreas = TopContainer->visibleDockAreaCount(); - DockWidgetAreas AllowedContainerAreas = (VisibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; - auto DockArea = TopContainer->dockAreaAt(GlobalPos); - // If the dock container contains only one single DockArea, then we need - // to respect the allowed areas - only the center area is relevant here because - // all other allowed areas are from the container - if (VisibleDockAreas == 1 && DockArea) - { - AllowedContainerAreas.setFlag(CenterDockWidgetArea, DockArea->allowedAreas().testFlag(CenterDockWidgetArea)); - } - - if (DockContainer->features().testFlag(CDockWidget::DockWidgetPinnable)) - { - AllowedContainerAreas |= AutoHideDockAreas; - } - - ContainerOverlay->setAllowedAreas(AllowedContainerAreas); - - DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer); - ContainerOverlay->enableDropPreview(ContainerArea != InvalidDockWidgetArea); - if (DockArea && DockArea->isVisible() && VisibleDockAreas > 0) - { - DockAreaOverlay->enableDropPreview(true); - DockAreaOverlay->setAllowedAreas( - (VisibleDockAreas == 1) ? NoDockWidgetArea : DockArea->allowedAreas()); - DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea); - - // A CenterDockWidgetArea for the dockAreaOverlay() indicates that - // the mouse is in the title bar. If the ContainerArea is valid - // then we ignore the dock area of the dockAreaOverlay() and disable - // the drop preview - if ((Area == CenterDockWidgetArea) - && (ContainerArea != InvalidDockWidgetArea)) - { - DockAreaOverlay->enableDropPreview(false); - ContainerOverlay->enableDropPreview(true); - } - else - { - ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area); - } - } - else - { - DockAreaOverlay->hideOverlay(); - } + CDockContainerWidget::showDropOverlays(DockManager, TopContainer, GlobalPos, + DockContainer->features().testFlag(CDockWidget::DockWidgetPinnable)); } @@ -653,8 +610,16 @@ void FloatingDockContainerPrivate::handleEscapeKey() //============================================================================ +// Wayland: a floating widget that is a child of the dock manager makes Qt +// turn the dock manager and all of its child widgets into native windows +// when the floating widget is shown. Native child widgets are +// wl_subsurfaces that take part in the compositors drag and drop target +// picking and steal the drop focus from the dock container during a +// platform drag. The dock manager deletes the registered floating widgets +// in its destructor, so the floating widget does not need a parent for +// memory management. CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : - tFloatingWidgetBase(DockManager), + tFloatingWidgetBase(internal::isWayland() ? nullptr : static_cast(DockManager)), d(new FloatingDockContainerPrivate(this)) { d->DockManager = DockManager; @@ -696,16 +661,14 @@ CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : native_window = window_manager != "KWIN"; } - if (native_window) - { - // Native windows do not work if wayland is used. Ubuntu 22.04 uses wayland by default. To use - // native windows, switch to Xorg - QString XdgSessionType = qgetenv("XDG_SESSION_TYPE").toLower(); - if ("wayland" == XdgSessionType) - { - native_window = false; - } - } + // Wayland does not allow clients to move windows, so the custom title + // bar cannot work there - dragging it would not move the window. Always + // use a native window so that the user can move the floating widget + // via the window decoration provided by the compositor or by Qt. + if (internal::isWayland()) + { + native_window = true; + } if (native_window) { @@ -737,6 +700,27 @@ CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : winId(); } + // Wayland: the floating widget has no parent widget (see above), so it + // does not inherit the dock manager's effective style sheet through the + // widget hierarchy. Apply the style sheets along the dock manager parent + // chain explicitly, so the floating widget looks like the docked content. + if (internal::isWayland()) + { + QString StyleSheet; + for (QWidget* Widget = DockManager; Widget; Widget = Widget->parentWidget()) + { + const QString WidgetStyleSheet = Widget->styleSheet(); + if (!WidgetStyleSheet.isEmpty()) + { + StyleSheet = WidgetStyleSheet + QLatin1Char('\n') + StyleSheet; + } + } + if (!StyleSheet.isEmpty()) + { + setStyleSheet(StyleSheet); + } + } + DockManager->registerFloatingWidget(this); } @@ -1250,6 +1234,157 @@ void CFloatingDockContainer::finishDragging() d->titleMouseReleaseEvent(); } + +//============================================================================ +// MIME type that transports the CFloatingDockContainer pointer between the +// drag source and the CDockContainerWidget drop targets within this +// application +static const char* const FloatingWidgetMimeType = "application/x-ads-floating-dock-container"; + +// MIME types that QWaylandDataDevice translates into an +// xdg_toplevel_drag_v1 request so that the compositor attaches the floating +// widget window to the drag cursor. The format matches +// QMainWindowLayout::performPlatformWidgetDrag() in qtbase +static const char* const PlatformDragWindowMimeType = "application/x-qt-mainwindowdrag-window"; +static const char* const PlatformDragPositionMimeType = "application/x-qt-mainwindowdrag-position"; + +// Maximum time to wait for the floating widget surface to be exposed before +// starting the platform drag +static const int PlatformDragExposureTimeoutMs = 250; + +// Maximum age of the recorded drop candidate that startPlatformDrag() still +// accepts as a drop. It must be longer than the interval between drag move +// events (so a drop right after the last move counts) but short enough to +// reject a release far away from the last drop area +static const int PlatformDragDropCandidateTimeoutMs = 400; + + +// Records the last drop candidate during a Wayland platform drag. Some +// compositors do not deliver a drop event when the dragged window overlaps +// the drop target (they send a leave event instead), so startPlatformDrag() +// falls back to this candidate when no drop event was delivered. +struct PlatformDragDropCandidate +{ + QPointer Container; + QPoint GlobalPos; + bool ValidDropArea = false; + bool DropHandled = false; + QElapsedTimer SinceLastUpdate; +}; +static PlatformDragDropCandidate s_PlatformDragDropCandidate; + + +//============================================================================ +void CFloatingDockContainer::platformDragUpdateDropCandidate( + CDockContainerWidget* Container, const QPoint& GlobalPos, bool ValidDropArea) +{ + s_PlatformDragDropCandidate.Container = Container; + s_PlatformDragDropCandidate.GlobalPos = GlobalPos; + s_PlatformDragDropCandidate.ValidDropArea = ValidDropArea; + s_PlatformDragDropCandidate.SinceLastUpdate.start(); +} + + +//============================================================================ +void CFloatingDockContainer::platformDragNotifyDropHandled() +{ + s_PlatformDragDropCandidate.DropHandled = true; +} + + +//============================================================================ +Qt::DropAction CFloatingDockContainer::startPlatformDrag( + CFloatingDockContainer* FloatingWidget, const QPoint& GlobalPressPos, + QWidget* DragSource) +{ + auto serialize = [](const auto &object) + { + QByteArray Data; + QDataStream DataStream(&Data, QIODevice::WriteOnly); + DataStream << object; + return Data; + }; + + // The compositor can only attach the floating widget to the drag cursor + // after the window surface has been created and committed with the + // xdg_toplevel role. The floating widget was just shown, so process + // events until it is exposed + QElapsedTimer ExposureTimer; + ExposureTimer.start(); + while (FloatingWidget->windowHandle() + && !FloatingWidget->windowHandle()->isExposed() + && ExposureTimer.elapsed() < PlatformDragExposureTimeoutMs) + { + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + } + + // The offset is the press position relative to the floating widget top + // left corner, so the compositor keeps the cursor at the same spot on + // the window during the drag + const QPoint DragStartOffset = FloatingWidget->mapFromGlobal(GlobalPressPos); + + auto Drag = new QDrag(DragSource); + auto MimeData = new QMimeData(); + MimeData->setData(QLatin1String(PlatformDragWindowMimeType), + serialize(reinterpret_cast(FloatingWidget->windowHandle()))); + MimeData->setData(QLatin1String(PlatformDragPositionMimeType), + serialize(DragStartOffset)); + MimeData->setData(QLatin1String(FloatingWidgetMimeType), + serialize(reinterpret_cast(FloatingWidget))); + Drag->setMimeData(MimeData); + + // The drop targets dock the floating widget in their dropEvent() handler, + // which deletes the floating widget before exec() returns + QPointer GuardedFloatingWidget(FloatingWidget); + auto DockManager = FloatingWidget->dockContainer()->dockManager(); + s_PlatformDragDropCandidate = PlatformDragDropCandidate(); + Qt::DropAction Result = Drag->exec(); + + // Some compositors (e.g. Mutter) do not deliver a drop event to the + // target container when the dragged window overlaps it - they send a + // leave event on release instead. If no drop event was delivered, but + // the drag ended over a valid drop area, dock the floating widget into + // the recorded candidate. A short timeout distinguishes a drop on a + // drop area from a release outside of any drop area to keep the widget + // floating + auto& Candidate = s_PlatformDragDropCandidate; + if (!Candidate.DropHandled && !GuardedFloatingWidget.isNull() + && Candidate.Container && Candidate.ValidDropArea + && Candidate.SinceLastUpdate.isValid() + && Candidate.SinceLastUpdate.elapsed() < PlatformDragDropCandidateTimeoutMs) + { + Candidate.Container->dropFloatingWidget(GuardedFloatingWidget, + Candidate.GlobalPos); + } + s_PlatformDragDropCandidate = PlatformDragDropCandidate(); + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); + Drag->deleteLater(); + + if (GuardedFloatingWidget) + { + GuardedFloatingWidget->d->setState(DraggingInactive); + } + + return Result; +} + + +//============================================================================ +CFloatingDockContainer* CFloatingDockContainer::floatingWidgetFromMimeData( + const QMimeData* MimeData) +{ + if (!MimeData || !MimeData->hasFormat(QLatin1String(FloatingWidgetMimeType))) + { + return nullptr; + } + + qintptr FloatingWidgetPtr = 0; + QDataStream DataStream(MimeData->data(QLatin1String(FloatingWidgetMimeType))); + DataStream >> FloatingWidgetPtr; + return reinterpret_cast(FloatingWidgetPtr); +} + #ifdef Q_OS_MACOS //============================================================================ bool CFloatingDockContainer::event(QEvent *e) @@ -1428,7 +1563,12 @@ void CFloatingDockContainer::resizeEvent(QResizeEvent *event) void CFloatingDockContainer::moveEvent(QMoveEvent *event) { Super::moveEvent(event); - if (!d->IsResizing && event->spontaneous() && d->MousePressed) + // On Wayland, the global cursor position is not available and the + // compositor does not report window moves, so the docking via window + // moves cannot work. Floating widgets are docked via the platform drag + // and drop implemented in CDockContainerWidget instead + if (!d->IsResizing && event->spontaneous() && d->MousePressed + && !internal::isWayland()) { d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); diff --git a/src/FloatingDockContainer.h b/src/FloatingDockContainer.h index 52991cc2..7ff575e6 100644 --- a/src/FloatingDockContainer.h +++ b/src/FloatingDockContainer.h @@ -42,6 +42,7 @@ #endif class CDockingStateReader; +class QMimeData; namespace ads { @@ -117,6 +118,31 @@ class ADS_EXPORT CFloatingDockContainer : public tFloatingWidgetBase, public IFl friend class CDockWidget; friend class CDockAreaWidget; friend class CFloatingWidgetTitleBar; + friend class CDockContainerWidget; + + /** + * Returns the floating widget that is transported in the mime data of + * a drag and drop operation started by startPlatformDrag() and nullptr, + * if the mime data does not contain a floating widget + */ + static CFloatingDockContainer* floatingWidgetFromMimeData(const QMimeData* MimeData); + + /** + * Records the drop candidate while a platform drag is hovering over a + * dock container. Called from CDockContainerWidget::dragMoveEvent(). + * Some compositors (e.g. Mutter) do not deliver a drop event when the + * dragged window overlaps the drop target. startPlatformDrag() then docks + * the floating widget into the last recorded candidate, if the drag ended + * over a valid drop area + */ + static void platformDragUpdateDropCandidate(CDockContainerWidget* Container, + const QPoint& GlobalPos, bool ValidDropArea); + + /** + * Marks the platform drag drop as handled by a delivered drop event, so + * that startPlatformDrag() does not dock the floating widget a second time + */ + static void platformDragNotifyDropHandled(); private Q_SLOTS: void onDockAreasAddedOrRemoved(); @@ -236,6 +262,26 @@ private Q_SLOTS: startFloating(DragStartMousePos, Size, DraggingFloatingWidget, MouseEventHandler); } + /** + * Starts a compositor driven drag of the given floating widget on + * Wayland. + * Wayland neither reports the global cursor position nor allows clients + * to move top level windows, so the mouse tracked dragging that is used + * on the other platforms cannot work. Instead, this function executes a + * QDrag with the MIME types that QWaylandDataDevice translates into an + * xdg_toplevel_drag_v1 request, so the compositor moves the floating + * widget with the cursor. The CDockContainerWidget drag and drop event + * handlers show the drop overlays and dock the widget on drop. + * GlobalPressPos is the mouse press position in global coordinates and + * DragSource is the widget that received the mouse press event. + * The function blocks until the user drops or cancels the drag. It + * returns Qt::MoveAction, if the floating widget was docked into a + * drop area and Qt::IgnoreAction, if the floating widget was dropped + * outside of any drop area and remains floating. + */ + static Qt::DropAction startPlatformDrag(CFloatingDockContainer* FloatingWidget, + const QPoint& GlobalPressPos, QWidget* DragSource); + /** * This function returns true, if it can be closed. * It can be closed, if all dock widgets in all dock areas can be closed diff --git a/src/ads_globals.cpp b/src/ads_globals.cpp index b019318e..13beca5b 100644 --- a/src/ads_globals.cpp +++ b/src/ads_globals.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "DockSplitter.h" #include "DockManager.h" @@ -374,6 +375,14 @@ bool isSideBarArea(DockWidgetArea Area) } +//============================================================================ +bool isWayland() +{ + return QGuiApplication::platformName().startsWith( + QLatin1String("wayland"), Qt::CaseInsensitive); +} + + //============================================================================ QPixmap createTransparentPixmap(const QPixmap& Source, qreal Opacity) { diff --git a/src/ads_globals.h b/src/ads_globals.h index f7ccc8e3..42150912 100644 --- a/src/ads_globals.h +++ b/src/ads_globals.h @@ -229,6 +229,15 @@ class CDockInsertParam : public QPair CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area); +/** + * Returns true, if the application runs on the Wayland display server + * protocol. Wayland does not allow clients to move top level windows in + * screen coordinates or to query the global cursor position, so docking + * requires a different implementation on Wayland. + */ +bool isWayland(); + + /** * Returns the SieBarLocation for the AutoHide dock widget areas */