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 <volker.hilsheimer@qt.io>
This commit is contained in:
Liang Qi 2021-11-25 19:58:42 +01:00
parent 9c05fdac81
commit 493a85a9e4
3 changed files with 75 additions and 0 deletions

View File

@ -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:

View File

@ -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); }

View File

@ -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