Commit 1b904566 authored by Marco Martin's avatar Marco Martin

keyboard navigation in systemsettings

Summary:
correct keyboard navigation in systemsettings, since there is a mix
of qwidgets and QQuickWidgets, when a QQW takes focus, then the
focus chain in the keyboard navigation should traverse it, then go
put of it and focus the next widget, being a normal qwidget or another
qqw in which case the same hack applies

Test Plan:
navigating in and out the sidebar and the intro page works

QML based kcms are still not managed, they will need a similar thing

Reviewers: #plasma

Subscribers: davidedmundson, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8221
parent cfbc64d8
......@@ -63,6 +63,22 @@ using namespace KAStats::Terms;
K_PLUGIN_FACTORY( SidebarModeFactory, registerPlugin<SidebarMode>(); )
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 @@ public:
KPackage::Package package;
SubcategoryModel * subCategoryModel;
MostUsedModel * mostUsedModel;
QWidget * mainWidget;
FocusHackWidget * mainWidget;
QQuickWidget * placeHolderWidget;
QHBoxLayout * mainLayout;
KDeclarative::KDeclarative kdeclarative;
......@@ -320,7 +336,7 @@ void SidebarMode::initEvent()
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 @@ void SidebarMode::initWidget()
}
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 @@ void SidebarMode::initWidget()
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 @@ void SidebarMode::initWidget()
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<QKeyEvent *>(event);
QQuickWidget *qqw = static_cast<QQuickWidget *>(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<QFocusEvent *>(event);
QQuickWidget *qqw = static_cast<QQuickWidget *>(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();
......
......@@ -21,6 +21,7 @@
#define SIDEBARMODE_H
#include "BaseMode.h"
#include <QWidget>
class ModuleView;
class KAboutData;
......@@ -28,6 +29,17 @@ 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
......
......@@ -42,6 +42,9 @@ Kirigami.ScrollablePage {
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 @@ Kirigami.ScrollablePage {
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 @@ Kirigami.ScrollablePage {
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 @@ Kirigami.ScrollablePage {
}
}
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;
}
}
}
}
}
......@@ -23,7 +23,7 @@ import org.kde.kirigami 2.1 as Kirigami
MouseArea {
id: root
id: item
property alias icon: iconItem.source
property alias text: label.text
property string module
......@@ -33,29 +33,56 @@ MouseArea {
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);
}
......@@ -98,6 +98,13 @@ Kirigami.ScrollablePage {
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 @@ Kirigami.ScrollablePage {
icon: model.decoration
label: model.display
separatorVisible: false
activeFocusOnTab: root.pageStack.currentIndex == 1
highlighted: focus
onClicked: systemsettings.activeSubCategory = index
onFocusChanged: {
......@@ -132,23 +138,6 @@ Kirigami.ScrollablePage {
//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;
}
}
}
}
}
......@@ -23,6 +23,18 @@ import org.kde.kirigami 2.1 as Kirigami
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 @@ Rectangle {
: (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
......
......@@ -23,10 +23,21 @@ import org.kde.kirigami 2.1 as Kirigami
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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment