Skip to content

Commit

Permalink
QWindow: move context menu synthesis code into private virtual
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Jan Arve Sæther <[email protected]>
  • Loading branch information
mitchcurtis authored and ec1oud committed Dec 6, 2024
1 parent 40c3b28 commit 9f75fe2
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 31 deletions.
83 changes: 52 additions & 31 deletions src/gui/kernel/qwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;
}

/*!
Expand Down
2 changes: 2 additions & 0 deletions src/gui/kernel/qwindow_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class Q_GUI_EXPORT QWindowPrivate : public QObjectPrivate
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; }
Expand Down

0 comments on commit 9f75fe2

Please sign in to comment.