Commit 7dd5210c authored by Benjamin Port's avatar Benjamin Port Committed by Marco Martin
Browse files

Allow Panel and systray keyboard navigation

Allow the Panel to get active keyboard focus via a kwyboard shortcut which will cycle between all panels.
When a panel has focus, the active focus can be navigated around either with tab/backtab or arrow keys.
Simple popup applets which only display an icon will work automatically, 
Complex applets like the System tray or the taskbar will have focus that can navigate on all their sub-elements, to activate a particular systray applet or activate a particular window

Co-authored with Benjamin Port<benjamin.port@enioka.com>

CCBUG: 352476
parent a31293f7
Pipeline #131910 passed with stage
in 11 minutes and 12 seconds
......@@ -42,6 +42,21 @@ Item {
id: tab
checked: model.current
text: model.name
activeFocusOnTab: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
activityModel.setCurrentActivity(model.id, function() {});
break;
}
}
Accessible.checked: model.current
Accessible.name: model.name
Accessible.description: i18n("Switch to activity %1", model.name)
Accessible.role: Accessible.Button
onClicked: {
activityModel.setCurrentActivity(model.id, function() {});
}
......
......@@ -5,7 +5,7 @@
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.0
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0
......@@ -28,6 +28,21 @@ MouseArea {
height: Math.round(PlasmaCore.Units.iconSizes.desktop + 2 * PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).height)
width: Math.round(PlasmaCore.Units.iconSizes.desktop * 1.5)
activeFocusOnTab: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
plasmoid.nativeInterface.run()
break;
}
}
Accessible.name: plasmoid.title
Accessible.description: plasmoid.nativeInterface.genericName !== mainText ? plasmoid.nativeInterface.genericName :""
Accessible.role: Accessible.Button
Layout.minimumWidth: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? height : PlasmaCore.Units.iconSizes.small
Layout.minimumHeight: plasmoid.formFactor === PlasmaCore.Types.Vertical ? width : (PlasmaCore.Units.iconSizes.small + 2 * PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).height)
......
......@@ -4,7 +4,7 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import QtQuick 2.15
import QtQuick.Layouts 1.0
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
......@@ -91,6 +91,20 @@ Flow {
anchors.fill: parent
hoverEnabled: true
onReleased: clickHandler(modelData.operation, this)
activeFocusOnTab: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
clickHandler(modelData.operation, this)
break;
}
}
Accessible.name: modelData.tooltip_mainText
Accessible.description: modelData.tooltip_subText
Accessible.role: Accessible.Button
PlasmaCore.ToolTipArea {
anchors.fill: parent
......
......@@ -32,6 +32,7 @@ Item {
Layout.maximumHeight: defaultHeight
property alias hiddenLayout: hiddenItemsView.layout
property alias plasmoidContainer: container
// Header
PlasmaExtras.PlasmoidHeading {
......@@ -168,6 +169,11 @@ Item {
Layout.fillHeight: true
Layout.topMargin: PlasmaCore.Units.smallSpacing
visible: !systemTrayState.activeApplet
onVisibleChanged: {
if (visible) {
layout.forceActiveFocus();
}
}
}
// Container for currently visible item
......@@ -179,6 +185,11 @@ Item {
// We need to add margin on the top so it matches the dialog's own margin
Layout.topMargin: mergeHeadings ? 0 : dialog.margins.top
onVisibleChanged: {
if (visible) {
forceActiveFocus();
}
}
}
}
......
......@@ -16,13 +16,31 @@ PlasmaCore.ToolTipArea {
property int iconSize: PlasmaCore.Units.iconSizes.smallMedium
implicitWidth: iconSize
implicitHeight: iconSize
activeFocusOnTab: true
Accessible.name: i18n("Expand System Tray")
Accessible.description: i18n("Show all the items in the system tray in a popup")
Accessible.role: Accessible.Button
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
systemTrayState.expanded = !systemTrayState.expanded;
}
}
subText: systemTrayState.expanded ? i18n("Close popup") : i18n("Show hidden icons")
MouseArea {
id: arrowMouseArea
anchors.fill: parent
onClicked: systemTrayState.expanded = !systemTrayState.expanded
onClicked: {
systemTrayState.expanded = !systemTrayState.expanded;
expandedRepresentation.hiddenLayout.currentIndex = -1;
}
readonly property int arrowAnimationDuration: PlasmaCore.Units.shortDuration
......
......@@ -6,7 +6,7 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick 2.15
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents // For Highlight
......@@ -55,24 +55,13 @@ PlasmaComponents3.ScrollView {
}
delegate: ItemLoader {}
keyNavigationEnabled: true
activeFocusOnTab: true
onActiveFocusChanged: if (activeFocus && currentIndex === -1) {
currentIndex = 0
} else if (!activeFocus && currentIndex >= 0) {
currentIndex = -1
}
Keys.priority: Keys.AfterItem
Keys.onPressed: if (currentItem && currentItem.item) {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
case Qt.Key_Space:
case Qt.Key_Select:
currentItem.item.activated(Qt.point(width/2, height/2))
event.accepted = true
break
default: break
onActiveFocusChanged: {
if (activeFocus && currentIndex === -1) {
currentIndex = 0
} else if (!activeFocus && currentIndex >= 0) {
currentIndex = -1
}
}
}
......
......@@ -28,7 +28,7 @@ PlasmaCore.ToolTipArea {
readonly property bool inVisibleLayout: effectiveStatus === PlasmaCore.Types.ActiveStatus
// input agnostic way to trigger the main action
signal activated(var args)
signal activated(var pos)
// proxy signals for MouseArea
signal clicked(var mouse)
......@@ -36,6 +36,13 @@ PlasmaCore.ToolTipArea {
signal wheel(var wheel)
signal contextMenu(var mouse)
// Make sure the proper item manages the keyboard
onActiveFocusChanged: {
if (activeFocus) {
iconContainer.forceActiveFocus();
}
}
/* subclasses need to assign to this tooltip properties
mainText:
subText:
......@@ -128,8 +135,28 @@ PlasmaCore.ToolTipArea {
anchors.fill: abstractItem
spacing: 0
Item {
FocusScope {
id: iconContainer
activeFocusOnTab: true
Accessible.name: abstractItem.text
Accessible.description: abstractItem.subText
Accessible.role: Accessible.Button
Accessible.onPressAction: abstractItem.activated(Qt.point(iconContainer.width/2, iconContainer.height/2));
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
abstractItem.activated(Qt.point(width/2, height/2));
break;
case Qt.Key_Menu:
abstractItem.contextMenu(null);
event.accepted = true;
break;
}
}
property alias container: abstractItem
property alias inVisibleLayout: abstractItem.inVisibleLayout
......
......@@ -10,6 +10,11 @@ Loader {
id: itemLoader
property var itemModel: model
onActiveFocusChanged: {
if (activeFocus && item) {
item.forceActiveFocus();
}
}
source: {
if (model.itemType === "Plasmoid" && model.hasApplet) {
......
......@@ -26,32 +26,10 @@ AbstractItem {
// action of a plasmoid is supposed to be, even if it's just expanding the
// plasmoid. Not all plasmoids are supposed to expand and not all plasmoids
// do anything with onActivated.
onActivated: if (applet) {
let fullRep = applet.fullRepresentationItem
/* HACK: Plasmoids can have an empty but not null fullRepresentationItem,
* even if fullRepresentation is not explicitly defined or is explicitly null.
*
* If fullRep is a plain Item and there are no children, assume it is empty.
* There will be uncommon situations where this assumption is wrong.
*
* `typeof fullRep` only returns "object", which is useless.
* We aren't using `fullRep instanceof Item` because it would always
* return true if fullRep is not null.
* If fullRep.toString() starts with "QQuickItem_QML",
* then it really is just a plain Item.
*
* We really need to refactor system tray someday.
*/
if (fullRep && (!fullRep.toString().startsWith("QQuickItem_QML")
|| fullRep.children.length > 0)
) {
// Assume that an applet with a fullRepresentationItem that
// fits the criteria will want to expand the applet when activated.
applet.expanded = !applet.expanded
onActivated: {
if (applet) {
applet.nativeInterface.activated()
}
// If there is no fullRepresentationItem, hopefully the applet is using
// the onActivated signal handler for something useful.
applet.activated()
}
onClicked: {
......
......@@ -37,18 +37,18 @@ AbstractItem {
onActivated: {
let service = model.Service;
let operation = service.operationDescription("Activate")
operation.x = args.x //mouseX
operation.y = args.y //mouseY
let job = service.startOperationCall(operation)
let operation = service.operationDescription("Activate");
operation.x = pos.x; //mouseX
operation.y = pos.y; //mouseY
let job = service.startOperationCall(operation);
job.finished.connect(() => {
if (!job.result) {
// On error try to invoke the context menu.
// Workaround primarily for apps using libappindicator.
openContextMenu(args)
openContextMenu(pos);
}
})
taskIcon.startActivatedAnimation()
taskIcon.startActivatedAnimation();
}
onContextMenu: {
......
......@@ -214,7 +214,16 @@ MouseArea {
backgroundHints: (plasmoid.containmentDisplayHints & PlasmaCore.Types.DesktopFullyCovered) ? PlasmaCore.Dialog.SolidBackground : PlasmaCore.Dialog.StandardBackground
onVisibleChanged: {
systemTrayState.expanded = visible
systemTrayState.expanded = visible;
if (!systemTrayState.expanded) {
return;
}
if (expandedRepresentation.plasmoidContainer.visible) {
expandedRepresentation.plasmoidContainer.forceActiveFocus();
} else if (expandedRepresentation.hiddenLayout.visible) {
expandedRepresentation.hiddenLayout.forceActiveFocus();
}
}
mainItem: ExpandedRepresentation {
id: expandedRepresentation
......
......@@ -806,6 +806,29 @@ void PanelView::moveEvent(QMoveEvent *ev)
PlasmaQuick::ContainmentView::moveEvent(ev);
}
void PanelView::keyPressEvent(QKeyEvent *event)
{
PlasmaQuick::ContainmentView::keyPressEvent(event);
if (event->isAccepted()) {
return;
}
// Catch arrows keyPress to have same behavior as tab/backtab
if ((event->key() == Qt::Key_Right && qApp->layoutDirection() == Qt::LeftToRight)
|| (event->key() == Qt::Key_Left && qApp->layoutDirection() != Qt::LeftToRight) || event->key() == Qt::Key_Down) {
event->accept();
QKeyEvent tabEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
qApp->sendEvent((QObject *)this, (QEvent *)&tabEvent);
return;
} else if ((event->key() == Qt::Key_Right && qApp->layoutDirection() != Qt::LeftToRight)
|| (event->key() == Qt::Key_Left && qApp->layoutDirection() == Qt::LeftToRight) || event->key() == Qt::Key_Up) {
event->accept();
QKeyEvent backtabEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier);
qApp->sendEvent((QObject *)this, (QEvent *)&backtabEvent);
return;
}
}
void PanelView::integrateScreen()
{
updateMask();
......@@ -1295,12 +1318,21 @@ void PanelView::refreshStatus(Plasma::Types::ItemStatus status)
if (status == Plasma::Types::NeedsAttentionStatus) {
showTemporarily();
setFlags(flags() | Qt::WindowDoesNotAcceptFocus);
if (m_shellSurface) {
m_shellSurface->setPanelTakesFocus(false);
}
} else if (status == Plasma::Types::AcceptingInputStatus) {
setFlags(flags() & ~Qt::WindowDoesNotAcceptFocus);
KWindowSystem::forceActiveWindow(winId());
if (m_shellSurface) {
m_shellSurface->setPanelTakesFocus(true);
}
} else {
restoreAutoHide();
setFlags(flags() | Qt::WindowDoesNotAcceptFocus);
if (m_shellSurface) {
m_shellSurface->setPanelTakesFocus(false);
}
}
}
......
......@@ -180,6 +180,7 @@ protected:
void resizeEvent(QResizeEvent *ev) override;
void showEvent(QShowEvent *event) override;
void moveEvent(QMoveEvent *ev) override;
void keyPressEvent(QKeyEvent *event) override;
bool event(QEvent *e) override;
Q_SIGNALS:
......
......@@ -256,6 +256,28 @@ void ShellCorona::init()
connect(this, &ShellCorona::containmentAdded, this, updateManageContainmentsVisiblility);
connect(this, &ShellCorona::screenRemoved, this, updateManageContainmentsVisiblility);
updateManageContainmentsVisiblility();
QAction *cyclePanelFocusAction = actions()->addAction(QStringLiteral("cycle-panels"));
cyclePanelFocusAction->setText(i18n("Move keyboard focus between panels"));
KGlobalAccel::self()->setGlobalShortcut(cyclePanelFocusAction, Qt::META | Qt::ALT | Qt::Key_P);
connect(cyclePanelFocusAction, &QAction::triggered, this, [this]() {
if (m_panelViews.isEmpty()) {
return;
}
PanelView *activePanel = qobject_cast<PanelView *>(qGuiApp->focusWindow());
Plasma::Containment *containmentToActivate = nullptr;
if (activePanel) {
auto it = m_panelViews.constFind(activePanel->containment());
it++;
if (it != m_panelViews.constEnd()) {
containmentToActivate = it.value()->containment();
}
}
if (!containmentToActivate) {
containmentToActivate = m_panelViews.values().first()->containment();
}
emit containmentToActivate->activated();
});
}
ShellCorona::~ShellCorona()
......
Supports Markdown
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