Propagate ShortcutOverride events up the parent chain

As with key events, ShortcutOverride events have to propagate up the
parent chain so that parent items can override shortcuts with their own
key handling.

For that to work, items that implement shortcutOverride but don't handle
the event must explicitly ignore it, as the events, like all input
events are accepted by default.

This revealed that QQuickTextEdit (via QQuickTextControl) and
QQuickTextInput did not correclty ignore unhandled shortcut overrides,
so fix those implementations. As a drive-by, explicitly capture the
event parameter in the QML test's signal handler.

Task-number: QTBUG-107703
Pick-to: 6.4
Change-Id: If38469c000733f53f5f936de38869b3b6f9fb07a
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2022-10-21 18:15:45 +02:00
parent 9e000eab70
commit a818d49aa5
7 changed files with 88 additions and 15 deletions

View File

@ -322,6 +322,8 @@ void QQuickItemKeyFilter::shortcutOverride(QKeyEvent *event)
{
if (m_next)
m_next->shortcutOverride(event);
else
event->ignore();
}
void QQuickItemKeyFilter::componentComplete()
@ -5491,9 +5493,10 @@ void QQuickItemPrivate::deliverInputMethodEvent(QInputMethodEvent *e)
void QQuickItemPrivate::deliverShortcutOverrideEvent(QKeyEvent *event)
{
if (extra.isAllocated() && extra->keyHandler) {
if (extra.isAllocated() && extra->keyHandler)
extra->keyHandler->shortcutOverride(event);
}
else
event->ignore();
}
bool QQuickItemPrivate::anyPointerHandlerWants(const QPointerEvent *event, const QEventPoint &point) const

View File

@ -742,8 +742,7 @@ void QQuickTextControl::processEvent(QEvent *e, const QTransform &transform)
case QEvent::ShortcutOverride:
if (d->interactionFlags & Qt::TextEditable) {
QKeyEvent* ke = static_cast<QKeyEvent *>(e);
if (isCommonTextEditShortcut(ke))
ke->accept();
ke->setAccepted(isCommonTextEditShortcut(ke));
}
break;
default:

View File

@ -1692,8 +1692,10 @@ bool QQuickTextInput::event(QEvent* ev)
#if QT_CONFIG(shortcut)
Q_D(QQuickTextInput);
if (ev->type() == QEvent::ShortcutOverride) {
if (d->m_readOnly)
if (d->m_readOnly) {
ev->ignore();
return false;
}
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
if (ke == QKeySequence::Copy
|| ke == QKeySequence::Paste
@ -1736,6 +1738,7 @@ bool QQuickTextInput::event(QEvent* ev)
}
}
}
ev->ignore();
}
#endif

View File

@ -765,8 +765,7 @@ bool QQuickDeliveryAgent::event(QEvent *ev)
break;
#endif
case QEvent::ShortcutOverride:
if (d->activeFocusItem)
QCoreApplication::sendEvent(d->activeFocusItem, ev);
d->deliverKeyEvent(static_cast<QKeyEvent *>(ev));
break;
case QEvent::InputMethod:
case QEvent::InputMethodQuery:
@ -826,10 +825,16 @@ void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e)
{
if (activeFocusItem) {
const bool keyPress = (e->type() == QEvent::KeyPress);
if (keyPress)
switch (e->type()) {
case QEvent::KeyPress:
Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), e->modifiers());
else
break;
case QEvent::KeyRelease:
Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), e->modifiers());
break;
default:
break;
}
QQuickItem *item = activeFocusItem;
@ -839,12 +844,10 @@ void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e)
e->key(), e->modifiers(), e->text(),
e->isAutoRepeat(), e->count());
e->accept();
QCoreApplication::sendEvent(item, e);
while (!e->isAccepted() && (item = item->parentItem())) {
do {
e->accept();
QCoreApplication::sendEvent(item, e);
}
} while (!e->isAccepted() && (item = item->parentItem()));
}
}

View File

@ -14,7 +14,7 @@ Item {
id: txt
x: 100
text: "enter text"
Keys.onShortcutOverride: {
Keys.onShortcutOverride: (event) => {
who = "TextEdit"
event.accepted = (event.key === Qt.Key_Escape)
}
@ -26,7 +26,7 @@ Item {
height: width
focus: true
color: focus ? "red" : "gray"
Keys.onShortcutOverride: {
Keys.onShortcutOverride: (event) => {
who = "Rectangle"
event.accepted = (event.key === Qt.Key_Escape)
}

View File

@ -0,0 +1,30 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Window
Window {
id: root
visible: true
width: 200
height: 200
property bool overridden: false
property bool receivedA: false
property bool receivedB: false
Item {
Keys.onShortcutOverride: (e) => e.accepted = root.overridden = (e.key === Qt.Key_A)
Item {
focus: true
Shortcut {
sequence: "A"
onActivated: root.receivedA = true
}
Shortcut {
sequence: "B"
onActivated: root.receivedB = true
}
}
}
}

View File

@ -536,6 +536,8 @@ private slots:
#if QT_CONFIG(shortcut)
void testShortCut();
void shortcutOverride_data();
void shortcutOverride();
#endif
void rendererInterface();
@ -3695,6 +3697,39 @@ void tst_qquickwindow::testShortCut()
QVERIFY(eventFilter.events.contains(int(QEvent::ShortcutOverride)));
QVERIFY(window->property("received").value<bool>());
}
void tst_qquickwindow::shortcutOverride_data()
{
QTest::addColumn<Qt::Key>("key");
QTest::addColumn<bool>("overridden");
QTest::addColumn<bool>("receivedA");
QTest::addColumn<bool>("receivedB");
QTest::addRow("Space") << Qt::Key_Space << false << false << false;
QTest::addRow("A") << Qt::Key_A << true << false << false;
QTest::addRow("B") << Qt::Key_B << false << false << true;
}
void tst_qquickwindow::shortcutOverride()
{
QFETCH(Qt::Key, key);
QFETCH(bool, overridden);
QFETCH(bool, receivedA);
QFETCH(bool, receivedB);
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("shortcutOverride.qml"));
QScopedPointer<QWindow> window(qobject_cast<QQuickWindow *>(component.create()));
QVERIFY(window);
QVERIFY(QTest::qWaitForWindowActive(window.get()));
QTest::keyPress(window.get(), key);
QCOMPARE(window->property("overridden").value<bool>(), overridden);
QCOMPARE(window->property("receivedA").value<bool>(), receivedA);
QCOMPARE(window->property("receivedB").value<bool>(), receivedB);
}
#endif
void tst_qquickwindow::rendererInterface()