Add way to override when to show context menu

Add Qt::ContextMenuTrigger enum used with
QStyleHints::setContextMenuTrigger() to override default platform
behavior when to trigger context menu event.
The default is to show context menu on mouse press on
UNIX systems and on mouse release on Windows.

Give developer a possibility to override platform default behavior
to make cross platform application that behaves the same way on all
platforms

Task-number: QTBUG-93486
Change-Id: Ic832d3d8a7c355a8adb46868fff9cfd19988cf3c
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volodymyr Zibarov 2024-05-03 15:58:50 +03:00
parent 3d59a0f7c7
commit 4585cacaa9
10 changed files with 115 additions and 11 deletions

View File

@ -1356,6 +1356,11 @@ namespace Qt {
PreventContextMenu
};
enum class ContextMenuTrigger {
Press,
Release,
};
enum InputMethodQuery {
ImEnabled = 0x1,
ImCursorRectangle = 0x2,
@ -1731,6 +1736,7 @@ namespace Qt {
Q_ENUM_NS(ScrollBarPolicy)
Q_ENUM_NS(FocusPolicy)
Q_ENUM_NS(ContextMenuPolicy)
Q_ENUM_NS(ContextMenuTrigger)
Q_ENUM_NS(ArrowType)
Q_ENUM_NS(ToolButtonStyle)
Q_ENUM_NS(PenStyle)

View File

@ -2086,6 +2086,18 @@
\value CustomContextMenu the widget emits the QWidget::customContextMenuRequested() signal.
*/
/*!
\enum Qt::ContextMenuTrigger
\since 6.8
This enum type defines the mouse event used to trigger a context menu event.
\value Press context menu on mouse press event, default on UNIX systems.
\value Release context menu on mouse release event, default on Windows.
\sa QStyleHints::contextMenuTrigger
*/
/*!
\enum Qt::FocusPolicy

View File

@ -3447,6 +3447,15 @@ void QGuiApplicationPrivate::updatePalette()
}
}
QEvent::Type QGuiApplicationPrivate::contextMenuEventType()
{
switch (QGuiApplication::styleHints()->contextMenuTrigger()) {
case Qt::ContextMenuTrigger::Press: return QEvent::MouseButtonPress;
case Qt::ContextMenuTrigger::Release: return QEvent::MouseButtonRelease;
}
return QEvent::None;
}
void QGuiApplicationPrivate::clearPalette()
{
delete app_pal;

View File

@ -323,6 +323,8 @@ public:
static void updatePalette();
static QEvent::Type contextMenuEventType();
protected:
virtual void handleThemeChanged();

View File

@ -442,6 +442,40 @@ void QStyleHints::setShowShortcutsInContextMenus(bool s)
}
}
/*!
\property QStyleHints::contextMenuTrigger
\since 6.8
\brief mouse event used to trigger a context menu event.
The default on UNIX systems is to show context menu on mouse button press event, while on
Windows it is the mouse button release event. This property can be used to override the default
platform behavior.
\note Developers must use this property with great care, as it changes the default interaction
mode that their users will expect on the platform that they are running on.
\sa Qt::ContextMenuTrigger
*/
Qt::ContextMenuTrigger QStyleHints::contextMenuTrigger() const
{
Q_D(const QStyleHints);
if (d->m_contextMenuTrigger == -1) {
return themeableHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool()
? Qt::ContextMenuTrigger::Release
: Qt::ContextMenuTrigger::Press;
}
return Qt::ContextMenuTrigger(d->m_contextMenuTrigger);
}
void QStyleHints::setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger)
{
Q_D(QStyleHints);
const Qt::ContextMenuTrigger currentTrigger = this->contextMenuTrigger();
d->m_contextMenuTrigger = int(contextMenuTrigger);
if (currentTrigger != contextMenuTrigger)
emit contextMenuTriggerChanged(contextMenuTrigger);
}
/*!
\property QStyleHints::passwordMaskDelay
\brief the time, in milliseconds, a typed letter is displayed unshrouded

View File

@ -36,6 +36,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(bool showIsMaximized READ showIsMaximized STORED false CONSTANT FINAL)
Q_PROPERTY(bool showShortcutsInContextMenus READ showShortcutsInContextMenus
WRITE setShowShortcutsInContextMenus NOTIFY showShortcutsInContextMenusChanged FINAL)
Q_PROPERTY(Qt::ContextMenuTrigger contextMenuTrigger READ contextMenuTrigger WRITE
setContextMenuTrigger NOTIFY contextMenuTriggerChanged FINAL)
Q_PROPERTY(int startDragDistance READ startDragDistance NOTIFY startDragDistanceChanged FINAL)
Q_PROPERTY(int startDragTime READ startDragTime NOTIFY startDragTimeChanged FINAL)
Q_PROPERTY(int startDragVelocity READ startDragVelocity STORED false CONSTANT FINAL)
@ -80,6 +82,8 @@ public:
bool showIsMaximized() const;
bool showShortcutsInContextMenus() const;
void setShowShortcutsInContextMenus(bool showShortcutsInContextMenus);
Qt::ContextMenuTrigger contextMenuTrigger() const;
void setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger);
int passwordMaskDelay() const;
QChar passwordMaskCharacter() const;
qreal fontSmoothingGamma() const;
@ -108,6 +112,7 @@ Q_SIGNALS:
void tabFocusBehaviorChanged(Qt::TabFocusBehavior tabFocusBehavior);
void useHoverEffectsChanged(bool useHoverEffects);
void showShortcutsInContextMenusChanged(bool);
void contextMenuTriggerChanged(Qt::ContextMenuTrigger contextMenuTrigger);
void wheelScrollLinesChanged(int scrollLines);
void mouseQuickSelectionThresholdChanged(int threshold);
void colorSchemeChanged(Qt::ColorScheme colorScheme);

View File

@ -35,6 +35,7 @@ public:
int m_tabFocusBehavior = -1;
int m_uiEffects = -1;
int m_showShortcutsInContextMenus = -1;
int m_contextMenuTrigger = -1;
int m_wheelScrollLines = -1;
int m_mouseQuickSelectionThreshold = -1;
int m_mouseDoubleClickDistance = -1;

View File

@ -2654,16 +2654,14 @@ bool QWindow::event(QEvent *ev)
This logic could be simplified by always synthesizing events in
QGuiApplicationPrivate, or perhaps even in each QPA plugin. See QTBUG-93486.
*/
static const QEvent::Type contextMenuTrigger =
QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool() ?
QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
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() == contextMenuTrigger && me->button() == Qt::RightButton) {
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);

