Merge lp:~fboucault/unity-2d/keyboard_navigation_experimental into lp:unity-2d/3.0

Proposed by Florian Boucault on 2011-07-27
Status: Merged
Approved by: Florian Boucault on 2011-07-27
Approved revision: 642
Merged at revision: 651
Proposed branch: lp:~fboucault/unity-2d/keyboard_navigation_experimental
Merge into: lp:unity-2d/3.0
Diff against target: 1004 lines (+476/-157)
14 files modified
places/CenteredGridView.qml (+26/-0)
places/Home.qml (+36/-18)
places/HomeShortcuts.qml (+26/-1)
places/ListViewWithHeaders.qml (+277/-0)
places/ListViewWithScrollbar.qml (+7/-30)
places/PlaceEntryView.qml (+40/-22)
places/Renderer.qml (+3/-3)
places/RendererGrid.qml (+17/-72)
places/Scrollbar.qml (+1/-1)
places/SearchEntry.qml (+3/-1)
places/SearchRefine.qml (+9/-0)
places/SearchRefineOptionType.qml (+5/-1)
places/UnityEmptySearchRenderer.qml (+1/-1)
places/dash.qml (+25/-7)
To merge this branch: bzr merge lp:~fboucault/unity-2d/keyboard_navigation_experimental
Reviewer Review Type Date Requested Status
Gerry Boland (community) search for bugs 2011-07-27 Needs Fixing on 2011-07-27
Review via email: mp+69480@code.launchpad.net

Description of the change

[dash] Implemented keyboard navigation.

To post a comment you must log in.
Gerry Boland (gerboland) wrote :

I've found bugs:

- if number of results less than 6, the "See more/fewer results" option is of course hidden but
is still found by the keyboard navigation. Repro: do a search which returns <6 Applications. Hit down: where did focus go? Hit down again: there it is!

- Do search which returns large list of applications, scroll down. Now return to search box &
  do new search (again returning large list). New search results are still scrolled down.

- At Home, if you leave mouse highlighting one gridItem, using keyboard to navigate gives
  you a second "active" looking gridItem.

- At Home, press down, down, right, right, up, up, then Esc to close it. Now
  open Home again, press down, down and you appear on the third column now.

- At Search List of more than 6 rows or so, be about half-way down the applications list.
  Press up, down, left, right, and see how the list moves on the left/right motion - it
  shouldn't. This one is hard to reproduce - I get it searching for "e".

- At "Search Applications", at the search box, press right. Where is focus?

- Quickly type Meta, "w", down. See where focus appears - at "See Fewer Results" in the Files&Folders section

Suggestions/Questions?
- highlighted grid item's text has underline. Correct?

- At Home, continually pressing right on gridItems should wrap around?? Instead of stopping?
  Similarly for down & up? Fewer barriers?

review: Needs Fixing (search for bugs)
Florian Boucault (fboucault) wrote :

> I've found bugs:
>
> - if number of results less than 6, the "See more/fewer results" option is of
> course hidden but
> is still found by the keyboard navigation. Repro: do a search which returns <6
> Applications. Hit down: where did focus go? Hit down again: there it is!

[REPORT BUG] It is done that way for accessibility reasons. The name of the group needs to be spoken to blind people. Maybe there is a nicer way to do it.

>
> - Do search which returns large list of applications, scroll down. Now return
> to search box &
> do new search (again returning large list). New search results are still
> scrolled down.

[NEEDS FIXING] Scrolling down is done with the keyboard.

>
> - At Home, if you leave mouse highlighting one gridItem, using keyboard to
> navigate gives
> you a second "active" looking gridItem.
>

[REPORT BUG] Not sure what the solution should be. Should the keyboard focus follow the mouse?

> - At Home, press down, down, right, right, up, up, then Esc to close it. Now
> open Home again, press down, down and you appear on the third column now.
>

The dash is going stateful so that behaviour is _probably_ ok.

> - At Search List of more than 6 rows or so, be about half-way down the
> applications list.
> Press up, down, left, right, and see how the list moves on the left/right
> motion - it
> shouldn't. This one is hard to reproduce - I get it searching for "e".
>

[REPORT BUG]

> - At "Search Applications", at the search box, press right. Where is focus?
>

[NEEDS FIXING] I think the focus was sent to the search filters previously.

> - Quickly type Meta, "w", down. See where focus appears - at "See Fewer
> Results" in the Files&Folders section
>

[REPORT BUG] Not a big issue but to solve it it would require acting on keyboard presses only when the results are all in. Not sure if it's right especially if we consider network sources.

>
> Suggestions/Questions?
> - highlighted grid item's text has underline. Correct?
>

[REPORT BUG] Should be like in Unity 3D instead.

> - At Home, continually pressing right on gridItems should wrap around??
> Instead of stopping?
> Similarly for down & up? Fewer barriers?

It's fine like it is I think.

639. By Florian Boucault on 2011-07-27

Remade RendererGrid.interactive false thus preventing mouse interaction with the individual groups.

640. By Florian Boucault on 2011-07-27

Prevent the focus to be on the refine search pane when it is folded.

641. By Florian Boucault on 2011-07-27

Added FIXME/

642. By Florian Boucault on 2011-07-27

Merged colo:proper_grid

Florian Boucault (fboucault) wrote :

