From: Christoph Wolk Date: Sat, 8 Feb 2025 17:21:50 +0000 (+0100) Subject: [PATCH] kcmoduleqml: simplify and improve focus handling X-Git-Tag: archive/raspbian/6.13.0-2+rpi1^2~1 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=49fa780ab1e4f2a0ea072aa80ef10a6629c0370e;p=kf6-kcmutils.git [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. Gbp-Pq: Name upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch --- diff --git a/src/kcmoduleqml.cpp b/src/kcmoduleqml.cpp index 5666029..35ac1e5 100644 --- a/src/kcmoduleqml.cpp +++ b/src/kcmoduleqml.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include "quick/kquickconfigmodule.h" #include +#include 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(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