View File

@ -505,9 +505,6 @@ void QWidgetWindow::handleNonClientAreaMouseEvent(QMouseEvent *e)
void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
{
static const QEvent::Type contextMenuTrigger =
QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool() ?
QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
if (QApplicationPrivate::inPopupMode()) {
QPointer<QWidget> activePopupWidget = QApplication::activePopupWidget();
QPointF mapped = event->position();
@ -624,7 +621,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
}
qt_replay_popup_mouse_event = false;
#ifndef QT_NO_CONTEXTMENU
} else if (event->type() == contextMenuTrigger
} else if (event->type() == QGuiApplicationPrivate::contextMenuEventType()
&& event->button() == Qt::RightButton
&& (openPopupCount == oldOpenPopupCount)) {
QWidget *receiver = activePopupWidget;
@ -637,7 +634,6 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
QApplication::forwardEvent(receiver, &e, event);
}
#else
Q_UNUSED(contextMenuTrigger);
Q_UNUSED(oldOpenPopupCount);
}
#endif
@ -684,7 +680,8 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
event->setAccepted(translated.isAccepted());
}
#ifndef QT_NO_CONTEXTMENU
if (event->type() == contextMenuTrigger && event->button() == Qt::RightButton
if (event->type() == QGuiApplicationPrivate::contextMenuEventType()
&& event->button() == Qt::RightButton
&& m_widget->rect().contains(event->position().toPoint())) {
QContextMenuEvent e(QContextMenuEvent::Mouse, mapped, event->globalPosition().toPoint(), event->modifiers());
QGuiApplication::forwardEvent(receiver, &e, event);

View File

@ -477,6 +477,10 @@ private slots:
void reparentWindowHandles_data();
void reparentWindowHandles();
#ifndef QT_NO_CONTEXTMENU
void contextMenuTrigger();
#endif
private:
const QString m_platform;
QSize m_testWidgetSize;
@ -14040,5 +14044,41 @@ void tst_QWidget::reparentWindowHandles()
}
}
#ifndef QT_NO_CONTEXTMENU
void tst_QWidget::contextMenuTrigger()
{
class ContextMenuWidget : public QWidget
{
public:
int events = 0;
protected:
void contextMenuEvent(QContextMenuEvent *) override { ++events; }
};
const Qt::ContextMenuTrigger wasTrigger = QGuiApplication::styleHints()->contextMenuTrigger();
auto restoreTriggerGuard = qScopeGuard([wasTrigger]{
QGuiApplication::styleHints()->setContextMenuTrigger(wasTrigger);
});
ContextMenuWidget widget;
widget.show();
QVERIFY(!qApp->topLevelWindows().empty());
auto *window = qApp->topLevelWindows()[0];
QVERIFY(window);
QCOMPARE(widget.events, 0);
QGuiApplication::styleHints()->setContextMenuTrigger(Qt::ContextMenuTrigger::Press);
QTest::mousePress(window, Qt::RightButton);
QCOMPARE(widget.events, 1);
QTest::mouseRelease(window, Qt::RightButton);
QCOMPARE(widget.events, 1);
QGuiApplication::styleHints()->setContextMenuTrigger(Qt::ContextMenuTrigger::Release);
QTest::mousePress(window, Qt::RightButton);
QCOMPARE(widget.events, 1);
QTest::mouseRelease(window, Qt::RightButton);
QCOMPARE(widget.events, 2);
}
#endif
QTEST_MAIN(tst_QWidget)
#include "tst_qwidget.moc"