> > I've found bugs:
> >
> > - if number of results less than 6, the "See more/fewer results" option is
> of
> > course hidden but
> > is still found by the keyboard navigation. Repro: do a search which returns
> <6
> > Applications. Hit down: where did focus go? Hit down again: there it is!
>
> [REPORT BUG] It is done that way for accessibility reasons. The name of the
> group needs to be spoken to blind people. Maybe there is a nicer way to do it.
>
> >
> > - Do search which returns large list of applications, scroll down. Now
> return
> > to search box &
> > do new search (again returning large list). New search results are still
> > scrolled down.
>
> [NEEDS FIXING] Scrolling down is done with the keyboard.
>

Fixed.

> >
> > - At Home, if you leave mouse highlighting one gridItem, using keyboard to
> > navigate gives
> > you a second "active" looking gridItem.
> >
>
> [REPORT BUG] Not sure what the solution should be. Should the keyboard focus
> follow the mouse?
>
> > - At Home, press down, down, right, right, up, up, then Esc to close it. Now
> > open Home again, press down, down and you appear on the third column now.
> >
>
> The dash is going stateful so that behaviour is _probably_ ok.
>
> > - At Search List of more than 6 rows or so, be about half-way down the
> > applications list.
> > Press up, down, left, right, and see how the list moves on the left/right
> > motion - it
> > shouldn't. This one is hard to reproduce - I get it searching for "e".
> >
>
> [REPORT BUG]
>
> > - At "Search Applications", at the search box, press right. Where is focus?
> >
>
> [NEEDS FIXING] I think the focus was sent to the search filters previously.
>

Fixed.

