on xenial help sign in
Shell dialog improvements (kbd focus, mouse eater)
3f99e4cLukáš Tinkl
plugins/Utils/tabfocusfence.cpp+26
@@ -25,6 +25,32 @@ TabFocusFenceItem::TabFocusFenceItem(QQuickItem *parent) : QQuickItem(parent)
25 setFlag(ItemIsFocusScope);
25 setFlag(ItemIsFocusScope);
26}
26}
27
27
28+bool TabFocusFenceItem::focusNext()
29+{
30+ QQuickItem * current = scopedFocusItem();
31+ if (current) {
32+ QQuickItem * next = current->nextItemInFocusChain(true);
33+ if (next) {
34+ next->setFocus(true, Qt::TabFocusReason);
35+ return true;
36+ }
37+ }
38+ return false;
39+}
40+
41+bool TabFocusFenceItem::focusPrev()
42+{
43+ QQuickItem * current = scopedFocusItem();
44+ if (current) {
45+ QQuickItem * prev = current->nextItemInFocusChain(false);
46+ if (prev) {
47+ prev->setFocus(true, Qt::BacktabFocusReason);
48+ return true;
49+ }
50+ }
51+ return false;
52+}
53+
28void TabFocusFenceItem::keyPressEvent(QKeyEvent *event)
54void TabFocusFenceItem::keyPressEvent(QKeyEvent *event)
29{
55{
30 // Needed so we eat Tab keys when there's only one item inside the fence
56 // Needed so we eat Tab keys when there's only one item inside the fence
plugins/Utils/tabfocusfence.h+3
@@ -28,6 +28,9 @@ Q_OBJECT
28public:
28public:
29 TabFocusFenceItem(QQuickItem *parent = nullptr);
29 TabFocusFenceItem(QQuickItem *parent = nullptr);
30
30
31+ Q_INVOKABLE bool focusNext();
32+ Q_INVOKABLE bool focusPrev();
33+
31 void keyPressEvent(QKeyEvent *event) override;
34 void keyPressEvent(QKeyEvent *event) override;
32 void keyReleaseEvent(QKeyEvent *event) override;
35 void keyReleaseEvent(QKeyEvent *event) override;
33};
36};
qml/Components/Dialogs.qml+14−2
@@ -24,10 +24,13 @@ import Unity.Platform 1.0
24import Utils 0.1
24import Utils 0.1
25import "../Greeter"
25import "../Greeter"
26
26
27−Item {
27+MouseArea {
28 id: root
28 id: root
29+ acceptedButtons: Qt.AllButtons
30+ hoverEnabled: true
31+ onWheel: wheel.accepted = true
29
32
30− readonly property alias hasActiveDialog: dialogLoader.active
33+ readonly property bool hasActiveDialog: dialogLoader.active || d.modeSwitchWarningPopup
31
34
32 // to be set from outside, useful mostly for testing purposes
35 // to be set from outside, useful mostly for testing purposes
33 property var unitySessionService: DBusUnitySessionService
36 property var unitySessionService: DBusUnitySessionService
@@ -42,6 +45,7 @@ Item {
42 }
45 }
43 property string usageScenario
46 property string usageScenario
44 property size screenSize: Qt.size(Screen.width, Screen.height)
47 property size screenSize: Qt.size(Screen.width, Screen.height)
48+ property bool hasKeyboard: false
45
49
46 signal powerOffClicked();
50 signal powerOffClicked();
47
51
@@ -120,6 +124,11 @@ Item {
120 onTriggered: LightDMService.greeter.showGreeter()
124 onTriggered: LightDMService.greeter.showGreeter()
121 }
125 }
122
126
127+ GlobalShortcut { // lock screen
128+ shortcut: Qt.MetaModifier|Qt.Key_L
129+ onTriggered: LightDMService.greeter.showGreeter()
130+ }
131+
123 QtObject {
132 QtObject {
124 id: d // private stuff
133 id: d // private stuff
125 objectName: "dialogsPrivate"
134 objectName: "dialogsPrivate"
@@ -155,6 +164,7 @@ Item {
155 LightDMService.greeter.showGreeter()
164 LightDMService.greeter.showGreeter()
156 logoutDialog.hide();
165 logoutDialog.hide();
157 }
166 }
167+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
158 }
168 }
159 Button {
169 Button {
160 width: parent.width
170 width: parent.width
@@ -198,6 +208,7 @@ Item {
198 rebootDialog.hide();
208 rebootDialog.hide();
199 }
209 }
200 color: theme.palette.normal.negative
210 color: theme.palette.normal.negative
211+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
201 }
212 }
202 }
213 }
203 }
214 }
@@ -218,6 +229,7 @@ Item {
218 root.powerOffClicked();
229 root.powerOffClicked();
219 }
230 }
220 color: theme.palette.normal.negative
231 color: theme.palette.normal.negative
232+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
221 }
233 }
222 Button {
234 Button {
223 width: parent.width
235 width: parent.width
qml/Components/ShellDialog.qml+6−2
@@ -37,8 +37,6 @@ Dialog {
37
37
38 onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
38 onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
39
39
40− Keys.onEscapePressed: hide()
41−
42 focus: true
40 focus: true
43
41
44 // FIXME: this is a hack because Dialog subtheming seems broken atm
42 // FIXME: this is a hack because Dialog subtheming seems broken atm
@@ -63,5 +61,11 @@ Dialog {
63 width: parent.width
61 width: parent.width
64 spacing: units.gu(2)
62 spacing: units.gu(2)
65 }
63 }
64+ Keys.onDownPressed: {
65+ event.accepted = focusNext();
66+ }
67+ Keys.onUpPressed: {
68+ event.accepted = focusPrev();
69+ }
66 }
70 }
67}
71}
qml/OrientedShell.qml+2−1
@@ -266,7 +266,8 @@ Item {
266 nativeWidth: root.width
266 nativeWidth: root.width
267 nativeHeight: root.height
267 nativeHeight: root.height
268 mode: applicationArguments.mode
268 mode: applicationArguments.mode
269− hasMouse: miceModel.count + touchPadModel.count > 0
269+ hasMouse: pointerInputDevices > 0
270+ hasKeyboard: keyboardsModel.count > 0
270 // TODO: Factor in if the current screen is a touch screen and if the user wants to
271 // TODO: Factor in if the current screen is a touch screen and if the user wants to
271 // have multiple keyboards around. For now we only enable one keyboard at a time
272 // have multiple keyboards around. For now we only enable one keyboard at a time
272 // thus hiding it here if there is a physical one around or if we have a second
273 // thus hiding it here if there is a physical one around or if we have a second
qml/Shell.qml+3
@@ -68,6 +68,7 @@ StyledItem {
68 stage.updateFocusedAppOrientationAnimated();
68 stage.updateFocusedAppOrientationAnimated();
69 }
69 }
70 property bool hasMouse: false
70 property bool hasMouse: false
71+ property bool hasKeyboard: false
71
72
72 // to be read from outside
73 // to be read from outside
73 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
74 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
@@ -657,8 +658,10 @@ StyledItem {
657 id: dialogs
658 id: dialogs
658 objectName: "dialogs"
659 objectName: "dialogs"
659 anchors.fill: parent
660 anchors.fill: parent
661+ visible: hasActiveDialog
660 z: overlay.z + 10
662 z: overlay.z + 10
661 usageScenario: shell.usageScenario
663 usageScenario: shell.usageScenario
664+ hasKeyboard: shell.hasKeyboard
662 onPowerOffClicked: {
665 onPowerOffClicked: {
663 shutdownFadeOutRectangle.enabled = true;
666 shutdownFadeOutRectangle.enabled = true;
664 shutdownFadeOutRectangle.visible = true;
667 shutdownFadeOutRectangle.visible = true;
tests/qmltests/tst_OrientedShell.qml+12−5
@@ -1559,7 +1559,14 @@ Rectangle {
1559 return surface.activeFocus;
1559 return surface.activeFocus;
1560 }
1560 }
1561
1561
1562+ function test_tabCyclyingInShutdownDialog_data() {
1563+ return [
1564+ {tag: "TAB", key: Qt.Key_Tab},
1565+ {tag: "DOWN", key: Qt.Key_Down}
1566+ ];
1567+ }
1568+
1562− function test_tabCyclyingInShutdownDialog() {
1569+ function test_tabCyclyingInShutdownDialog(data) {
1563 loadShell("mako");
1570 loadShell("mako");
1564
1571
1565 testCase.showPowerDialog();
1572 testCase.showPowerDialog();
@@ -1569,13 +1576,13 @@ Rectangle {
1569
1576
1570 tryCompare(buttons[0], "activeFocus", true);
1577 tryCompare(buttons[0], "activeFocus", true);
1571
1578
1572− keyClick(Qt.Key_Tab);
1579+ keyClick(data.key);
1573 tryCompare(buttons[1], "activeFocus", true);
1580 tryCompare(buttons[1], "activeFocus", true);
1574
1581
1575− keyClick(Qt.Key_Tab);
1582+ keyClick(data.key);
1576 tryCompare(buttons[2], "activeFocus", true);
1583 tryCompare(buttons[2], "activeFocus", true);
1577
1584
1578− keyClick(Qt.Key_Tab);
1585+ keyClick(data.key);
1579 tryCompare(buttons[0], "activeFocus", true);
1586 tryCompare(buttons[0], "activeFocus", true);
1580
1587
1581 keyClick(Qt.Key_Escape);
1588 keyClick(Qt.Key_Escape);
@@ -1584,7 +1591,7 @@ Rectangle {
1584 tryCompare(dialogLoader, "item", null);
1591 tryCompare(dialogLoader, "item", null);
1585 }
1592 }
1586
1593
1587− function test_escColosesShutdownDialog() {
1594+ function test_escClosesShutdownDialog() {
1588 loadShell("mako");
1595 loadShell("mako");
1589
1596
1590 testCase.showPowerDialog();
1597 testCase.showPowerDialog();
tests/qmltests/tst_Shell.qml−1
@@ -568,7 +568,6 @@ Rectangle {
568 // is loaded by default
568 // is loaded by default
569 tearDown();
569 tearDown();
570 }
570 }
571−
572 }
571 }
573
572
574 function cleanup() {
573 function cleanup() {