diff --git a/sidebar/SidebarMode.h b/sidebar/SidebarMode.h --- a/sidebar/SidebarMode.h +++ b/sidebar/SidebarMode.h @@ -21,13 +21,25 @@ #define SIDEBARMODE_H #include "BaseMode.h" +#include class ModuleView; class KAboutData; class QModelIndex; class QAbstractItemView; class QAbstractItemModel; +class FocusHackWidget : public QWidget { + Q_OBJECT +public: + FocusHackWidget(QWidget *parent = nullptr); + ~FocusHackWidget(); + +public Q_SLOTS: + void focusNext(); + void focusPrevious(); +}; + class SidebarMode : public BaseMode { Q_OBJECT diff --git a/sidebar/SidebarMode.cpp b/sidebar/SidebarMode.cpp --- a/sidebar/SidebarMode.cpp +++ b/sidebar/SidebarMode.cpp @@ -63,6 +63,22 @@ K_PLUGIN_FACTORY( SidebarModeFactory, registerPlugin(); ) +FocusHackWidget::FocusHackWidget(QWidget *parent) + : QWidget(parent) +{} +FocusHackWidget::~FocusHackWidget() +{} + +void FocusHackWidget::focusNext() +{ + focusNextChild(); +} + +void FocusHackWidget::focusPrevious() +{ + focusNextPrevChild(false); +} + class SubcategoryModel : public QStandardItemModel { public: @@ -221,7 +237,7 @@ KPackage::Package package; SubcategoryModel * subCategoryModel; MostUsedModel * mostUsedModel; - QWidget * mainWidget; + FocusHackWidget * mainWidget; QQuickWidget * placeHolderWidget; QHBoxLayout * mainLayout; KDeclarative::KDeclarative kdeclarative; @@ -320,7 +336,7 @@ d->mostUsedModel = new MostUsedModel( this ); d->subCategoryModel = new SubcategoryModel( d->searchModel, this ); - d->mainWidget = new QWidget(); + d->mainWidget = new FocusHackWidget(); d->mainWidget->installEventFilter(this); d->mainLayout = new QHBoxLayout(d->mainWidget); d->mainLayout->setContentsMargins(0, 0, 0, 0); @@ -435,7 +451,9 @@ } d->quickWidget = new QQuickWidget(d->mainWidget); + d->quickWidget->quickWindow()->setTitle(i18n("Sidebar")); d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + d->quickWidget->engine()->rootContext()->setContextProperty("systemsettings", this); d->package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML"); d->package.setPath(QStringLiteral("org.kde.systemsettings.sidebar")); @@ -460,16 +478,22 @@ d->quickWidget->setFixedWidth(240); } }); + connect(d->quickWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext())); + connect(d->quickWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious())); d->quickWidget->installEventFilter(this); d->toolTipManager = new ToolTipManager(d->searchModel, d->quickWidget); d->placeHolderWidget = new QQuickWidget(d->mainWidget); + d->placeHolderWidget->quickWindow()->setTitle(i18n("Most Used")); d->placeHolderWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); d->placeHolderWidget->engine()->rootContext()->setContextObject(new KLocalizedContext(d->placeHolderWidget)); d->placeHolderWidget->engine()->rootContext()->setContextProperty("systemsettings", this); d->placeHolderWidget->setSource(QUrl::fromLocalFile(d->package.filePath("ui", "introPage.qml"))); + connect(d->placeHolderWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext())); + connect(d->placeHolderWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious())); + d->placeHolderWidget->installEventFilter(this); d->mainLayout->addWidget( d->quickWidget ); d->moduleView->hide(); @@ -482,8 +506,28 @@ bool SidebarMode::eventFilter(QObject* watched, QEvent* event) { - //TODO: patch Qt - if (watched == d->quickWidget && event->type() == QEvent::Leave) { + //FIXME: those are all workarounds around the QQuickWidget brokeness + if ((watched == d->quickWidget || watched == d->placeHolderWidget) + && event->type() == QEvent::KeyPress) { + //allow tab navigation inside the qquickwidget + QKeyEvent *ke = static_cast(event); + QQuickWidget *qqw = static_cast(watched); + if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { + QCoreApplication::sendEvent(qqw->quickWindow(), event); + return true; + } + } else if ((watched == d->quickWidget || watched == d->placeHolderWidget) + && event->type() == QEvent::FocusIn) { + QFocusEvent *fe = static_cast(event); + QQuickWidget *qqw = static_cast(watched); + if (qqw && qqw->rootObject()) { + if (fe->reason() == Qt::TabFocusReason) { + QMetaObject::invokeMethod(qqw->rootObject(), "focusFirstChild"); + } else if (fe->reason() == Qt::BacktabFocusReason) { + QMetaObject::invokeMethod(qqw->rootObject(), "focusLastChild"); + } + } + } else if (watched == d->quickWidget && event->type() == QEvent::Leave) { QCoreApplication::sendEvent(d->quickWidget->quickWindow(), event); } else if (watched == d->mainWidget && event->type() == QEvent::Resize) { emit widthChanged(); diff --git a/sidebar/package/contents/ui/CategoriesPage.qml b/sidebar/package/contents/ui/CategoriesPage.qml --- a/sidebar/package/contents/ui/CategoriesPage.qml +++ b/sidebar/package/contents/ui/CategoriesPage.qml @@ -42,6 +42,9 @@ iconName: "application-menu" Layout.maximumWidth: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2 Layout.maximumHeight: width + Keys.onBacktabPressed: { + root.focusPreviousRequest() + } menu: QtControls.Menu { id: globalMenu QtControls.MenuItem { @@ -131,6 +134,16 @@ model: systemsettings.categoryModel currentIndex: systemsettings.activeCategory onContentYChanged: systemsettings.hideToolTip(); + activeFocusOnTab: true + keyNavigationWraps: true + Accessible.role: Accessible.List + Keys.onTabPressed: { + if (applicationWindow().wideScreen) { + subCategoryColumn.focus = true; + } else { + root.focusNextRequest(); + } + } section { property: "categoryDisplayRole" delegate: Kirigami.AbstractListItem { @@ -159,8 +172,9 @@ icon: model.decoration label: model.display separatorVisible: false - activeFocusOnTab: root.pageStack.currentIndex == 0 highlighted: focus + Accessible.role: Accessible.ListItem + Accessible.name: model.display onClicked: { if (systemsettings.activeCategory == index) { root.pageStack.currentIndex = 1; @@ -182,18 +196,6 @@ } } checked: systemsettings.activeCategory == index - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Up: - delegate.nextItemInFocusChain(false).forceActiveFocus(); - break; - case Qt.Key_Down: - delegate.nextItemInFocusChain(true).forceActiveFocus(); - break; - default: - break; - } - } } } } diff --git a/sidebar/package/contents/ui/IntroIcon.qml b/sidebar/package/contents/ui/IntroIcon.qml --- a/sidebar/package/contents/ui/IntroIcon.qml +++ b/sidebar/package/contents/ui/IntroIcon.qml @@ -23,7 +23,7 @@ MouseArea { - id: root + id: item property alias icon: iconItem.source property alias text: label.text property string module @@ -33,29 +33,56 @@ cursorShape: Qt.PointingHandCursor Layout.fillWidth: true Layout.alignment: Qt.AlignTop + activeFocusOnTab: true onClicked: systemsettings.loadMostUsed(index); + + Keys.onTabPressed: { + if (index < (mostUsedRepeater.count-1)) { + event.accepted = false; + } else { + root.focusNextRequest(); + } + } + Keys.onBacktabPressed: { + if (index > 0) { + event.accepted = false; + } else { + root.focusPreviousRequest(); + } + } + Kirigami.Separator { + anchors{ + left: parent.left + right: parent.right + bottom: parent.bottom + } + visible: item.activeFocus + color: Kirigami.Theme.highlightColor + } ColumnLayout { id: column width: parent.width Kirigami.Icon { id: iconItem Layout.alignment: Qt.AlignHCenter - Layout.minimumWidth: root.iconSize + Layout.minimumWidth: item.iconSize Layout.minimumHeight: Layout.minimumWidth height: width } QQC2.Label { id: label Layout.fillWidth: true - Layout.maximumWidth: root.width + Layout.maximumWidth: item.width Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap } } + Accessible.role: Accessible.Button Accessible.name: label.text + Accessible.description: i18n("Most used modeule number %1", index+1) Accessible.onPressAction: systemsettings.loadMostUsed(index); } diff --git a/sidebar/package/contents/ui/SubCategoryPage.qml b/sidebar/package/contents/ui/SubCategoryPage.qml --- a/sidebar/package/contents/ui/SubCategoryPage.qml +++ b/sidebar/package/contents/ui/SubCategoryPage.qml @@ -98,6 +98,13 @@ anchors.fill: parent model: systemsettings.subCategoryModel currentIndex: systemsettings.activeSubCategory + activeFocusOnTab: true + keyNavigationWraps: true + Accessible.role: Accessible.List + Keys.onTabPressed: root.focusNextRequest(); + Keys.onBacktabPressed: { + mainColumn.focus = true; + } onCountChanged: { if (count > 1) { if (root.pageStack.depth < 2) { @@ -120,7 +127,6 @@ icon: model.decoration label: model.display separatorVisible: false - activeFocusOnTab: root.pageStack.currentIndex == 1 highlighted: focus onClicked: systemsettings.activeSubCategory = index onFocusChanged: { @@ -132,23 +138,6 @@ //checkable: false //FIXME: Qt 5.7 doesn't have checkable, this way fails at runtime but still works correctly on 5.7 Component.onCompleted: delegate.checkable = true; - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Up: - delegate.nextItemInFocusChain(false).forceActiveFocus(); - break; - case Qt.Key_Down: - delegate.nextItemInFocusChain(true).forceActiveFocus(); - break; - case Qt.Key_left: - case Qt.Key_Backspace: - root.pageStack.currentIndex = 0; - mainColumn.forceActiveFocus(); - break; - default: - break; - } - } } } } diff --git a/sidebar/package/contents/ui/introPage.qml b/sidebar/package/contents/ui/introPage.qml --- a/sidebar/package/contents/ui/introPage.qml +++ b/sidebar/package/contents/ui/introPage.qml @@ -23,6 +23,18 @@ Rectangle { id: root color: Kirigami.Theme.backgroundColor + + signal focusNextRequest() + signal focusPreviousRequest() + + function focusFirstChild() { + iconsRow.children[0].focus = true; + } + + function focusLastChild() { + iconsRow.children[iconsRow.children.length-1].focus = true; + } + ColumnLayout { anchors { bottom: separator.top @@ -70,6 +82,7 @@ : (iconsRow.spaceForIcon >= Kirigami.Units.iconSizes.large ? Kirigami.Units.iconSizes.large : Kirigami.Units.iconSizes.medium) Repeater { + id: mostUsedRepeater model: systemsettings.mostUsedModel delegate: IntroIcon { icon: model.decoration diff --git a/sidebar/package/contents/ui/main.qml b/sidebar/package/contents/ui/main.qml --- a/sidebar/package/contents/ui/main.qml +++ b/sidebar/package/contents/ui/main.qml @@ -23,10 +23,21 @@ Kirigami.ApplicationItem { id: root - implicitWidth: wideScreen ? Kirigami.Units.gridUnit * 24 : Kirigami.Units.gridUnit * 12 + implicitWidth: wideScreen ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 15 pageStack.initialPage: mainColumn pageStack.defaultColumnWidth: wideScreen ? root.width / 2 : root.width + signal focusNextRequest() + signal focusPreviousRequest() + + function focusFirstChild() { + mainColumn.focus = true; + } + + function focusLastChild() { + subCategoryColumn.focus = true; + } + wideScreen: pageStack.depth > 1 && systemsettings.width > Kirigami.Units.gridUnit * 70 CategoriesPage { id: mainColumn