diff options
| author | David Edmundson <[email protected]> | 2023-05-25 22:25:46 +0100 | 
|---|---|---|
| committer | David Edmundson <[email protected]> | 2025-10-14 00:02:02 +0100 | 
| commit | 4eed22e67ed98ec2565be84a4838955af0faf8b0 (patch) | |
| tree | 406c45e7a0d046ba7cf67e52553f426a3a803fe2 /src | |
| parent | 6bb3171e312d1caf3c29c00bcd2cc366b98a5bd1 (diff) | |
widgets: Pass popup semantic information to Wayland
On Wayland applications do not know their own window positions, this
means that popups cannot stay on screen by themselves. When a window
tries to go offscreen the way the window is adjusted is
context-specific:
- The first menu will be kept on screen by moving the x position until
it all fits.
 - A submenu is too close to the screen edge, the menu will open on the
opposite side of the parent menu to avoid obscuring it.
 - A combo box drop downs will vertically flip to the other side of the
originating combobox.
The Wayland API requires the application to provide semantic hints about
the area in which the popup should be placed, along with hints about in
which direction we should try to place the popup and how to handle being
constrained.
At a QPA level we don't know the location of individual widgets so this
needs forwarding explicitly. Rather than exposing all possible
positioning information, it is inferred from the window type at a QPA
level, this required extending windowType to have more explicit values.
Ideally this needs to be application-facing API as there are third-party
comboboxes, but for now it is private with Qt's default controls opting
into it.
Task-number: QTBUG-99618
Task-number: QTBUG-124810
Fixes: QTBUG-135883
Change-Id: I49a2f18d1bfe1b755f259627722e076d58c13e8f
Reviewed-by: David Redondo <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui/kernel/qplatformwindow_p.h | 12 | ||||
| -rw-r--r-- | src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp | 193 | ||||
| -rw-r--r-- | src/plugins/platforms/wayland/qwaylandwindow.cpp | 24 | ||||
| -rw-r--r-- | src/plugins/platforms/wayland/qwaylandwindow_p.h | 10 | ||||
| -rw-r--r-- | src/widgets/kernel/qtooltip.cpp | 13 | ||||
| -rw-r--r-- | src/widgets/util/qcompleter.cpp | 15 | ||||
| -rw-r--r-- | src/widgets/widgets/qcombobox.cpp | 14 | ||||
| -rw-r--r-- | src/widgets/widgets/qmenu.cpp | 19 | 
8 files changed, 212 insertions, 88 deletions
| diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h index c446ac760c0..24c0fd7c431 100644 --- a/src/gui/kernel/qplatformwindow_p.h +++ b/src/gui/kernel/qplatformwindow_p.h @@ -125,6 +125,14 @@ struct Q_GUI_EXPORT QWaylandWindow : public QObject  public:      QT_DECLARE_NATIVE_INTERFACE(QWaylandWindow, 1, QWindow) +    enum WindowType { +        Default, +        ToolTip, +        ComboBox, +        Menu, +        SubMenu, +    }; +      virtual wl_surface *surface() const = 0;      virtual void setCustomMargins(const QMargins &margins) = 0;      virtual void requestXdgActivationToken(uint serial) = 0; @@ -136,6 +144,10 @@ public:          return role ? *role : nullptr;      }      virtual void setSessionRestoreId(const QString &role) = 0; + +    virtual void setExtendedWindowType(WindowType windowType) = 0; +    virtual void setParentControlGeometry(const QRect &parentAnchor) = 0; +  Q_SIGNALS:      void surfaceCreated();      void surfaceDestroyed(); diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index 8386b65bdce..17422bd606d 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -697,108 +697,135 @@ void QWaylandXdgSurface::setWindowPosition(const QPoint &position)      window()->updateExposure();  } +static QtWayland::xdg_positioner::gravity gravityFromEdge(Qt::Edges edges) +{ +    switch (edges) { +    case Qt::Edges(): +        return QtWayland::xdg_positioner::gravity_none; +    case Qt::TopEdge: +        return QtWayland::xdg_positioner::gravity_top; +    case Qt::TopEdge | Qt::RightEdge: +        return QtWayland::xdg_positioner::gravity_top_right; +    case Qt::RightEdge: +        return QtWayland::xdg_positioner::gravity_right; +    case Qt::BottomEdge | Qt::RightEdge: +        return QtWayland::xdg_positioner::gravity_bottom_right; +    case Qt::BottomEdge: +        return QtWayland::xdg_positioner::gravity_bottom; +    case Qt::BottomEdge | Qt::LeftEdge: +        return QtWayland::xdg_positioner::gravity_bottom_left; +    case Qt::LeftEdge: +        return QtWayland::xdg_positioner::gravity_left; +    case Qt::TopEdge | Qt::LeftEdge: +        return QtWayland::xdg_positioner::gravity_top_left; +    } +    qCWarning(lcQpaWayland) << "Cannot map positioner gravity " << edges; +    return QtWayland::xdg_positioner::gravity_none; +} + +static QtWayland::xdg_positioner::anchor anchorFromEdge(Qt::Edges edges) +{ +    switch (edges) { +    case Qt::Edges(): +        return QtWayland::xdg_positioner::anchor_none; +    case Qt::TopEdge: +        return QtWayland::xdg_positioner::anchor_top; +    case Qt::TopEdge | Qt::RightEdge: +        return QtWayland::xdg_positioner::anchor_top_right; +    case Qt::RightEdge: +        return QtWayland::xdg_positioner::anchor_right; +    case Qt::BottomEdge | Qt::RightEdge: +        return QtWayland::xdg_positioner::anchor_bottom_right; +    case Qt::BottomEdge: +        return QtWayland::xdg_positioner::anchor_bottom; +    case Qt::BottomEdge | Qt::LeftEdge: +        return QtWayland::xdg_positioner::anchor_bottom_left; +    case Qt::LeftEdge: +        return QtWayland::xdg_positioner::anchor_left; +    case Qt::TopEdge | Qt::LeftEdge: +        return QtWayland::xdg_positioner::anchor_top_left; +    } +    qCWarning(lcQpaWayland) << "Cannot map positioner anchor" << edges; +    return QtWayland::xdg_positioner::anchor_none; +} +  std::unique_ptr<QWaylandXdgSurface::Positioner> QWaylandXdgSurface::createPositioner(QWaylandWindow *parent)  {      std::unique_ptr<Positioner> positioner(new Positioner(m_shell)); -    // set_popup expects a position relative to the parent -    QRect windowGeometry = m_window->windowContentGeometry(); -    QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins(); -    QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins(); -    // These property overrides may be removed when public API becomes available -    QRect placementAnchor = m_window->window()->property("_q_waylandPopupAnchorRect").toRect(); -    if (!placementAnchor.isValid()) { -        placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1)); -    } -    placementAnchor.translate(windowMargins.left(), windowMargins.top()); -    placementAnchor.translate(-parentMargins.left(), -parentMargins.top()); +    // Default case, map the guessed global position to a relative position +    QRect placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1)); +    Qt::Edges anchor = Qt::TopEdge | Qt::RightEdge; +    Qt::Edges gravity = Qt::BottomEdge | Qt::RightEdge; +    uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y; -    uint32_t anchor = QtWayland::xdg_positioner::anchor_top_left; +    // Override from window type +    if (m_window->parentControlGeometry().isValid()) +        placementAnchor = m_window->parentControlGeometry(); + +    switch (m_window->extendedWindowType()) { +    case QNativeInterface::Private::QWaylandWindow::Menu: +    case QNativeInterface::Private::QWaylandWindow::WindowType::ComboBox: +        anchor = Qt::BottomEdge | Qt::LeftEdge; +        gravity = Qt::BottomEdge | Qt::RightEdge; +        constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | +                QtWayland::xdg_positioner::constraint_adjustment_flip_y | QtWayland::xdg_positioner::constraint_adjustment_slide_y; +        break; +    case QNativeInterface::Private::QWaylandWindow::SubMenu: +        anchor = Qt::TopEdge | Qt::RightEdge; +        gravity = Qt::BottomEdge | Qt::RightEdge; +        constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x | +                QtWayland::xdg_positioner::constraint_adjustment_slide_y; +        break; +    case QNativeInterface::Private::QWaylandWindow::ToolTip: +        anchor = Qt::BottomEdge | Qt::RightEdge; +        gravity = Qt::BottomEdge | Qt::RightEdge; +        constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x | QtWayland::xdg_positioner::constraint_adjustment_slide_x | +                QtWayland::xdg_positioner::constraint_adjustment_flip_y | QtWayland::xdg_positioner::constraint_adjustment_slide_y; +        break; +    default: +        break; +    } + +    if (qApp->layoutDirection() == Qt::RightToLeft) { +        if (anchor & (Qt::RightEdge | Qt::LeftEdge)) +            anchor ^= (Qt::RightEdge | Qt::LeftEdge); +        if (gravity & (Qt::RightEdge | Qt::LeftEdge)) +            gravity ^= (Qt::RightEdge | Qt::LeftEdge); +    } + +    // Override with properties fauxAPI +    const QVariant placementAnchorVariant = m_window->window()->property("_q_waylandPopupAnchorRect"); +    if (placementAnchorVariant.isValid()) +        placementAnchor = placementAnchorVariant.toRect();      const QVariant anchorVariant = m_window->window()->property("_q_waylandPopupAnchor"); -    if (anchorVariant.isValid()) { -        switch (anchorVariant.value<Qt::Edges>()) { -        case Qt::Edges(): -            anchor = QtWayland::xdg_positioner::anchor_none; -            break; -        case Qt::TopEdge: -            anchor = QtWayland::xdg_positioner::anchor_top; -            break; -        case Qt::TopEdge | Qt::RightEdge: -            anchor = QtWayland::xdg_positioner::anchor_top_right; -            break; -        case Qt::RightEdge: -            anchor = QtWayland::xdg_positioner::anchor_right; -            break; -        case Qt::BottomEdge | Qt::RightEdge: -            anchor = QtWayland::xdg_positioner::anchor_bottom_right; -            break; -        case Qt::BottomEdge: -            anchor = QtWayland::xdg_positioner::anchor_bottom; -            break; -        case Qt::BottomEdge | Qt::LeftEdge: -            anchor = QtWayland::xdg_positioner::anchor_bottom_left; -            break; -        case Qt::LeftEdge: -            anchor = QtWayland::xdg_positioner::anchor_left; -            break; -        case Qt::TopEdge | Qt::LeftEdge: -            anchor = QtWayland::xdg_positioner::anchor_top_left; -            break; -        } -    } - -    uint32_t gravity = QtWayland::xdg_positioner::gravity_bottom_right; +    if (anchorVariant.isValid()) +        anchor = anchorVariant.value<Qt::Edges>();      const QVariant popupGravityVariant = m_window->window()->property("_q_waylandPopupGravity"); -    if (popupGravityVariant.isValid()) { -        switch (popupGravityVariant.value<Qt::Edges>()) { -        case Qt::Edges(): -            gravity = QtWayland::xdg_positioner::gravity_none; -            break; -        case Qt::TopEdge: -            gravity = QtWayland::xdg_positioner::gravity_top; -            break; -        case Qt::TopEdge | Qt::RightEdge: -            gravity = QtWayland::xdg_positioner::gravity_top_right; -            break; -        case Qt::RightEdge: -            gravity = QtWayland::xdg_positioner::gravity_right; -            break; -        case Qt::BottomEdge | Qt::RightEdge: -            gravity = QtWayland::xdg_positioner::gravity_bottom_right; -            break; -        case Qt::BottomEdge: -            gravity = QtWayland::xdg_positioner::gravity_bottom; -            break; -        case Qt::BottomEdge | Qt::LeftEdge: -            gravity = QtWayland::xdg_positioner::gravity_bottom_left; -            break; -        case Qt::LeftEdge: -            gravity = QtWayland::xdg_positioner::gravity_left; -            break; -        case Qt::TopEdge | Qt::LeftEdge: -            gravity = QtWayland::xdg_positioner::gravity_top_left; -            break; -        } -    } - -    uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y; +    if (popupGravityVariant.isValid()) +        gravity = popupGravityVariant.value<Qt::Edges>();      const QVariant constraintAdjustmentVariant = m_window->window()->property("_q_waylandPopupConstraintAdjustment"); -    if (constraintAdjustmentVariant.isValid()) { +    if (constraintAdjustmentVariant.isValid())          constraintAdjustment = constraintAdjustmentVariant.toUInt(); -    } + +    // set_popup expects a position relative to the parent +    QRect windowGeometry = m_window->windowContentGeometry(); +    QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins(); +    QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins(); +    placementAnchor.translate(windowMargins.left(), windowMargins.top()); +    placementAnchor.translate(-parentMargins.left(), -parentMargins.top());      positioner->set_anchor_rect(placementAnchor.x(),                                  placementAnchor.y(),                                  placementAnchor.width(),                                  placementAnchor.height()); -    positioner->set_anchor(anchor); -    positioner->set_gravity(gravity); +    positioner->set_anchor(anchorFromEdge(anchor)); +    positioner->set_gravity(gravityFromEdge(gravity));      positioner->set_size(windowGeometry.width(), windowGeometry.height());      positioner->set_constraint_adjustment(constraintAdjustment);      return positioner;  } -  void QWaylandXdgSurface::setIcon(const QIcon &icon)  {      if (!m_shell->m_topLevelIconManager || !m_toplevel) diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index 7c300843518..be527b08f4d 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -481,8 +481,9 @@ void QWaylandWindow::setGeometry(const QRect &r)      if (mShellSurface && !mInResizeFromApplyConfigure) {          const QRect frameGeometry = r.marginsAdded(clientSideMargins()).marginsRemoved(windowContentMargins()); -        if (qt_window_private(window())->positionAutomatic) +        if (qt_window_private(window())->positionAutomatic || m_popupInfo.parentControlGeometry.isValid())              mShellSurface->setWindowSize(frameGeometry.size()); +          else              mShellSurface->setWindowGeometry(frameGeometry);      } @@ -1945,6 +1946,27 @@ QString QWaylandWindow::sessionRestoreId() const      return mSessionRestoreId;  } +void QWaylandWindow::setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::WindowType windowType) { +    m_popupInfo.extendedWindowType = windowType; +} + +QNativeInterface::Private::QWaylandWindow::WindowType QWaylandWindow::extendedWindowType() const +{ +    return m_popupInfo.extendedWindowType; +} + +void QWaylandWindow::setParentControlGeometry(const QRect &parentControlGeometry) { +    m_popupInfo.parentControlGeometry = parentControlGeometry; +    if (mExposed) { +        mShellSurface->setWindowPosition(window()->position()); +    } +} + +QRect QWaylandWindow::parentControlGeometry() const +{ +    return m_popupInfo.parentControlGeometry; +} +  }  QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index 854724daf82..d6b24d0569f 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -255,6 +255,11 @@ public:      void setSessionRestoreId(const QString &role) override;      QString sessionRestoreId() const; +    void setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::WindowType) override; +    QNativeInterface::Private::QWaylandWindow::WindowType extendedWindowType() const; +    void setParentControlGeometry(const QRect &parentAnchor) override; +    QRect parentControlGeometry() const; +  public Q_SLOTS:      void applyConfigure(); @@ -397,6 +402,11 @@ private:      void handleFrameCallback(struct ::wl_callback* callback);      const QPlatformWindow *lastParent = nullptr; +    struct { +        QRect parentControlGeometry; +        QNativeInterface::Private::QWaylandWindow::WindowType extendedWindowType = QNativeInterface::Private::QWaylandWindow::Default; +    } m_popupInfo; +      static QWaylandWindow *mMouseGrab;      static QWaylandWindow *mTopPopup; diff --git a/src/widgets/kernel/qtooltip.cpp b/src/widgets/kernel/qtooltip.cpp index 9e6aaf4b95f..d989feb7f91 100644 --- a/src/widgets/kernel/qtooltip.cpp +++ b/src/widgets/kernel/qtooltip.cpp @@ -20,6 +20,8 @@  #if QT_CONFIG(style_stylesheet)  #include <private/qstylesheetstyle_p.h>  #endif +#include <qpa/qplatformwindow.h> +#include <qpa/qplatformwindow_p.h>  #include <qlabel.h>  #include <QtWidgets/private/qlabel_p.h> @@ -386,6 +388,17 @@ void QTipLabel::placeTip(const QPoint &pos, QWidget *w)          p += offset; +#if QT_CONFIG(wayland) +        create(); +        if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(windowHandle()->handle())) { +            // based on the existing code below, by default position at 'p' stored at the bottom right of our rect +            // then  flip to the other arbitrary 4x24 space if constrained +            const QRect controlGeometry(QRect(p.x() - 4, p.y() - 24, 4, 24)); +            waylandWindow->setParentControlGeometry(controlGeometry); +            waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ToolTip); +        } +#endif +          QRect screenRect = screen->geometry();          if (p.x() + this->width() > screenRect.x() + screenRect.width())              p.rx() -= 4 + this->width(); diff --git a/src/widgets/util/qcompleter.cpp b/src/widgets/util/qcompleter.cpp index 220f600ea41..735b574d293 100644 --- a/src/widgets/util/qcompleter.cpp +++ b/src/widgets/util/qcompleter.cpp @@ -122,6 +122,8 @@  #include "QtGui/qevent.h"  #include <private/qapplication_p.h>  #include <private/qwidget_p.h> +#include <qpa/qplatformwindow.h> +#include <qpa/qplatformwindow_p.h>  #if QT_CONFIG(lineedit)  #include "QtWidgets/qlineedit.h"  #endif @@ -925,10 +927,15 @@ void QCompleterPrivate::showPopup(const QRect& rect)      popup->setGeometry(pos.x(), pos.y(), w, h);      if (!popup->isVisible()) { -        // Make sure popup has a transient parent set, Wayland needs it. QTBUG-130474 -        popup->winId(); // force creation of windowHandle -        popup->windowHandle()->setTransientParent(widget->window()->windowHandle()); - +#if QT_CONFIG(wayland) +        popup->createWinId(); +        if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(popup->windowHandle()->handle())) { +            popup->windowHandle()->setTransientParent(widget->window()->windowHandle()); +            const QRect controlGeometry = QRect(widget->mapTo(widget->topLevelWidget(), QPoint(0,0)), widget->size()); +            waylandWindow->setParentControlGeometry(controlGeometry); +            waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ComboBox); +        } +#endif          popup->show();      }  } diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index 6f25b8bde67..2f51b83a49d 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -7,6 +7,9 @@  #include <qstylepainter.h>  #include <qpa/qplatformtheme.h>  #include <qpa/qplatformmenu.h> +#include <qpa/qplatformwindow.h> +#include <qpa/qplatformwindow_p.h> +  #include <qlineedit.h>  #include <qapplication.h>  #include <qlistview.h> @@ -2868,6 +2871,17 @@ void QComboBox::showPopup()              container->hide();          }      } + +#if QT_CONFIG(wayland) +    if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(container->windowHandle()->handle())) { +        const QRect popup(style->subControlRect(QStyle::CC_ComboBox, &opt, +                                         QStyle::SC_ComboBoxListBoxPopup, this)); +        const QRect controlGeometry = QRect(mapTo(window(), popup.topLeft()), popup.size()); +        waylandWindow->setParentControlGeometry(controlGeometry); +        waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ComboBox); +    } +#endif +      container->show();      if (!neededHorizontalScrollBar && needHorizontalScrollBar()) {          listRect.adjust(0, 0, 0, sb->height()); diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp index 5cda8f33f4c..7d4228709be 100644 --- a/src/widgets/widgets/qmenu.cpp +++ b/src/widgets/widgets/qmenu.cpp @@ -40,6 +40,8 @@  #include <private/qaction_p.h>  #include <private/qguiapplication_p.h>  #include <qpa/qplatformtheme.h> +#include <qpa/qplatformwindow.h> +#include <qpa/qplatformwindow_p.h>  #include <private/qstyle_p.h>  QT_BEGIN_NAMESPACE @@ -2535,6 +2537,23 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po      }      popupScreen = QGuiApplication::screenAt(pos);      q->setGeometry(QRect(pos, size)); + +#if QT_CONFIG(wayland) +    q->create(); +    if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(q->windowHandle()->handle())) { +        if (causedButton) { +            waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu); +            waylandWindow->setParentControlGeometry(causedButton->geometry()); +        } else if (caused) { +            waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::SubMenu); +            waylandWindow->setParentControlGeometry(caused->d_func()->actionRect(caused->d_func()->currentAction)); +        } else if (auto menubar = qobject_cast<QMenuBar*>(causedPopup.widget)) { +            waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu); +            waylandWindow->setParentControlGeometry(menubar->actionGeometry(causedPopup.action)); +        } +    } +#endif +  #if QT_CONFIG(effects)      int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;      int vGuess = QEffects::DownScroll; | 
