QWindow: move context menu synthesis code into private virtual

QQuickWindow needs to be able to call this to ensure that it gets
the context menu event after the mouse event, and not the other way
around, otherwise the menu is immediately closed after opening.

Move the code into QWindowPrivate::maybeSynthesizeContextMenuEvent(),
which is called only for mouse press and release events, and which does
the synthesis only if the event was not already accepted and has no
exclusive grabber.  Use scenePosition() to avoid getting a localized
position left over from delivery to specific widgets or Qt Quick items.
Add explanations to internal docs.

This also opens up the opportunity for QQuickWindowPrivate to do
this in a Qt Quick-specific way.

Task-number: QTBUG-67331
Task-number: QTBUG-93486
Change-Id: I909671d9d62c9007b22646cbea6eede7465ab686
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
Mitch Curtis 2024-04-17 15:06:14 +08:00 committed by Shawn Rutledge
parent 40c3b28172
commit 9f75fe29d3
2 changed files with 54 additions and 31 deletions

View File

@ -2609,18 +2609,25 @@ void QWindow::closeEvent(QCloseEvent *ev)
*/
bool QWindow::event(QEvent *ev)
{
Q_D(QWindow);
switch (ev->type()) {
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent*>(ev));
break;
case QEvent::MouseButtonPress:
mousePressEvent(static_cast<QMouseEvent*>(ev));
case QEvent::MouseButtonPress: {
auto *me = static_cast<QMouseEvent*>(ev);
mousePressEvent(me);
d->maybeSynthesizeContextMenuEvent(me);
break;
}
case QEvent::MouseButtonRelease:
mouseReleaseEvent(static_cast<QMouseEvent*>(ev));
case QEvent::MouseButtonRelease: {
auto *me = static_cast<QMouseEvent*>(ev);
mouseReleaseEvent(me);
d->maybeSynthesizeContextMenuEvent(me);
break;
}
case QEvent::MouseButtonDblClick:
mouseDoubleClickEvent(static_cast<QMouseEvent*>(ev));
@ -2677,7 +2684,6 @@ bool QWindow::event(QEvent *ev)
case QEvent::Close: {
Q_D(QWindow);
const bool wasVisible = d->treatAsVisible();
const bool participatesInLastWindowClosed = d->participatesInLastWindowClosed();
@ -2738,35 +2744,50 @@ bool QWindow::event(QEvent *ev)
return QObject::event(ev);
}
return true;
}
/*! \internal
Synthesize and send a QContextMenuEvent if the given \a event is a suitable
mouse event (a right-button press or release, depending on
QStyleHints::contextMenuTrigger()) that was *not accepted* and *isn't*
exclusively grabbed. On most platforms, it's done on mouse release; on
Windows, it's done on press, because of the potential to support
right-button clicks and drags to select or lasso items, and then still
getting a context menu at the end of that gesture. (That is in conflict
with supporting the press-drag-release gesture to select menu items on the
context menus themselves. Context menus can be implemented that way by
handling the separate press, move and release events.) Any time the
\a event was already handled in some way, it must be accepted, to avoid
synthesis of the QContextMenuEvent here.
The QContextMenuEvent occurs at the scenePosition(). The position()
was likely already "localized" during the previous delivery.
The synthesis from a mouse button event could be done in the platform
plugin, but so far on Windows it's not done: WM_CONTEXTMENU is not
generated by the OS, because we never call the default window procedure
that would do that in response to unhandled WM_RBUTTONUP. If we
eventually want to do that, we would have to avoid doing it here,
on platforms where the platform plugin is responsible for it.
QGuiApplicationPrivate::processContextMenuEvent also allows
keyboard-triggered context menu events that the QPA plugin might generate.
On Windows, the keyboard may have a menu key. On macOS, control-return
is the usual shortcut; on Gnome, it's shift-F10; and so on.
*/
void QWindowPrivate::maybeSynthesizeContextMenuEvent(QMouseEvent *event)
{
#ifndef QT_NO_CONTEXTMENU
/*
QGuiApplicationPrivate::processContextMenuEvent blocks mouse-triggered
context menu events that the QPA plugin might generate. In practice that
never happens, as even on Windows WM_CONTEXTMENU is never generated by
the OS (we never call the default window procedure that would do that in
response to unhandled WM_RBUTTONUP).
So, we always have to syntheize QContextMenuEvent for mouse events anyway.
QWidgetWindow synthesizes QContextMenuEvent similar to this code, and
never calls QWindow::event, so we have to do it here as well.
This logic could be simplified by always synthesizing events in
QGuiApplicationPrivate, or perhaps even in each QPA plugin. See QTBUG-93486.
*/
auto asMouseEvent = [](QEvent *ev) {
const auto t = ev->type();
return t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease
? static_cast<QMouseEvent *>(ev) : nullptr ;
};
if (QMouseEvent *me = asMouseEvent(ev);
me && ev->type() == QGuiApplicationPrivate::contextMenuEventType()
&& me->button() == Qt::RightButton) {
QContextMenuEvent e(QContextMenuEvent::Mouse, me->position().toPoint(),
me->globalPosition().toPoint(), me->modifiers());
QGuiApplication::sendEvent(this, &e);
if (!event->isAccepted() && !event->exclusivePointGrabber()
&& event->button() == Qt::RightButton
&& event->type() == QGuiApplicationPrivate::contextMenuEventType()) {
QContextMenuEvent e(QContextMenuEvent::Mouse, event->scenePosition().toPoint(),
event->globalPosition().toPoint(), event->modifiers());
qCDebug(lcPopup) << "synthesized QContextMenuEvent after un-accepted" << event->type() << ":" << &e;
QGuiApplication::sendEvent(q_func(), &e);
}
#endif
return true;
}
/*!

View File

@ -95,6 +95,8 @@ public:
virtual bool participatesInLastWindowClosed() const;
virtual bool treatAsVisible() const;
virtual void maybeSynthesizeContextMenuEvent(QMouseEvent *event);
const QWindow *forwardToPopup(QEvent *event, const QWindow *activePopupOnPress);
bool isPopup() const { return (windowFlags & Qt::WindowType_Mask) == Qt::Popup; }