> > - Quickly type Meta, "w", down. See where focus appears - at "See Fewer
> > Results" in the Files&Folders section
> >
>
> [REPORT BUG] Not a big issue but to solve it it would require acting on
> keyboard presses only when the results are all in. Not sure if it's right
> especially if we consider network sources.
>
> >
> > Suggestions/Questions?
> > - highlighted grid item's text has underline. Correct?
> >
>
> [REPORT BUG] Should be like in Unity 3D instead.
>
> > - At Home, continually pressing right on gridItems should wrap around??
> > Instead of stopping?
> > Similarly for down & up? Fewer barriers?
>
> It's fine like it is I think.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'places/CenteredGridView.qml'
2--- places/CenteredGridView.qml 2011-06-23 17:08:53 +0000
3+++ places/CenteredGridView.qml 2011-07-27 19:03:19 +0000
4@@ -24,6 +24,32 @@
5 property int delegateWidth: 100
6 property int delegateHeight: 100
7
8+ /* Manually handle key presses so that when the 'interactive' property is
9+ set to false the keyboard navigation still works.
10+
11+ Ref.: https://bugreports.qt.nokia.com/browse/QTBUG-17051
12+ */
13+ function selectChild(index) {
14+ if (index < 0 || index >= count) return false
15+ currentIndex = index
16+ return true
17+ }
18+
19+ Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
20+ function handleKeyPress(key) {
21+ switch (key) {
22+ case Qt.Key_Right:
23+ return selectChild(currentIndex+1)
24+ case Qt.Key_Left:
25+ return selectChild(currentIndex-1)
26+ case Qt.Key_Up:
27+ return selectChild(currentIndex-cellsPerRow)
28+ case Qt.Key_Down:
29+ return selectChild(currentIndex+cellsPerRow)
30+ }
31+ return false
32+ }
33+
34 /* Compute the number of cells per row and column so that:
35 - the items are always centered horizontally within the grid (= same
36 margins on the right and left sides of the grid) when flow is set
37
38=== modified file 'places/Home.qml'
39--- places/Home.qml 2011-07-26 16:36:51 +0000
40+++ places/Home.qml 2011-07-27 19:03:19 +0000
41@@ -19,7 +19,7 @@
42 import QtQuick 1.0
43 import Unity2d 1.0 /* Necessary for SortFilterProxyModel and for the ImageProvider serving image://icons/theme_name/icon_name */
44
45-Item {
46+FocusScope {
47 property variant model: PageModel {
48 /* model.entrySearchQuery is copied over to all place entries's globalSearchQuery property */
49 onEntrySearchQueryChanged: {
50@@ -96,26 +96,39 @@
51 ListViewWithScrollbar {
52 id: globalSearch
53
54+ focus: globalSearchActive
55 opacity: globalSearchActive ? 1 : 0
56 anchors.fill: parent
57
58- list.model: dash.places
59-
60- list.delegate: UnityDefaultRenderer {
61- width: ListView.view.width
62-
63- parentListView: list
64- placeEntryModel: item
65- displayName: item.name
66- iconHint: item.icon
67-
68- model: item.globalResultsModel
69+ model: dash.places
70+
71+ bodyDelegate: UnityDefaultRenderer {
72+ placeEntryModel: model.item
73+ displayName: model.item.name
74+ iconHint: model.item.icon
75+
76+ group_model: model.item.globalResultsModel
77+ property bool focusable: group_model != undefined && group_model.count > 0
78+ }
79+
80+ headerDelegate: GroupHeader {
81+ visible: body.needHeader && body.focusable
82+ height: visible ? 32 : 0
83+
84+ property bool foldable: body.folded != undefined
85+ availableCount: foldable && body.group_model != null ? body.group_model.count - body.cellsPerRow : 0
86+ folded: foldable ? body.folded : false
87+ onClicked: if(foldable) body.folded = !body.folded
88+
89+ icon: body.iconHint
90+ label: body.displayName
91 }
92 }
93
94- Rectangle {
95+ FocusScope {
96 id: shortcuts
97
98+ focus: !globalSearchActive
99 opacity: (!globalSearchActive && (shortcutsActive || dashView.dashMode == DashDeclarativeView.FullScreenMode)) ? 1 : 0
100 anchors.horizontalCenter: parent.horizontalCenter
101 anchors.verticalCenter: parent.verticalCenter
102@@ -123,11 +136,14 @@
103 width: 888
104 height: 466
105
106- radius: 5
107- border.width: 1
108- /* FIXME: wrong colors */
109- border.color: Qt.rgba(1, 1, 1, 0.2)
110- color: Qt.rgba(0, 0, 0, 0.3)
111+ Rectangle {
112+ anchors.fill: parent
113+ radius: 5
114+ border.width: 1
115+ /* FIXME: wrong colors */
116+ border.color: Qt.rgba(1, 1, 1, 0.2)
117+ color: Qt.rgba(0, 0, 0, 0.3)
118+ }
119
120 Button {
121 id: closeShortcutsButton
122@@ -158,11 +174,13 @@
123 on the default version if a custom one doesn’t exist. */
124 Loader {
125 id: customShortcutsLoader
126+ focus: status == Loader.Ready
127 anchors.fill: parent
128 source: "HomeShortcutsCustomized.qml"
129 }
130 Loader {
131 id: defaultShortcutsLoader
132+ focus: !customShortcutsLoader.focus
133 anchors.fill: parent
134 source: (customShortcutsLoader.status == Loader.Error) ? "HomeShortcuts.qml" : ""
135 }
136
137=== modified file 'places/HomeShortcuts.qml'
138--- places/HomeShortcuts.qml 2011-07-04 23:07:20 +0000
139+++ places/HomeShortcuts.qml 2011-07-27 19:03:19 +0000
140@@ -19,16 +19,41 @@
141 import QtQuick 1.0
142 import Unity2d 1.0 /* Necessary for the ImageProvider serving image://icons/theme_name/icon_name */
143
144-Flow {
145+Grid {
146 anchors.fill: parent
147 anchors.topMargin: 26
148 anchors.bottomMargin: 35
149 anchors.leftMargin: 32
150 anchors.rightMargin: 32
151 spacing: 61
152+ columns: 4
153+ rows: 2
154+
155+ function selectChild(index) {
156+ if (index < 0 || index >= children.length) return false
157+ currentIndex = index
158+ children[index].focus = true
159+ return true
160+ }
161+
162+ property int currentIndex: 0
163+ Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
164+ function handleKeyPress(key) {
165+ switch (key) {
166+ case Qt.Key_Right:
167+ return selectChild(currentIndex+1)
168+ case Qt.Key_Left:
169+ return selectChild(currentIndex-1)
170+ case Qt.Key_Up:
171+ return selectChild(currentIndex-columns)
172+ case Qt.Key_Down:
173+ return selectChild(currentIndex+columns)
174+ }
175+ }
176
177 /* FIXME: dummy icons need to be replaced by design's */
178 HomeButton {
179+ focus: true
180 label: u2d.tr("Media Apps")
181 icon: "artwork/find_media_apps.png"
182 onClicked: activatePlaceEntry("/usr/share/unity/places/applications.place", "Files", 9)
183
184=== added file 'places/ListViewWithHeaders.qml'
185--- places/ListViewWithHeaders.qml 1970-01-01 00:00:00 +0000
186+++ places/ListViewWithHeaders.qml 2011-07-27 19:03:19 +0000
187@@ -0,0 +1,277 @@
188+/*
189+ * This file is part of unity-2d
190+ *
191+ * Copyright 2010-2011 Canonical Ltd.
192+ *
193+ * This program is free software; you can redistribute it and/or modify
194+ * it under the terms of the GNU General Public License as published by
195+ * the Free Software Foundation; version 3.
196+ *
197+ * This program is distributed in the hope that it will be useful,
198+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
199+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
200+ * GNU General Public License for more details.
201+ *
202+ * You should have received a copy of the GNU General Public License
203+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
204+ */
205+
206+import QtQuick 1.0
207+
208+/*
209+ List item that behaves similarly to a ListView but supports adding headers
210+ before every delegate.
211+ It works around the lack of flexibility in section headers positioning of
212+ ListView (cf. http://bugreports.qt.nokia.com/browse/QTBUG-12880). It also
213+ supports delegates that are flickable by flicking their content properly
214+ depending on where you are in the list.
215+
216+ To use it the following properties need to be set:
217+ - bodyDelegate: Component used as a template for each item of the model; it
218+ must have the following properties:
219+ - 'contentY': same definition as a Flickable's
220+ - 'totalHeight': the total height of the content of the body
221+ - 'currentItem': a reference to the item of the body (e.g. a delegate of
222+ a ListView) currently focused by the body
223+ - headerDelegate: Component used as a template for the header preceding each body
224+
225+ Two behaviours are available for the headers positioning:
226+ - normal: the headers are always positioned just before the body
227+ - accordion: the headers are stacked at the top and bottom of the list
228+
229+ Currently, it only works in a vertical layout.
230+*/
231+FocusScope {
232+ id: list
233+
234+ property alias flickable: mouse
235+ property alias model: repeater.model
236+ property bool accordion: false
237+ /* bodyDelegate must be an item that has the following properties:
238+ - 'contentY'
239+ - 'totalHeight'
240+ - 'currentItem'
241+ */
242+ property Component bodyDelegate
243+ property Component headerDelegate
244+ property int currentIndex: -1
245+ /* FIXME: Should be read-only but it is not possible to define a read-only property from QML.
246+ Ref.: https://bugreports.qt.nokia.com//browse/QTBUG-15257
247+ */
248+ property variant currentItem: items.childFromIndex(currentIndex)
249+
250+
251+ clip: true
252+
253+ /* updateMouseContentY() needs to be called when any of the variable
254+ involved in the computation of the 'y' property of the currentSubItem changes.
255+
256+ if accordion is false:
257+ - heightFirstChildren(index)
258+ - headerLoader.height
259+ - list.height
260+
261+ if accordion is true:
262+ - heightFirstChildren(index)
263+ - items.availableHeight
264+ - heightFirstHeaders(index)
265+
266+ items.contentHeight seems to depend on all of those so it's enough to
267+ depend only on it.
268+ */
269+ function updateMouseContentY() {
270+ if (currentSubItem != undefined) {
271+ mouse.contentY = Math.max(currentSubItem.mapToItem(mouse.contentItem, 0, 0).y -list.height/2, 0)
272+ }
273+ }
274+ property variant currentSubItem: currentItem != undefined ? currentItem.bodyLoader.item.currentItem : undefined
275+ onCurrentSubItemChanged: updateMouseContentY()
276+
277+ Connections {
278+ target: items
279+ onContentHeightChanged: updateMouseContentY()
280+ }
281+
282+ FocusScope {
283+ id: items
284+
285+ property int availableHeight: list.height - heightFirstHeaders(repeater.count)
286+ property int contentHeight: items.heightFirstChildren(repeater.count)
287+ property real value: mouse.contentY
288+
289+ anchors.fill: parent
290+
291+ function heightFirstChildren(n) {
292+ var i
293+ var totalHeight = 0
294+ /* items.children contains both the repeated items and the repeater
295+ itself. Skip and ignore the repeater. */
296+ for (i=0; i<n && i<children.length; i++) {
297+ if(children[i] == repeater) {n += 1; continue}
298+ totalHeight += children[i].height
299+ }
300+ return totalHeight
301+ }
302+
303+ function heightFirstHeaders(n) {
304+ var i
305+ var totalHeight = 0
306+ /* items.children contains both the repeated items and the repeater
307+ itself. Skip and ignore the repeater. */
308+ for (i=0; i<n && i<children.length; i++) {
309+ if(children[i] == repeater) {n += 1; continue}
310+ totalHeight += children[i].headerLoader.height
311+ }
312+ return totalHeight
313+ }
314+
315+ function clamp(x, min, max) {
316+ return Math.max(Math.min(x, max), min)
317+ }
318+
319+
320+ /* Keyboard navigation */
321+ function isIndexValid(index) {
322+ /* Assuming that children contains exactly one item that is not a child (repeater) */
323+ return index >= 0 && index < children.length-1
324+ }
325+
326+ focus: true
327+ Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
328+ function handleKeyPress(key) {
329+ switch (key) {
330+ case Qt.Key_Down:
331+ return selectNextEnabled()
332+ case Qt.Key_Up:
333+ return selectPreviousEnabled()
334+ }
335+ }
336+
337+ function childFromIndex(index) {
338+ var indexInChildren = 0
339+ for(var i=0; i<children.length; i++) {
340+ if (children[i] != repeater) {
341+ if (indexInChildren == index) return children[i]
342+ indexInChildren++
343+ }
344+ }
345+
346+ return undefined
347+ }
348+
349+ function selectNextEnabled() {
350+ var index = currentIndex
351+ do {
352+ index += 1
353+ if (!isIndexValid(index)) return false
354+ } while(!childFromIndex(index).focusable)
355+ currentIndex = index
356+ return true
357+ }
358+
359+ function selectPreviousEnabled() {
360+ var index = currentIndex
361+ do {
362+ index -= 1
363+ if (!isIndexValid(index)) return false
364+ } while(!childFromIndex(index).focusable)
365+ currentIndex = index
366+ return true
367+ }
368+
369+ property bool needsFocus: false
370+ onChildrenChanged: {
371+ /* FIXME: this workarounds the fact that list.focus is set to false
372+ when the child with focus is destroyed
373+ */
374+ if (needsFocus) {
375+ list.focus = true
376+ needsFocus = false
377+ }
378+ /* Assuming that children contains exactly one item that is not a child (repeater) */
379+ if(children.length <= 1) {
380+ list.currentIndex = -1
381+ } else {
382+ list.currentIndex = -1
383+ selectNextEnabled()
384+ }
385+ }
386+
387+ Repeater {
388+ id: repeater
389+
390+ FocusScope {
391+ property alias bodyLoader: bodyLoader
392+ property alias headerLoader: headerLoader
393+
394+ focus: index == list.currentIndex
395+ Component.onDestruction: items.needsFocus = true
396+
397+ width: list.width
398+ height: headerLoader.height + bodyLoader.item.totalHeight
399+ property bool focusable: bodyLoader.item.focusable
400+
401+ property int pmin: pmax - (ymax - ymin)
402+ property int pmax: items.heightFirstChildren(index) - ymin
403+ property int ymin: list.accordion ? items.heightFirstHeaders(index) : -headerLoader.height
404+ property int ymax: list.accordion ? ymin + items.availableHeight : list.height
405+ y: items.clamp(-items.value + ymax + pmin, ymin, ymax)
406+
407+ Loader {
408+ id: headerLoader
409+
410+ focus: visible
411+ KeyNavigation.down: bodyLoader
412+ sourceComponent: headerDelegate
413+ onLoaded: item.focus = true
414+ width: parent.width
415+
416+ /* Workaround Qt bug http://bugreports.qt.nokia.com/browse/QTBUG-18857
417+ More documentation at http://bugreports.qt.nokia.com/browse/QTBUG-18011
418+ */
419+ property int index
420+ Binding { target: headerLoader; property: "index"; value: index }
421+ property variant model
422+ Binding { target: headerLoader; property: "model"; value: model }
423+ property variant body
424+ Binding { target: headerLoader; property: "body"; value: bodyLoader.item }
425+ }
426+
427+ Loader {
428+ id: bodyLoader
429+
430+ focus: !headerLoader.focus
431+ KeyNavigation.up: headerLoader
432+ sourceComponent: list.bodyDelegate
433+ onLoaded: item.focus = true
434+ width: parent.width
435+ anchors.top: headerLoader.bottom
436+ height: items.clamp(parent.ymax - parent.y, 0, item.totalHeight)
437+
438+ Binding {
439+ target: bodyLoader.item
440+ property: "contentY"
441+ value: Math.max(items.value - pmax, 0)
442+ }
443+
444+ /* Workaround Qt bug http://bugreports.qt.nokia.com/browse/QTBUG-18857
445+ More documentation at http://bugreports.qt.nokia.com/browse/QTBUG-18011
446+ */
447+ property int index
448+ Binding { target: bodyLoader; property: "index"; value: index }
449+ property variant model
450+ Binding { target: bodyLoader; property: "model"; value: model }
451+ }
452+ }
453+ }
454+ }
455+
456+ Flickable {
457+ id: mouse
458+
459+ z: -1
460+ anchors.fill: parent
461+ contentWidth: parent.width
462+ contentHeight: items.contentHeight
463+ }
464+}
465
466=== modified file 'places/ListViewWithScrollbar.qml'
467--- places/ListViewWithScrollbar.qml 2011-06-23 17:08:53 +0000
468+++ places/ListViewWithScrollbar.qml 2011-07-27 19:03:19 +0000
469@@ -18,44 +18,21 @@
470
471 import QtQuick 1.0
472
473-Item {
474- property alias list: list
475+FocusScope {
476 property alias scrollbar: scrollbar
477+ property alias model: list.model
478+ property alias bodyDelegate: list.bodyDelegate
479+ property alias headerDelegate: list.headerDelegate
480
481- ListView {
482+ ListViewWithHeaders {
483 id: list
484
485+ focus: true
486 anchors.top: parent.top
487 anchors.bottom: parent.bottom
488 anchors.left: parent.left
489 anchors.right: scrollbar.left
490 anchors.rightMargin: 15
491-
492- clip: true
493- /* FIXME: proper spacing cannot be set because of the hack in Group.qml
494- whereby empty groups are still in the list but invisible and of
495- height 0.
496- */
497- //spacing: 31
498-
499- orientation: ListView.Vertical
500-
501- /* WARNING - HACK - FIXME
502- Issue:
503- User wise annoying jumps in the list are observable if cacheBuffer is
504- set to 0 (which is the default value). States such as 'folded' are
505- lost when scrolling a lot.
506-
507- Explanation:
508- The height of the Group delegate depends on its content. However its
509- content is not known until the delegate is instantiated because it
510- depends on the number of results displayed by its GridView.
511-
512- Resolution:
513- We set the cacheBuffer to the biggest possible int in order to make
514- sure all delegates are always instantiated.
515- */
516- cacheBuffer: 2147483647
517 }
518
519 Scrollbar {
520@@ -67,7 +44,7 @@
521 anchors.bottomMargin: 10
522 anchors.right: parent.right
523
524- targetFlickable: list
525+ targetFlickable: list.flickable
526
527 /* Hide the scrollbar if there is less than a page of results */
528 opacity: targetFlickable.visibleArea.heightRatio < 1.0 ? 1.0 : 0.0
529
530=== modified file 'places/PlaceEntryView.qml'
531--- places/PlaceEntryView.qml 2011-07-19 07:46:03 +0000
532+++ places/PlaceEntryView.qml 2011-07-27 19:03:19 +0000
533@@ -19,7 +19,7 @@
534 import QtQuick 1.0
535 import Unity2d 1.0 /* Necessary for SortFilterProxyModel */
536
537-Item {
538+FocusScope {
539 id: placeEntryView
540
541 /* An instance of PlaceEntryModel */
542@@ -33,7 +33,7 @@
543 var placeEntry, i
544 for (i=0; i<placeEntryView.model.entryGroupsModel.count; i=i+1) {
545 firstGroupModel.groupId = i
546- if (firstGroupModel.count() != 0) {
547+ if (firstGroupModel.count != 0) {
548 var firstResult = firstGroupModel.get(0)
549 /* Places give back the uri of the item in 'column_0' per specification */
550 var uri = firstResult.column_0
551@@ -62,6 +62,7 @@
552 ListViewWithScrollbar {
553 id: results
554
555+ focus: true
556 anchors.fill: parent
557
558 /* The group's delegate is chosen dynamically depending on what
559@@ -74,10 +75,10 @@
560 If groupRenderer == 'UnityShowcaseRenderer' then it will look for
561 the file 'UnityShowcaseRenderer.qml' and use it to render the group.
562 */
563- list.delegate: Loader {
564- property string groupRenderer: column_0
565- property string displayName: column_1
566- property string iconHint: column_2
567+ bodyDelegate: Loader {
568+ property string groupRenderer: model.column_0
569+ property string displayName: model.column_1
570+ property string iconHint: model.column_2
571 property int groupId: index
572
573 source: groupRenderer ? groupRenderer+".qml" : ""
574@@ -86,12 +87,8 @@
575 console.log("Failed to load renderer", groupRenderer)
576 }
577
578- width: ListView.view.width
579-
580 /* Model that will be used by the group's delegate */
581- SortFilterProxyModel {
582- id: group_model
583-
584+ property variant group_model: SortFilterProxyModel {
585 model: placeEntryView.model.entryResultsModel
586
587 /* resultsModel contains data for all the groups of a given Place.
588@@ -102,16 +99,37 @@
589 filterRegExp: RegExp("^%1$".arg(groupId)) /* exact match */
590 }
591
592- onLoaded: {
593- item.parentListView = results.list
594- item.displayName = displayName
595- item.iconHint = iconHint
596- item.groupId = groupId
597- item.model = group_model
598- item.placeEntryModel = placeEntryView.model
599- }
600- }
601-
602- list.model: placeEntryView.model != undefined ? placeEntryView.model.entryGroupsModel : undefined
603+ /* Required by ListViewWithHeaders when the loaded Renderer is a Flickable.
604+ In that case the list view scrolls the Flickable appropriately.
605+ */
606+ property int totalHeight: item.totalHeight != undefined ? item.totalHeight : 0
607+ property int contentY
608+ Binding { target: item; property: "contentY"; value: contentY }
609+ property bool focusable: group_model.count > 0
610+ property variant currentItem: item.currentItem
611+
612+ Binding { target: item; property: "displayName"; value: displayName }
613+ Binding { target: item; property: "iconHint"; value: iconHint }
614+ Binding { target: item; property: "groupId"; value: groupId }
615+ Binding { target: item; property: "group_model"; value: group_model }
616+ Binding { target: item; property: "placeEntryModel"; value: placeEntryView.model }
617+
618+ onLoaded: item.focus = true
619+ }
620+
621+ headerDelegate: GroupHeader {
622+ visible: body.item.needHeader && body.focusable
623+ height: visible ? 32 : 0
624+
625+ property bool foldable: body.item.folded != undefined
626+ availableCount: foldable ? body.group_model.count - body.item.cellsPerRow : 0
627+ folded: foldable ? body.item.folded : false
628+ onClicked: if(foldable) body.item.folded = !body.item.folded
629+
630+ icon: body.iconHint
631+ label: body.displayName
632+ }
633+
634+ model: placeEntryView.model != undefined ? placeEntryView.model.entryGroupsModel : undefined
635 }
636 }
637
638=== modified file 'places/Renderer.qml'
639--- places/Renderer.qml 2011-06-23 17:08:53 +0000
640+++ places/Renderer.qml 2011-07-27 19:03:19 +0000
641@@ -26,11 +26,11 @@
642 itself. A typical renderer is the UnityDefaultRender that lays out the items
643 in a grid of icons with the item's title underneath it.
644 */
645-Item {
646+FocusScope {
647 property string displayName /* Name of the group typically displayed in the header */
648 property string iconHint /* Icon id of the group */
649 property int groupId /* Index of the group */
650- property variant model /* List model containing the items to be displayed by the renderer */
651+ property variant group_model /* List model containing the items to be displayed by the renderer */
652 property variant placeEntryModel /* Reference to the place entry the group belongs to */
653- property variant parentListView /* Reference to the ListView the renderer is nested into */
654+ property bool needHeader: false /* Whether or not the renderer requires a header to be displayed */
655 }
656
657=== modified file 'places/RendererGrid.qml'
658--- places/RendererGrid.qml 2011-06-26 15:41:06 +0000
659+++ places/RendererGrid.qml 2011-07-27 19:03:19 +0000
660@@ -28,6 +28,11 @@
661 Renderer {
662 id: renderer
663
664+ needHeader: true
665+ property alias cellsPerRow: results.cellsPerRow
666+ property alias contentY: results.contentY
667+ property alias currentItem: results.currentItem
668+
669 property variant cellRenderer
670 property bool folded
671 folded: {
672@@ -44,85 +49,23 @@
673 property int horizontalSpacing: 26
674 property int verticalSpacing: 26
675
676- /* Using results.contentHeight produces binding loop warnings and potential
677- rendering issues. We compute the height manually.
678- */
679- /* FIXME: tricking the system by making the delegate of height 0 and invisible
680- is no good: the item in the model still exists and some things
681- such as keyboard selection break.
682- */
683- visible: results.model.totalCount > 0
684- height: visible ? header.height + results_layout.anchors.topMargin + results.totalHeight : 0
685- //Behavior on height {NumberAnimation {duration: 200}}
686-
687- GroupHeader {
688- id: header
689-
690- availableCount: results.model.totalCount - results.cellsPerRow
691- folded: parent.folded
692- anchors.top: parent.top
693- anchors.left: parent.left
694- anchors.right: parent.right
695- height: 32
696- icon: parent.iconHint
697- label: parent.displayName
698-
699- onClicked: parent.folded = !parent.folded
700- }
701+ /* FIXME: using results_layout.anchors.topMargin in the following expression
702+ causes QML to think they might be an anchor loop. */
703+ property int totalHeight: results.count > 0 ? results_layout.anchors.topMargin + results.totalHeight : 0
704
705 Item {
706 id: results_layout
707
708- anchors.top: header.bottom
709- anchors.topMargin: 22
710- anchors.left: parent.left
711+ anchors.fill: parent
712+ anchors.topMargin: 12
713 anchors.leftMargin: 2
714- anchors.right: parent.right
715- anchors.bottom: parent.bottom
716
717 CenteredGridView {
718 id: results
719
720- /* FIXME: this is a gross hack compensating for the lack of sections
721- in GridView (see ListView.section).
722-
723- We nest GridViews inside a ListView and add headers manually
724- (GroupHeader). The total height of each Group is computed
725- manually and given back to the ListView. However that size cannot
726- be used by the individual GridViews because it would make them
727- load all of their delegates at once using far too much memory and
728- processing power. Instead we constrain the height of the GridViews
729- and compute their position manually to compensate for the position
730- changes when flicking the ListView.
731-
732- We assume that renderer.parentListView is the ListView we nest our
733- GridView into.
734- */
735- property variant flickable: renderer.parentListView.contentItem
736-
737- /* flickable.contentY*0 is equal to 0 but is necessary in order to
738- have the entire expression being evaluated at the right moment.
739- */
740- property int inFlickableY: flickable.contentY*0+parent.mapToItem(flickable, 0, 0).y
741- /* note: testing for flickable.height < 0 is probably useless since it is
742- unlikely flickable.height will ever be negative.
743- */
744- property int compensateY: inFlickableY > 0 || flickable.height < 0 || totalHeight < flickable.height ? 0 : -inFlickableY
745-
746- /* Synchronise the position and content's position of the GridView
747- with the current position of flickable's visibleArea */
748- function synchronisePosition() {
749- y = compensateY
750- contentY = compensateY
751- }
752-
753- onCompensateYChanged: synchronisePosition()
754- /* Any change in content needs to trigger a synchronisation */
755- onCountChanged: synchronisePosition()
756- onModelChanged: synchronisePosition()
757-
758- width: flickable.width
759- height: Math.min(totalHeight, flickable.height)
760+ focus: true
761+
762+ anchors.fill: parent
763
764 property int totalHeight: results.cellHeight*Math.ceil(count/cellsPerRow)
765
766@@ -134,7 +77,7 @@
767 interactive: false
768 clip: true
769
770- delegate: Item {
771+ delegate: FocusScope {
772
773 width: results.cellWidth
774 height: results.cellHeight
775@@ -155,6 +98,7 @@
776 height: results.delegateHeight
777 anchors.horizontalCenter: parent.horizontalCenter
778
779+ focus: true
780 sourceComponent: cellRenderer
781 onLoaded: {
782 item.uri = uri
783@@ -162,13 +106,14 @@
784 item.mimetype = mimetype
785 item.displayName = displayName
786 item.comment = comment
787+ item.focus = true
788 }
789 }
790 }
791
792 /* Only display one line of items when folded */
793 model: SortFilterProxyModel {
794- model: renderer.model != undefined ? renderer.model : null
795+ model: renderer.group_model != undefined ? renderer.group_model : null
796 limit: folded ? results.cellsPerRow : -1
797 }
798 }
799
800=== modified file 'places/Scrollbar.qml'
801--- places/Scrollbar.qml 2011-06-26 15:16:43 +0000
802+++ places/Scrollbar.qml 2011-07-27 19:03:19 +0000
803@@ -83,7 +83,7 @@
804 when: !dragMouseArea.drag.active
805 }
806
807- height: Math.max(minimalHeight, targetFlickable.visibleArea.heightRatio * scrollbar.height)
808+ height: Math.min(scrollbar.height, Math.max(minimalHeight, targetFlickable.visibleArea.heightRatio * scrollbar.height))
809
810 Behavior on height {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
811
812
813=== modified file 'places/SearchEntry.qml'
814--- places/SearchEntry.qml 2011-06-23 17:08:53 +0000
815+++ places/SearchEntry.qml 2011-07-27 19:03:19 +0000
816@@ -19,7 +19,7 @@
817 import QtQuick 1.0
818 import Effects 1.0
819
820-FocusScope {
821+AbstractButton {
822 property string searchQuery
823
824 /* Cancels current search when the dash becomes invisible */
825@@ -37,6 +37,8 @@
826 /* Keys forwarded to the search entry are forwarded to the text input. */
827 Keys.forwardTo: [search_input]
828
829+ opacity: state == "selected" ? 1.0 : 0.7
830+
831 BorderImage {
832 anchors.fill: parent
833 source: "artwork/search_background.sci"
834
835=== modified file 'places/SearchRefine.qml'
836--- places/SearchRefine.qml 2011-06-23 17:08:53 +0000
837+++ places/SearchRefine.qml 2011-07-27 19:03:19 +0000
838@@ -26,6 +26,9 @@
839 property int headerHeight
840 property variant placeEntryModel
841
842+ /* Give the focus to header when folded */
843+ onFoldedChanged: if (folded) header.focus = true
844+
845 AbstractButton {
846 id: header
847
848@@ -49,6 +52,9 @@
849
850 focus: true
851
852+ /* Do not navigate down to the options if they are folded */
853+ KeyNavigation.down: !searchRefine.folded ? options : header
854+
855 anchors.left: parent.left
856 anchors.right: parent.right
857 anchors.top: parent.top
858@@ -61,6 +67,7 @@
859 text: u2d.tr("Refine search")
860 font.bold: true
861 font.pixelSize: 16
862+ font.underline: parent.state == "selected"
863
864 anchors.top: parent.top
865 anchors.left: parent.left
866@@ -92,6 +99,8 @@
867 opacity: folded ? 0.0 : 1.0
868 Behavior on opacity {NumberAnimation {duration: 100; easing.type: Easing.InOutQuad}}
869
870+ KeyNavigation.up: header
871+
872 anchors.left: parent.left
873 anchors.right: parent.right
874 anchors.top: header.bottom
875
876=== modified file 'places/SearchRefineOptionType.qml'
877--- places/SearchRefineOptionType.qml 2011-07-26 17:05:50 +0000
878+++ places/SearchRefineOptionType.qml 2011-07-27 19:03:19 +0000
879@@ -21,9 +21,11 @@
880 SearchRefineOption {
881 id: searchRefineOption
882
883- AbstractButton {
884+ Item {
885 id: header
886
887+ KeyNavigation.down: filters
888+
889 focus: true
890 anchors.top: parent.top
891 anchors.left: parent.left
892@@ -39,6 +41,7 @@
893 text: searchRefineOption.title
894 font.pixelSize: 16
895 font.bold: true
896+ font.underline: parent.state == "selected"
897 }
898 }
899
900@@ -63,6 +66,7 @@
901
902 /* Make sure the first item is selected when getting the focus for the first time */
903 currentIndex: 0
904+ KeyNavigation.up: header
905
906 delegate: TickBox {
907 height: filters.cellHeight
908
909=== modified file 'places/UnityEmptySearchRenderer.qml'
910--- places/UnityEmptySearchRenderer.qml 2011-06-23 17:08:53 +0000
911+++ places/UnityEmptySearchRenderer.qml 2011-07-27 19:03:19 +0000
912@@ -40,7 +40,7 @@
913 boundsBehavior: ListView.StopAtBounds
914 orientation: ListView.Vertical
915
916- model: renderer.model
917+ model: renderer.group_model
918 delegate: Button {
919 property string uri: column_0
920 property string iconHint: column_1
921
922=== modified file 'places/dash.qml'
923--- places/dash.qml 2011-07-05 19:01:18 +0000
924+++ places/dash.qml 2011-07-27 19:03:19 +0000
925@@ -30,7 +30,16 @@
926 value: (currentPage && currentPage.expanded != undefined) ? currentPage.expanded : true
927 }
928
929+ /* Unload the current page when closing the dash */
930+ Connections {
931+ target: dashView
932+ onActiveChanged: if (!dashView.active) pageLoader.source = ""
933+ }
934+
935 function activatePage(page) {
936+ /* Always give the focus to the search entry when switching pages */
937+ search_entry.focus = true
938+
939 if (page == currentPage) {
940 return
941 }
942@@ -40,12 +49,6 @@
943 }
944 currentPage = page
945 currentPage.visible = true
946- /* FIXME: For some reason currentPage gets the focus when it becomes
947- visible. Reset the focus to the search_bar instead.
948- It could be due to Qt bug QTBUG-13380:
949- "Listview gets focus when it becomes visible"
950- */
951- search_entry.focus = true
952 }
953
954 function activatePlaceEntry(fileName, groupName, section) {
955@@ -121,13 +124,19 @@
956 /* Unhandled keys will always be forwarded to the search bar. That way
957 the user can type and search from anywhere in the interface without
958 necessarily focusing the search bar first. */
959- Keys.forwardTo: [search_entry]
960+ /* FIXME: deactivated because it makes the user lose the focus very often */
961+ //Keys.forwardTo: [search_entry]
962
963
964 SearchEntry {
965 id: search_entry
966
967 focus: true
968+ /* FIXME: check on visible necessary; fixed in Qt Quick 1.1
969+ ref: http://bugreports.qt.nokia.com/browse/QTBUG-15862
970+ */
971+ KeyNavigation.right: refine_search.visible ? refine_search : search_entry
972+ KeyNavigation.down: pageLoader
973
974 anchors.top: parent.top
975 anchors.topMargin: 10
976@@ -142,6 +151,8 @@
977 SearchRefine {
978 id: refine_search
979
980+ KeyNavigation.left: search_entry
981+
982 /* SearchRefine is only to be displayed for places, not in the home page */
983 visible: dashView.activePlaceEntry != ""
984 placeEntryModel: visible && currentPage != undefined ? currentPage.model : undefined
985@@ -158,6 +169,12 @@
986 Loader {
987 id: pageLoader
988
989+ /* FIXME: check on visible necessary; fixed in Qt Quick 1.1
990+ ref: http://bugreports.qt.nokia.com/browse/QTBUG-15862
991+ */
992+ KeyNavigation.right: refine_search.visible && !refine_search.folded ? refine_search : pageLoader
993+ KeyNavigation.up: search_entry
994+
995 anchors.top: search_entry.bottom
996 anchors.topMargin: 2
997 anchors.bottom: parent.bottom
998@@ -165,6 +182,7 @@
999 anchors.leftMargin: 20
1000 anchors.right: !refine_search.visible || refine_search.folded ? parent.right : refine_search.left
1001 anchors.rightMargin: !refine_search.visible || refine_search.folded ? 0 : 15
1002+ onLoaded: item.focus = true
1003 }
1004 }
1005

Subscribers

People subscribed via source and target branches