diff --git a/sidebar/SidebarMode.cpp b/sidebar/SidebarMode.cpp index bdea7caf..a39b2a27 100644 --- a/sidebar/SidebarMode.cpp +++ b/sidebar/SidebarMode.cpp @@ -1,501 +1,545 @@ /************************************************************************** * Copyright (C) 2009 by Ben Cooksley * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * * 02110-1301, USA. * ***************************************************************************/ #include "SidebarMode.h" #include "CategoryDrawer.h" #include "CategorizedView.h" #include "MenuItem.h" #include "MenuModel.h" #include "ModuleView.h" #include "MenuProxyModel.h" #include "BaseData.h" #include "SidebarDelegate.h" #include "ToolTips/tooltipmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; 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: SubcategoryModel(QAbstractItemModel *parentModel, QObject *parent = 0) : QStandardItemModel(parent), m_parentModel(parentModel) {} void setParentIndex(const QModelIndex &activeModule) { blockSignals(true); //make the view receive a single signal when the new subcategory is loaded, //never make the view believe there are zero items if this is not the final count //this avoids the brief flash it had clear(); const int subRows = m_parentModel->rowCount(activeModule); if ( subRows > 1) { for (int i = 0; i < subRows; ++i) { const QModelIndex& index = m_parentModel->index(i, 0, activeModule); QStandardItem *item = new QStandardItem(m_parentModel->data(index, Qt::DecorationRole).value(), m_parentModel->data(index, Qt::DisplayRole).toString()); item->setData(index.data(Qt::UserRole), Qt::UserRole); appendRow(item); } } blockSignals(false); beginResetModel(); endResetModel(); } private: QAbstractItemModel *m_parentModel; }; class MostUsedModel : public QSortFilterProxyModel { public: MostUsedModel(QObject *parent = 0) : QSortFilterProxyModel (parent) { sort(0, Qt::DescendingOrder); setSortRole(ResultModel::ScoreRole); setDynamicSortFilter(true); //prepare default items m_defaultModel = new QStandardItemModel(this); QStandardItem *item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:kcm_lookandfeel.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:user_manager.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:screenlocker.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:powerdevilprofilesconfig.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:kcm_kscreen.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); } void setResultModel(ResultModel *model) { if (m_resultModel == model) { return; } auto updateModel = [this]() { if (m_resultModel->rowCount() >= 5) { setSourceModel(m_resultModel); } else { setSourceModel(m_defaultModel); } }; m_resultModel = model; connect(m_resultModel, &QAbstractItemModel::rowsInserted, this, updateModel); connect(m_resultModel, &QAbstractItemModel::rowsRemoved, this, updateModel); updateModel(); } QHash roleNames() const { QHash roleNames; roleNames.insert(Qt::DisplayRole, "display"); roleNames.insert(Qt::DecorationRole, "decoration"); roleNames.insert(ResultModel::ScoreRole, "score"); return roleNames; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { MenuItem *mi; const QString desktopName = QSortFilterProxyModel::data(index, ResultModel::ResourceRole).toUrl().path(); if (m_menuItems.contains(desktopName)) { mi = m_menuItems.value(desktopName); } else { mi = new MenuItem(false, nullptr); const_cast(this)->m_menuItems.insert(desktopName, mi); KService::Ptr service = KService::serviceByStorageId(desktopName); if (!service || !service->isValid()) { return QVariant(); } mi->setService(service); } switch (role) { case Qt::UserRole: return QVariant::fromValue(mi); case Qt::DisplayRole: if (mi->service() && mi->service()->isValid()) { return mi->service()->name(); } else { return QVariant(); } case Qt::DecorationRole: if (mi->service() && mi->service()->isValid()) { return mi->service()->icon(); } else { return QVariant(); } case ResultModel::ScoreRole: return QSortFilterProxyModel::data(index, ResultModel::ScoreRole).toInt(); default: return QVariant(); } } private: QHash m_menuItems; QStandardItemModel *m_defaultModel; ResultModel *m_resultModel; }; class SidebarMode::Private { public: Private() : quickWidget( nullptr ), moduleView( nullptr ), collection( nullptr ), activeCategory( -1 ), activeSubCategory( -1 ) {} virtual ~Private() { delete aboutIcon; } ToolTipManager *toolTipManager; QQuickWidget * quickWidget; KPackage::Package package; SubcategoryModel * subCategoryModel; MostUsedModel * mostUsedModel; - QWidget * mainWidget; + FocusHackWidget * mainWidget; QQuickWidget * placeHolderWidget; QHBoxLayout * mainLayout; KDeclarative::KDeclarative kdeclarative; MenuProxyModel * categorizedModel; MenuProxyModel * searchModel; KAboutData * aboutIcon; ModuleView * moduleView; KActionCollection *collection; QPersistentModelIndex activeCategoryIndex; int activeCategory; int activeSubCategory; }; SidebarMode::SidebarMode( QObject *parent, const QVariantList& ) : BaseMode( parent ) , d( new Private() ) { qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); d->aboutIcon = new KAboutData( "SidebarView", i18n( "Sidebar View" ), "1.0", i18n( "Provides a categorized sidebar for control modules." ), KAboutLicense::GPL, i18n( "(c) 2017, Marco Martin" ) ); d->aboutIcon->addAuthor( i18n( "Marco Martin" ), i18n( "Author" ), "mart@kde.org" ); d->aboutIcon->addAuthor( i18n( "Ben Cooksley" ), i18n( "Author" ), "bcooksley@kde.org" ); d->aboutIcon->addAuthor( i18n( "Mathias Soeken" ), i18n( "Developer" ), "msoeken@informatik.uni-bremen.de" ); d->aboutIcon->setProgramIconName( "view-sidetree" ); } SidebarMode::~SidebarMode() { delete d; } KAboutData * SidebarMode::aboutData() { return d->aboutIcon; } ModuleView * SidebarMode::moduleView() const { return d->moduleView; } QWidget * SidebarMode::mainWidget() { if( !d->quickWidget ) { initWidget(); } return d->mainWidget; } QAbstractItemModel * SidebarMode::categoryModel() const { return d->searchModel; } QAbstractItemModel * SidebarMode::subCategoryModel() const { return d->subCategoryModel; } QAbstractItemModel * SidebarMode::mostUsedModel() const { return d->mostUsedModel; } QList SidebarMode::views() const { QList list; //list.append( d->categoryView ); return list; } void SidebarMode::initEvent() { MenuModel * model = new MenuModel( rootItem(), this ); foreach( MenuItem * child, rootItem()->children() ) { model->addException( child ); } d->categorizedModel = new MenuProxyModel( this ); d->categorizedModel->setCategorizedModel( true ); d->categorizedModel->setSourceModel( model ); d->categorizedModel->sort( 0 ); d->categorizedModel->setFilterHighlightsEntries( false ); d->searchModel = new MenuProxyModel( this ); d->searchModel->setFilterHighlightsEntries( false ); d->searchModel->setSourceModel( d->categorizedModel ); connect( d->searchModel, &MenuProxyModel::filterRegExpChanged, this, [this] () { if (d->activeCategoryIndex.isValid() && d->activeCategoryIndex.row() >= 0) { d->subCategoryModel->setParentIndex( d->activeCategoryIndex ); emit activeCategoryChanged(); } }); 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); d->moduleView = new ModuleView( d->mainWidget ); connect( d->moduleView, &ModuleView::moduleChanged, this, &SidebarMode::moduleLoaded ); d->quickWidget = 0; moduleView()->setFaceType(KPageView::Plain); } void SidebarMode::triggerGlobalAction(const QString &name) { if (!d->collection) { return; } QAction *action = d->collection->action(name); if (action) { action->trigger(); } } void SidebarMode::requestToolTip(int index, const QRectF &rect) { if (showToolTips()) { d->toolTipManager->requestToolTip(d->searchModel->index(index, 0), rect.toRect()); } } void SidebarMode::hideToolTip() { d->toolTipManager->hideToolTip(); } Q_INVOKABLE void SidebarMode::loadMostUsed(int index) { const QModelIndex idx = d->mostUsedModel->index(index, 0); d->moduleView->closeModules(); d->moduleView->loadModule( idx ); } void SidebarMode::changeModule( const QModelIndex& activeModule ) { d->moduleView->closeModules(); const int subRows = d->searchModel->rowCount(activeModule); if ( subRows < 2) { d->moduleView->loadModule( activeModule ); } else { d->moduleView->loadModule( d->searchModel->index(0, 0, activeModule) ); } d->subCategoryModel->setParentIndex( activeModule ); } void SidebarMode::moduleLoaded() { d->placeHolderWidget->hide(); d->moduleView->show(); } int SidebarMode::activeCategory() const { return d->searchModel->mapFromSource(d->searchModel->sourceModel()->index(d->activeCategory, 0)).row(); } void SidebarMode::setActiveCategory(int cat) { if (d->activeCategory == cat) { return; } const QModelIndex idx = d->searchModel->index(cat, 0); d->activeCategoryIndex = idx; d->activeCategory = d->searchModel->mapToSource(idx).row(); changeModule(idx); d->activeSubCategory = 0; emit activeCategoryChanged(); emit activeSubCategoryChanged(); } int SidebarMode::activeSubCategory() const { return d->activeSubCategory; } void SidebarMode::setActiveSubCategory(int cat) { if (d->activeSubCategory == cat) { return; } d->activeSubCategory = cat; d->moduleView->closeModules(); d->moduleView->loadModule( d->subCategoryModel->index(cat, 0) ); emit activeSubCategoryChanged(); } int SidebarMode::width() const { return d->mainWidget->width(); } void SidebarMode::initWidget() { // Create the widgets if (!KMainWindow::memberList().isEmpty()) { KXmlGuiWindow *mainWindow = qobject_cast(KMainWindow::memberList().first()); if (mainWindow) { d->collection = mainWindow->actionCollection(); } } 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")); d->kdeclarative.setDeclarativeEngine(d->quickWidget->engine()); d->kdeclarative.setupBindings(); d->quickWidget->setSource(QUrl::fromLocalFile(d->package.filePath("mainscript"))); const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt(); if (rootImplicitWidth != 0) { d->quickWidget->setFixedWidth(rootImplicitWidth); } else { d->quickWidget->setFixedWidth(240); } connect(d->quickWidget->rootObject(), &QQuickItem::implicitWidthChanged, this, [this]() { const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt(); if (rootImplicitWidth != 0) { d->quickWidget->setFixedWidth(rootImplicitWidth); } else { 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(); d->mainLayout->addWidget( d->moduleView ); d->mainLayout->addWidget( d->placeHolderWidget ); emit changeToolBarItems(BaseMode::NoItems); d->mostUsedModel->setResultModel(new ResultModel( AllResources | Agent("org.kde.systemsettings") | HighScoredFirst | Limit(5), this)); } 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(); } else if (watched == d->mainWidget && event->type() == QEvent::Show) { emit changeToolBarItems(BaseMode::NoItems); } return BaseMode::eventFilter(watched, event); } void SidebarMode::giveFocus() { d->quickWidget->setFocus(); } #include "SidebarMode.moc" diff --git a/sidebar/SidebarMode.h b/sidebar/SidebarMode.h index f37d4949..291691ab 100644 --- a/sidebar/SidebarMode.h +++ b/sidebar/SidebarMode.h @@ -1,86 +1,98 @@ /*************************************************************************** * Copyright (C) 2009 by Ben Cooksley * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ #ifndef SIDEBARMODE_H #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 Q_PROPERTY(QAbstractItemModel *categoryModel READ categoryModel CONSTANT) Q_PROPERTY(QAbstractItemModel *subCategoryModel READ subCategoryModel CONSTANT) Q_PROPERTY(QAbstractItemModel *mostUsedModel READ mostUsedModel CONSTANT) Q_PROPERTY(int activeCategory READ activeCategory WRITE setActiveCategory NOTIFY activeCategoryChanged) Q_PROPERTY(int activeSubCategory READ activeSubCategory WRITE setActiveSubCategory NOTIFY activeSubCategoryChanged) Q_PROPERTY(int width READ width NOTIFY widthChanged) public: SidebarMode(QObject * parent, const QVariantList& ); ~SidebarMode(); QWidget * mainWidget() Q_DECL_OVERRIDE; void initEvent() Q_DECL_OVERRIDE; void giveFocus() Q_DECL_OVERRIDE; KAboutData * aboutData() Q_DECL_OVERRIDE; ModuleView * moduleView() const Q_DECL_OVERRIDE; QAbstractItemModel *categoryModel() const; QAbstractItemModel *subCategoryModel() const; QAbstractItemModel *mostUsedModel() const; int activeCategory() const; void setActiveCategory(int cat); int activeSubCategory() const; void setActiveSubCategory(int cat); int width() const; Q_INVOKABLE void triggerGlobalAction(const QString &name); Q_INVOKABLE void requestToolTip(int index, const QRectF &rect); Q_INVOKABLE void hideToolTip(); Q_INVOKABLE void loadMostUsed(int index); protected: QList views() const Q_DECL_OVERRIDE; bool eventFilter(QObject* watched, QEvent* event) Q_DECL_OVERRIDE; private Q_SLOTS: void changeModule( const QModelIndex& activeModule ); void moduleLoaded(); void initWidget(); Q_SIGNALS: void activeCategoryChanged(); void activeSubCategoryChanged(); void widthChanged(); private: class Private; Private *const d; }; #endif diff --git a/sidebar/package/contents/ui/CategoriesPage.qml b/sidebar/package/contents/ui/CategoriesPage.qml index 3ade46f5..0001b06a 100644 --- a/sidebar/package/contents/ui/CategoriesPage.qml +++ b/sidebar/package/contents/ui/CategoriesPage.qml @@ -1,199 +1,201 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.3 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.0 as QtControls import QtQuick.Controls 2.0 as QtControls2 import org.kde.kirigami 2.1 as Kirigami Kirigami.ScrollablePage { id: mainColumn Component.onCompleted: searchField.forceActiveFocus() header: Item { width: mainColumn.width height: Kirigami.Units.gridUnit * 2.5 RowLayout { id: searchLayout spacing: Kirigami.Units.smallSpacing anchors { fill: parent margins: Kirigami.Units.smallSpacing } QtControls.ToolButton { id: menuButton 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 { text: i18n("Configure") iconName: "settings-configure" onTriggered: systemsettings.triggerGlobalAction("configure"); } QtControls.MenuItem { text: i18n("System Settings Handbook") iconName: "help-contents" onTriggered: systemsettings.triggerGlobalAction("help_contents"); } QtControls.MenuItem { text: i18n("About System Settings") iconName: "help-about" onTriggered: systemsettings.triggerGlobalAction("help_about_app"); } QtControls.MenuItem { text: i18n("About KDE") iconName: "kde" onTriggered: systemsettings.triggerGlobalAction("help_about_kde"); } } } QtControls.TextField { id: searchField focus: true Layout.minimumHeight: Layout.maximumHeight Layout.maximumHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2 Layout.fillWidth: true placeholderText: i18n("Search...") onTextChanged: { systemsettings.categoryModel.filterRegExp = text; } MouseArea { anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: y } opacity: searchField.text.length > 0 ? 1 : 0 width: Kirigami.Units.iconSizes.small height: width onClicked: searchField.text = "" Kirigami.Icon { anchors.fill: parent source: LayoutMirroring.enabled ? "edit-clear-rtl" : "edit-clear" } Behavior on opacity { OpacityAnimator { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } } } } Kirigami.Separator { visible: !categoryView.atYBeginning anchors { left: parent.left right: parent.right top: parent.bottom } } } background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } Kirigami.Heading { anchors.centerIn: parent width: parent.width * 0.7 wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter text: i18nc("A search yelded no results", "No items matching your search") opacity: categoryView.count == 0 ? 0.3 : 0 Behavior on opacity { OpacityAnimator { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } } ListView { id: categoryView anchors.fill: parent 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 { enabled: false separatorVisible: false RowLayout { anchors { left: parent.left right: parent.right leftMargin: Kirigami.Units.smallSpacing } Kirigami.Label { id: sectionLabel text: section Layout.minimumHeight: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium) elide: Text.ElideRight //FIXME: kirigami bug, why? Component.onCompleted: font.bold = true } } } } delegate: Kirigami.BasicListItem { id: delegate 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; } else { systemsettings.activeCategory = index; subCategoryColumn.title = model.display; } } onHoveredChanged: { if (hovered) { systemsettings.requestToolTip(index, delegate.mapToItem(root, 0, 0, width, height)); } else { systemsettings.hideToolTip(); } } onFocusChanged: { if (focus) { onCurrentIndexChanged: categoryView.positionViewAtIndex(index, ListView.Contain); } } 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 index 97bf000a..088626ea 100644 --- a/sidebar/package/contents/ui/IntroIcon.qml +++ b/sidebar/package/contents/ui/IntroIcon.qml @@ -1,61 +1,88 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.0 as QQC2 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 property int iconSize: Kirigami.Units.iconSizes.huge Layout.minimumWidth: Kirigami.Units.iconSizes.medium Layout.minimumHeight: column.implicitHeight 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 index ee52f8bf..50ed54f6 100644 --- a/sidebar/package/contents/ui/SubCategoryPage.qml +++ b/sidebar/package/contents/ui/SubCategoryPage.qml @@ -1,154 +1,143 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.0 as QtControls import QtQuick.Controls 2.0 as QtControls2 import org.kde.kirigami 2.1 as Kirigami Kirigami.ScrollablePage { id: subCategoryColumn header: MouseArea { width: subCategoryColumn.width height: Kirigami.Units.gridUnit * 2.5 enabled: !applicationWindow().wideScreen onClicked: root.pageStack.currentIndex = 0; Accessible.role: Accessible.Button Accessible.name: i18n("Back") Item { id: headerControls anchors.fill: parent QtControls.ToolButton { id: backButton anchors.fill: parent visible: !applicationWindow().wideScreen onClicked: root.pageStack.currentIndex = 0; RowLayout { anchors.fill: parent opacity: 0.3 Kirigami.Icon { id: toolButtonIcon Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: Kirigami.Units.iconSizes.small Layout.preferredWidth: Layout.preferredHeight source: "go-previous" } QtControls2.Label { Layout.fillWidth: true Layout.fillHeight: true height: toolButtonIcon.height text: subCategoryColumn.title verticalAlignment: Text.AlignVCenter elide: Text.ElideRight //FIXME: QtControls bug, why? Component.onCompleted: font.bold = true } } } QtControls2.Label { anchors.verticalCenter: parent.verticalCenter x: y text: subCategoryColumn.title elide: Text.ElideRight visible: !backButton.visible opacity: 0.3 //FIXME: QtControls bug, why? Component.onCompleted: font.bold = true } } Kirigami.Separator { visible: !subCategoryView.atYBeginning anchors { left: parent.left right: parent.right top: parent.bottom } } } background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } ListView { id: subCategoryView 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) { root.pageStack.push(subCategoryColumn); } } else { root.pageStack.pop(mainColumn) } } Connections { target: systemsettings onActiveSubCategoryChanged: { root.pageStack.currentIndex = 1; subCategoryView.forceActiveFocus(); } } delegate: Kirigami.BasicListItem { id: delegate icon: model.decoration label: model.display separatorVisible: false - activeFocusOnTab: root.pageStack.currentIndex == 1 highlighted: focus onClicked: systemsettings.activeSubCategory = index onFocusChanged: { if (focus) { onCurrentIndexChanged: subCategoryView.positionViewAtIndex(index, ListView.Contain); } } checked: systemsettings.activeSubCategory == index //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 index a5420c1e..c5c5e5dc 100644 --- a/sidebar/package/contents/ui/introPage.qml +++ b/sidebar/package/contents/ui/introPage.qml @@ -1,85 +1,98 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 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 bottomMargin: Kirigami.Units.largeSpacing horizontalCenter: parent.horizontalCenter } Kirigami.Icon { Layout.alignment: Qt.AlignHCenter source: "systemsettings" width: Kirigami.Units.iconSizes.enormous height: width opacity: 0.3 } Kirigami.Label { Layout.alignment: Qt.AlignHCenter text: i18n("System Settings") } } Kirigami.Separator { id: separator anchors.centerIn: parent width: Math.round(parent.width * 0.8) } ColumnLayout { anchors { top: separator.bottom topMargin: Kirigami.Units.largeSpacing horizontalCenter: parent.horizontalCenter } width: Math.round(parent.width * 0.8) Kirigami.Heading { Layout.alignment: Qt.AlignHCenter level: 3 wrapMode: Text.NoWrap text: i18n("Frequently used:") } RowLayout { id: iconsRow Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Kirigami.Units.largeSpacing property int spaceForIcon: Math.max((iconsRow.parent.width - iconsRow.spacing * 4) / 5, Kirigami.Units.iconSizes.medium) property int iconSize: iconsRow.spaceForIcon >= Kirigami.Units.iconSizes.huge ? Kirigami.Units.iconSizes.huge : (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 text: model.display iconSize: iconsRow.iconSize Layout.minimumWidth: iconsRow.spaceForIcon Layout.maximumWidth: Layout.minimumWidth visible: (index + 1) * iconSize + index * iconsRow.spacing < iconsRow.parent.width } } } } } diff --git a/sidebar/package/contents/ui/main.qml b/sidebar/package/contents/ui/main.qml index 5cf649f0..b8ef04b2 100644 --- a/sidebar/package/contents/ui/main.qml +++ b/sidebar/package/contents/ui/main.qml @@ -1,46 +1,57 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.0 as QtControls 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 } SubCategoryPage { id: subCategoryColumn } Kirigami.Separator { z: 999 anchors { top: parent.top right: parent.right bottom: parent.bottom } } }