Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/acceptTheOptionSelector into lp:ubuntu-ui-toolkit/staging

Proposed by Christian Dywan on 2016-07-29
Status: Merged
Approved by: Tim Peeters on 2016-09-23
Approved revision: 2056
Merged at revision: 2128
Proposed branch: lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/acceptTheOptionSelector
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 367 lines (+284/-6)
5 files modified
src/imports/Components/1.3/OptionSelector.qml (+60/-1)
src/imports/Components/1.3/OptionSelectorDelegate.qml (+3/-5)
src/imports/Components/Themes/Ambiance/1.3/OptionSelectorStyle.qml (+3/-0)
tests/unit/visual/tst_focus.13.qml (+28/-0)
tests/unit/visual/tst_optionselector.13.qml (+190/-0)
To merge this branch: bzr merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/acceptTheOptionSelector
Reviewer Review Type Date Requested Status
ubuntu-sdk-build-bot continuous-integration Approve on 2016-09-23
Tim Peeters 2016-07-29 Approve on 2016-09-23
Review via email: mp+301478@code.launchpad.net

Commit Message

Focus ring, arrow keys and space to expand OptionSelector

To post a comment you must log in.
Tim Peeters (tpeeters) wrote :

After opening the OptionSelector in tst_optionselector.13.qml, are 3 focus items that you can switch between using TAB. One is the Sections in the header, then there is an invisible one (that does not show a focus outline), and finally the OptionSelector. When the invisible focus is selected, the up/down arrow keys can be used to change the selected item in the optionselector, so it appears that the invisible focus is also on the optionselector.

Also, there are inline comments relating to the code.

review: Needs Fixing
2051. By Christian Dywan on 2016-08-17

Drop FIXMEs from unrelated cases (filed as bug 1614045 instead)

2052. By Christian Dywan on 2016-08-17

Re-arrange Keys.onPressed select

Christian Dywan (kalikiana) wrote :

> why do you introduce a FIXME in the test?
I removed the FIXMEs and instead filed bug 1614045.

> don't we normally use Qt.resolvedUrl() for all images?
That would be wrong. It's slow enough to show up in function call profiling as can be seen in eg. the fix for bug 1608897. In this particular case it's simply not necessary.

> missing break; ? And did you want to do something
> with the increment variable?
Good catch :-D

2053. By Christian Dywan on 2016-08-22

Unit test and bug fix for unexpected child tab focus

Christian Dywan (kalikiana) wrote :

> When the invisible focus is selected,
> the up/down arrow keys can be used to change
> the selected item in the optionselector

Fixed and enhanced the unit test to catch it.

Tim Peeters (tpeeters) wrote :

There is still an issue that some times the selection can be changed while the OptionSelector is closed. Explained how to reproduce in the unit test QML on IRC.

review: Needs Fixing
Tim Peeters (tpeeters) wrote :

^:%s/IRC/Mumble

2054. By Christian Dywan on 2016-08-26

Fully test custom OptionSelector

2055. By Christian Dywan on 2016-09-19

Use distinct State, not extending the default

2056. By Christian Dywan on 2016-09-19

Merge lp:ubuntu-ui-toolkit/staging

Tim Peeters (tpeeters) wrote :

