From 493a85a9e468874471057910a61e7c54a45eee83 Mon Sep 17 00:00:00 2001 From: Liang Qi Date: Thu, 25 Nov 2021 19:58:42 +0100 Subject: Widgets: setTransientParent() when a QMenu is a window On some platforms, such as X11 and Wayland with some compositors, QMenu could be a popup window, which should be set a transient parent to get relative position, which is requested by Wayland. Added transientParentWindow() for QMenuPrivate like QDialogPrivate. Fixes: QTBUG-68636 Pick-to: 6.2 Change-Id: I6d8880cb008ecf61a4c005898b38e3953379a13d Reviewed-by: Volker Hilsheimer --- src/widgets/widgets/qmenu.cpp | 25 +++++++++++++ src/widgets/widgets/qmenu_p.h | 1 + tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp | 49 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp index 5ecac2c7c6..c6d1dd0e7e 100644 --- a/src/widgets/widgets/qmenu.cpp +++ b/src/widgets/widgets/qmenu.cpp @@ -625,6 +625,29 @@ void QMenuPrivate::hideMenu(QMenu *menu) menu->d_func()->causedPopup.widget = nullptr; } +QWindow *QMenuPrivate::transientParentWindow() const +{ + Q_Q(const QMenu); + if (const QWidget *parent = q->nativeParentWidget()) { + if (parent->windowHandle()) + return parent->windowHandle(); + } + + if (const QWindow *w = q->windowHandle()) { + if (w->transientParent()) + return w->transientParent(); + } + + if (causedPopup.widget) { + if (const QWidget *w = causedPopup.widget.data()) { + if (const QWidget *ww = w->window()) + return ww->windowHandle(); + } + } + + return nullptr; +} + void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst) { Q_Q(QMenu); @@ -2983,6 +3006,8 @@ bool QMenu::event(QEvent *e) d->sloppyState.reset(); if (d->currentAction) d->popupAction(d->currentAction, 0, false); + if (isWindow() && window() && window()->windowHandle() && !window()->windowHandle()->transientParent()) + window()->windowHandle()->setTransientParent(d->transientParentWindow()); break; #if QT_CONFIG(tooltip) case QEvent::ToolTip: diff --git a/src/widgets/widgets/qmenu_p.h b/src/widgets/widgets/qmenu_p.h index ffe956d007..bad9c294f8 100644 --- a/src/widgets/widgets/qmenu_p.h +++ b/src/widgets/widgets/qmenu_p.h @@ -440,6 +440,7 @@ public: QMenuCaused causedPopup; void hideUpToMenuBar(); void hideMenu(QMenu *menu); + QWindow *transientParentWindow() const; //index mappings inline QAction *actionAt(int i) const { return q_func()->actions().at(i); } diff --git a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp index 4b46c05568..31ecd024c0 100644 --- a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp +++ b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp @@ -118,6 +118,7 @@ private slots: void QTBUG_89082_actionTipsHide(); void QTBUG8122_widgetActionCrashOnClose(); void widgetActionTriggerClosesMenu(); + void transientParent(); void QTBUG_10735_crashWithDialog(); #ifdef Q_OS_MAC @@ -1588,6 +1589,54 @@ void tst_QMenu::widgetActionTriggerClosesMenu() QCOMPARE(actionTriggered, &widgetAction); } +void tst_QMenu::transientParent() +{ + QMainWindow window; + window.resize(480, 320); + window.menuBar()->setNativeMenuBar(false); + centerOnScreen(&window); + + QMenu *fileMenu = new QMenu("&File"); + QAction *exitAct = new QAction("Exit"); + fileMenu->addAction(exitAct); + + QMenu *editMenu = new QMenu("&Edit"); + QAction *undoAct = new QAction("Undo"); + editMenu->addAction(undoAct); + + QMenuBar *menuBar = new QMenuBar; + menuBar->addMenu(fileMenu); + menuBar->addMenu(editMenu); + window.setMenuBar(menuBar); + + // On Mac, we need to create native key events to test menu + // action activation, so skip this part of the test. +#if QT_CONFIG(shortcut) && !defined(Q_OS_DARWIN) + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + QWindow *topLevel = window.windowHandle(); + QVERIFY(topLevel); + + QApplication::setActiveWindow(&window); + window.setFocus(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + QVERIFY(window.hasFocus()); + + QTest::keyPress(&window, Qt::Key_F, Qt::AltModifier); + QTRY_VERIFY(QTest::qWaitForWindowExposed(fileMenu)); + if (fileMenu->isWindow() && fileMenu->window() && fileMenu->window()->windowHandle()) + QVERIFY(fileMenu->window()->windowHandle()->transientParent()); + QTest::keyRelease(fileMenu, Qt::Key_F, Qt::AltModifier); + + QTest::keyPress(fileMenu, Qt::Key_E, Qt::AltModifier); + QTRY_VERIFY(QTest::qWaitForWindowExposed(editMenu)); + if (editMenu->isWindow() && editMenu->window() && editMenu->window()->windowHandle()) + QVERIFY(editMenu->window()->windowHandle()->transientParent()); + QTest::keyRelease(editMenu, Qt::Key_E, Qt::AltModifier); +#endif // QT_CONFIG(shortcut) && !Q_OS_DARWIN + +} + class MyMenu : public QMenu { Q_OBJECT -- cgit v1.2.3