1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
From 6dc76c3dc14aaaecc926a0af8f092402b23bf22b Mon Sep 17 00:00:00 2001
From: Christoph Wolk <cwo.kde@posteo.net>
Date: Sat, 8 Feb 2025 18:21:50 +0100
Subject: [PATCH] kcmoduleqml: simplify and improve focus handling
kcmoduleqml contains some old tricks to make passing focus between the
qml and qtwidgets bits, but they are fragile, and break in many cases -
especially with systemsettings adding another layer of qml. The hacks
may have been necessary in the past, but things actually work well now,
so we can essentially just set a focusProxy and be done ... as long as
we don't care about accessibility. The qml bits receive focus before the
widgets parts fully hand it over, so the first qml item receiving focus
will not be announced over screen readers. We do care though, and thus
some special handling is still needed.
We switch to using focusProxy, and add a bit of surgery to the focus
transition so that screen readers always stay in the loop about what
things are focused. We also need a bit of special handling for the
backtab case, which doesn't quite work automatically with
activeFocusOnTab set on the qml root.
---
src/kcmoduleqml.cpp | 60 ++++++++++++++++++++++++++++-----------------
1 file changed, 38 insertions(+), 22 deletions(-)
diff --git a/src/kcmoduleqml.cpp b/src/kcmoduleqml.cpp
index 5666029f..35ac1e54 100644
--- a/src/kcmoduleqml.cpp
+++ b/src/kcmoduleqml.cpp
@@ -10,6 +10,7 @@
#include <QQuickItem>
#include <QQuickWidget>
#include <QQuickWindow>
+#include <QTimer>
#include <QVBoxLayout>
#include <KAboutData>
@@ -20,6 +21,7 @@
#include "quick/kquickconfigmodule.h"
#include <kcmutils_debug.h>
+#include <qquickitem.h>
class QmlConfigModuleWidget;
class KCModuleQmlPrivate
@@ -64,15 +66,6 @@ public:
setFocusPolicy(Qt::StrongFocus);
}
- void focusInEvent(QFocusEvent *event) override
- {
- if (event->reason() == Qt::TabFocusReason) {
- m_module->d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
- } else if (event->reason() == Qt::BacktabFocusReason) {
- m_module->d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
- }
- }
-
QSize sizeHint() const override
{
if (!m_module->d->rootPlaceHolder) {
@@ -84,21 +77,45 @@ public:
bool eventFilter(QObject *watched, QEvent *event) override
{
- if (watched == m_module->d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
+ // Everything would work mosty without manual intervention, but as of Qt 6.8
+ // things require special attention so that they work correctly with orca.
+ // The timing between the focusproxied QQuickWidget receiving focus and the
+ // focused qml Item being registered as focused is off and screen readers get
+ // confused. Instead, put initial focus on the root element and switch with a timer
+ // so the qml focuschange happens while the qquickwidget has focus. This
+ // requires activeFocusOnTab on the rootPlaceHolder to work, and that makes other things
+ // a bit messier than they would otherwise need to be.
+ if (event->type() == QEvent::FocusIn && watched == m_module->d->rootPlaceHolder) {
auto focusEvent = static_cast<QFocusEvent *>(event);
if (focusEvent->reason() == Qt::TabFocusReason) {
- QWidget *w = m_module->d->quickWidget->nextInFocusChain();
- while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
- w = w->nextInFocusChain();
- }
- w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
+ m_module->d->rootPlaceHolder->forceActiveFocus(Qt::OtherFocusReason);
+ QTimer::singleShot(0, this, [this] {
+ QQuickItem *nextItem = m_module->d->rootPlaceHolder->nextItemInFocusChain(true);
+ if (nextItem) {
+ nextItem->forceActiveFocus(Qt::TabFocusReason);
+ }
+ });
return true;
} else if (focusEvent->reason() == Qt::BacktabFocusReason) {
- QWidget *w = m_module->d->quickWidget->previousInFocusChain();
- while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
- w = w->previousInFocusChain();
+ // this can either happen from backtabbing in qml or from backtabbing
+ // from qwidgets past the focusproxy (e.g. from the kcm buttons).
+ if (!m_module->d->rootPlaceHolder->hasActiveFocus()) {
+ // we're in widgets, enter qml from reverse in the focus chain in the same way as above
+ QTimer::singleShot(0, this, [this] {
+ QQuickItem *nextItem = m_module->d->rootPlaceHolder->nextItemInFocusChain(false);
+ if (nextItem) {
+ nextItem->forceActiveFocus(Qt::TabFocusReason);
+ }
+ });
+ return true;
}
- w->setFocus(Qt::BacktabFocusReason);
+ // we're coming from qml, so we focus the widget outside. This also needs singleShot;
+ // if we do it immediately, focus cycles backward along the qml focus chain instead.
+ // Without activeFocusOnTab on the rootPlaceHolder we could just return false and
+ // Qt would handle everything by itself
+ QTimer::singleShot(0, this, [this] {
+ focusNextPrevChild(false);
+ });
return true;
}
}
@@ -146,15 +163,14 @@ KCModuleQml::KCModuleQml(KQuickConfigModule *configModule, QWidget *parent)
d->quickWidget = new QQuickWidget(d->configModule->engine().get(), d->widget);
d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
- d->quickWidget->setFocusPolicy(Qt::StrongFocus);
d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
d->quickWidget->setAttribute(Qt::WA_NoMousePropagation, true); // Workaround for QTBUG-109861 to fix drag everywhere
d->quickWindow = d->quickWidget->quickWindow();
d->quickWindow->setColor(Qt::transparent);
+ d->widget->setFocusProxy(d->quickWidget);
QQmlComponent *component = new QQmlComponent(d->configModule->engine().get(), this);
- // this has activeFocusOnTab to notice when the navigation wraps
- // around, so when we need to go outside and inside
+ // activeFocusOnTab is required to have screen readers not get confused
// pushPage/popPage are needed as push of StackView can't be directly invoked from c++
// because its parameters are QQmlV4Function which is not public.
// The managers of onEnter/ReturnPressed are a workaround of
--
GitLab
|