I can no longer reproduce any faulty behavior in the optionselector.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/imports/Components/1.3/OptionSelector.qml'
2--- src/imports/Components/1.3/OptionSelector.qml 2016-05-25 12:48:10 +0000
3+++ src/imports/Components/1.3/OptionSelector.qml 2016-09-19 16:42:06 +0000
4@@ -193,6 +193,64 @@
5 __height: column.height
6 showDivider: false
7
8+ activeFocusOnTab: true
9+
10+ // Prevent scrolling on pressed when released changes the index
11+ Keys.onPressed: {
12+ // Only change index while expanded
13+ if (!listContainer.currentlyExpanded)
14+ return;
15+ var increment;
16+ switch (event.key) {
17+ case Qt.Key_Up:
18+ increment = -1;
19+ break;
20+ case Qt.Key_Down:
21+ increment = 1;
22+ default:
23+ return;
24+ }
25+ var newIndex = Toolkit.MathUtils.clamp(list.currentIndex + increment, 0, list.count - 1);
26+ if (newIndex != list.currentIndex) {
27+ event.accepted = true;
28+ }
29+ }
30+ Keys.onReleased: {
31+ var increment;
32+ switch (event.key) {
33+ case Qt.Key_Up:
34+ increment = -1;
35+ break;
36+ case Qt.Key_Down:
37+ increment = 1;
38+ break;
39+ case Qt.Key_Enter:
40+ case Qt.Key_Return:
41+ case Qt.Key_Space:
42+ if (!list.multiSelection) {
43+ event.accepted = true;
44+ listContainer.currentlyExpanded = !listContainer.currentlyExpanded;
45+ }
46+ default:
47+ return;
48+ }
49+ // Only change index while expanded
50+ if (!listContainer.currentlyExpanded)
51+ return;
52+ var newIndex = Toolkit.MathUtils.clamp(list.currentIndex + increment, 0, list.count - 1);
53+ if (newIndex != list.currentIndex) {
54+ event.accepted = true;
55+ list.delegateClicked(newIndex);
56+ var shifted = (event.modifiers & Qt.ShiftModifier);
57+ if (list.multiSelection && !shifted)
58+ list.currentItem.selected = false;
59+ list.previousIndex = list.currentIndex;
60+ list.currentIndex = newIndex;
61+ if (list.multiSelection)
62+ list.currentItem.selected = shifted ? !list.currentItem.selected : true;
63+ }
64+ }
65+
66 Column {
67 id: column
68
69@@ -212,7 +270,8 @@
70 Toolkit.StyledItem {
71 id: listContainer
72 objectName: "listContainer"
73- activeFocusOnPress: true
74+ property bool keyNavigationFocus: optionSelector.keyNavigationFocus
75+ Component.onCompleted: activeFocusOnTab = false
76
77 readonly property url chevron: __styleInstance.chevron
78 readonly property url tick: __styleInstance.tick
79
80=== modified file 'src/imports/Components/1.3/OptionSelectorDelegate.qml'
81--- src/imports/Components/1.3/OptionSelectorDelegate.qml 2016-05-25 12:48:10 +0000
82+++ src/imports/Components/1.3/OptionSelectorDelegate.qml 2016-09-19 16:42:06 +0000
83@@ -113,12 +113,10 @@
84 if (listView.container.currentlyExpanded) {
85 listView.delegateClicked(index);
86
87- if (!listView.multiSelection) {
88- listView.previousIndex = listView.currentIndex;
89- listView.currentIndex = index;
90- } else {
91+ listView.previousIndex = listView.currentIndex;
92+ listView.currentIndex = index;
93+ if (listView.multiSelection)
94 selected = !selected;
95- }
96 }
97
98 if (!listView.expanded && !listView.multiSelection) {
99
100=== modified file 'src/imports/Components/Themes/Ambiance/1.3/OptionSelectorStyle.qml'
101--- src/imports/Components/Themes/Ambiance/1.3/OptionSelectorStyle.qml 2016-01-27 15:17:56 +0000
102+++ src/imports/Components/Themes/Ambiance/1.3/OptionSelectorStyle.qml 2016-09-19 16:42:06 +0000
103@@ -36,6 +36,9 @@
104 source: shapeSource
105 }
106
107+ FocusShape {
108+ }
109+
110 ShaderEffectSource {
111 id: shapeSource
112 sourceItem: backgroundFrame
113
114=== modified file 'tests/unit/visual/tst_focus.13.qml'
115--- tests/unit/visual/tst_focus.13.qml 2016-09-13 15:01:14 +0000
116+++ tests/unit/visual/tst_focus.13.qml 2016-09-19 16:42:06 +0000
117@@ -153,6 +153,13 @@
118 property bool override: false
119 Keys.onReleased: event.accepted = override
120 }
121+ OptionSelector {
122+ id: optionSelector
123+ text: 'Pick your poison'
124+ model: ['Gin and Tonic', 'White Russian', 'Sex on the Beach', 'Strawberry Mojito']
125+ property bool override: false
126+ Keys.onReleased: event.accepted = override
127+ }
128 Button {
129 id: popoverTest
130 text: "Popovers"
131@@ -283,6 +290,8 @@
132 */
133 {tag: "ComboButton", from: pickerPanel, to: comboButton, key: Qt.Key_Tab},
134 {tag: "ComboButton(back)", from: comboButton, to: pickerPanel, key: Qt.Key_Backtab},
135+ {tag: "OptionSelector", from: comboButton, to: optionSelector, key: Qt.Key_Tab},
136+ {tag: "OptionSelector(back)", from: optionSelector, to: comboButton, key: Qt.Key_Backtab},
137 // Left click/ tap
138 {tag: "TextField(click)", from: dummy, to: textField, key: Qt.LeftButton},
139 {tag: "TextArea(click)", from: dummy, to: textArea, key: Qt.LeftButton},
140@@ -318,6 +327,22 @@
141 verify(!data.from.activeFocus, "Source component still keeps focus");
142 verify(data.to.activeFocus, "Target component is not focused - focus is on %1"
143 .arg(String(window.activeFocusItem).split("(")[0]));
144+ // No child focus movement via tab within the component
145+ if (data.key != Qt.LeftButton) {
146+ keyClick(data.key);
147+ waitForRendering(data.to, 500);
148+ verify(!has_child(data.to, window.activeFocusItem), "Target component keeps focus after second Tab");
149+ }
150+ }
151+
152+ function has_child (component, child) {
153+ var c = child;
154+ while (c) {
155+ if (c == component)
156+ return true;
157+ c = c.parent;
158+ }
159+ return false;
160 }
161
162 function test_hide_osk_when_pickerpanel_opens() {
163@@ -456,6 +481,9 @@
164 {tag: "ComboButton/Enter", key: Qt.Key_Enter, item: comboButton , signalName: 'onTriggered'},
165 {tag: "ComboButton/Return", key: Qt.Key_Return, item: comboButton, signalName: 'onTriggered'},
166 {tag: "ComboButton/Space", key: Qt.Key_Space, item: comboButton, signalName: 'onTriggered'},
167+ {tag: "OptionSelector/Enter", key: Qt.Key_Enter, item: optionSelector, signalName: 'onTriggered'},
168+ {tag: "OptionSelector/Return", key: Qt.Key_Return, item: optionSelector, signalName: 'onTriggered'},
169+ {tag: "OptionSelector/Space", key: Qt.Key_Space, item: optionSelector, signalName: 'onTriggered'},
170 ];
171 }
172 function test_trigger_via_keyboard(data) {
173
174=== added file 'tests/unit/visual/tst_optionselector.13.qml'
175--- tests/unit/visual/tst_optionselector.13.qml 1970-01-01 00:00:00 +0000
176+++ tests/unit/visual/tst_optionselector.13.qml 2016-09-19 16:42:06 +0000
177@@ -0,0 +1,190 @@
178+/*
179+ * Copyright 2012-2016 Canonical Ltd.
180+ *
181+ * This program is free software; you can redistribute it and/or modify
182+ * it under the terms of the GNU Lesser General Public License as published by
183+ * the Free Software Foundation; version 3.
184+ *
185+ * This program is distributed in the hope that it will be useful,
186+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
187+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
188+ * GNU Lesser General Public License for more details.
189+ *
190+ * You should have received a copy of the GNU Lesser General Public License
191+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
192+ */
193+
194+import QtQuick 2.4
195+import QtTest 1.0
196+import Ubuntu.Components 1.3
197+import Ubuntu.Test 1.3
198+
199+Page {
200+ width: units.gu(80)
201+ height: units.gu(60)
202+
203+ header: PageHeader {
204+ id: pageHeader
205+ title: "OptionSelector"
206+ extension: Sections {
207+ id: sections
208+ model: [ "Collapsed", "Expanded", "Custom Delegate" ]
209+ }
210+ }
211+
212+ Column {
213+ anchors {
214+ top: pageHeader.bottom
215+ left: parent.left
216+ right: parent.right
217+ bottom: parent.bottom
218+ margins: units.gu(4)
219+ }
220+
221+ OptionSelector {
222+ id: defaultSelector
223+
224+ text: "Pick your poison"
225+ property int sectionIndex: sections.selectedIndex
226+ state: sectionIndex == 2 ? 'custom' : (sectionIndex == 1 ? 'expanded' : 'default')
227+ states: [
228+ State {
229+ name: 'default'
230+ PropertyChanges {
231+ target: defaultSelector;
232+ model: [
233+ 'Gin and Tonic',
234+ 'White Russian',
235+ 'Sex on the Beach',
236+ 'Strawberry Mojito'
237+ ];
238+ currentlyExpanded: false;
239+ expanded: false;
240+ }
241+ },
242+ State {
243+ name: 'expanded'
244+ PropertyChanges {
245+ target: defaultSelector;
246+ model: [
247+ 'Ginger Ale',
248+ 'Coke',
249+ 'Ipanema',
250+ 'Virgin Mojito'
251+ ];
252+ currentlyExpanded: true;
253+ expanded: true;
254+ }
255+ },
256+ State {
257+ name: 'custom'
258+ PropertyChanges { target: defaultSelector; visible: false; }
259+ PropertyChanges { target: customSelector; visible: true; }
260+ }
261+ ]
262+ }
263+
264+ OptionSelector {
265+ id: customSelector
266+ model: customModel
267+ delegate: selectorDelegate
268+ visible: false
269+ }
270+ }
271+
272+ Component {
273+ id: selectorDelegate
274+
275+ OptionSelectorDelegate {
276+ text: name
277+ subText: description
278+ iconSource: image
279+ constrainImage: true
280+ }
281+ }
282+
283+ ListModel {
284+ id: customModel
285+ ListElement { name: "Virgin Mary"; description: "With plenty hot sauce"; image: "../../resources/optionselector/test.png" }
286+ ListElement { name: "Shirley Temple"; description: "Grenadine for the color"; image: "../../resources/optionselector/test.png" }
287+ ListElement { name: "Ipanema"; description: "Non-alcoholic version of Caipirinha"; image: "../../resources/optionselector/test.png" }
288+ ListElement { name: "Millionaire Sour"; description: "Swaps ginger ale for Irish whiskey"; image: "../../resources/optionselector/test.png" }
289+ }
290+
291+ UbuntuTestCase {
292+ name: "OptionSelectorAPI"
293+ when: windowShown
294+
295+ function test_arrow_select_data() {
296+ return [
297+ // Collapsed: no movement
298+ { 'tag': 'Collapsed/Down', key: Qt.Key_Down, index: 0, newIndex: 0, expanded: false },
299+ { 'tag': 'Collapsed/Up', key: Qt.Key_Up, index: 3, newIndex: 3, expanded: false },
300+ // Expanded: arrows select
301+ { 'tag': 'Expanded/Up/First', key: Qt.Key_Up, index: 0, newIndex: 0, expanded: true },
302+ { 'tag': 'Expanded/Down/First', key: Qt.Key_Down, index: 0, newIndex: 1, expanded: true },
303+ { 'tag': 'Expanded/Up/Last', key: Qt.Key_Up, index: 3, newIndex: 2, expanded: true },
304+ { 'tag': 'Expanded/Down/Last', key: Qt.Key_Down, index: 3, newIndex: 3, expanded: true },
305+ // Expand and collapse on press
306+ { 'tag': 'Expand(Enter)', key: Qt.Key_Enter, index: 0, expanded: false, newExpanded: true },
307+ { 'tag': 'Expand(Return)', key: Qt.Key_Return, index: 0, expanded: false, newExpanded: true },
308+ { 'tag': 'Expand(Space)', key: Qt.Key_Space, index: 0, expanded: false, newExpanded: true },
309+ { 'tag': 'Collapse(Enter)', key: Qt.Key_Enter, index: 0, expanded: true, newExpanded: false },
310+ { 'tag': 'Collapse(Return)', key: Qt.Key_Return, index: 0, expanded: true, newExpanded: false },
311+ { 'tag': 'Collapse(Space)', key: Qt.Key_Space, index: 0, expanded: true, newExpanded: false },
312+ ]
313+ }
314+ function test_arrow_select(data, selector, sectionIndex) {
315+ if (!selector) {
316+ selector = defaultSelector;
317+ sectionIndex = 0;
318+ }
319+ sections.selectedIndex = sectionIndex;
320+ selector.forceActiveFocus();
321+ selector.currentlyExpanded = data.expanded;
322+ selector.selectedIndex = data.index;
323+ waitForRendering(selector);
324+ keyClick(data.key);
325+ waitForRendering(selector);
326+ if (data.newIndex)
327+ compare(selector.selectedIndex, data.newIndex, 'Wrong index after key press');
328+ if (data.newExpanded)
329+ compare(selector.currentlyExpanded, data.newExpanded, data.newExpanded ? 'Not expanded' : 'Not collapsed');
330+ }
331+
332+ function test_expanded() {
333+ var selector = defaultSelector;
334+ sections.selectedIndex = 0;
335+ selector.forceActiveFocus();
336+ waitForRendering(selector);
337+ compare(selector.currentlyExpanded, false);
338+ compare(selector.expanded, false);
339+ keyClick(Qt.Key_Return);
340+ waitForRendering(selector);
341+ compare(selector.currentlyExpanded, true);
342+ compare(selector.expanded, false);
343+
344+ sections.selectedIndex = 1;
345+ waitForRendering(selector);
346+ compare(selector.currentlyExpanded, true);
347+ compare(selector.expanded, true);
348+
349+ sections.selectedIndex = 0;
350+ waitForRendering(selector);
351+ compare(selector.expanded, false);
352+ compare(selector.currentlyExpanded, false);
353+ compare(selector.selectedIndex, 0);
354+ selector.forceActiveFocus();
355+ waitForRendering(selector);
356+ keyClick(Qt.Key_Down);
357+ compare(selector.selectedIndex, 0);
358+ }
359+
360+ function test_custom_data() {
361+ return test_arrow_select_data()
362+ }
363+ function test_custom(data) {
364+ test_arrow_select(data, customSelector, 2);
365+ }
366+ }
367+}

Subscribers

People subscribed via source and target branches