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

Proposed by Florian Boucault
Status: Merged
Approved by: Florian Boucault
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 Needs Fixing
Review via email: mp+69480@code.launchpad.net

Description of the change

[dash] Implemented keyboard navigation.

To post a comment you must log in.
Revision history for this message
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)
Revision history for this message
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

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

640. By Florian Boucault

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

641. By Florian Boucault

Added FIXME/

642. By Florian Boucault

Merged colo:proper_grid

Revision history for this message
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
=== modified file 'places/CenteredGridView.qml'
--- places/CenteredGridView.qml 2011-06-23 17:08:53 +0000
+++ places/CenteredGridView.qml 2011-07-27 19:03:19 +0000
@@ -24,6 +24,32 @@
24 property int delegateWidth: 10024 property int delegateWidth: 100
25 property int delegateHeight: 10025 property int delegateHeight: 100
2626
27 /* Manually handle key presses so that when the 'interactive' property is
28 set to false the keyboard navigation still works.
29
30 Ref.: https://bugreports.qt.nokia.com/browse/QTBUG-17051
31 */
32 function selectChild(index) {
33 if (index < 0 || index >= count) return false
34 currentIndex = index
35 return true
36 }
37
38 Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
39 function handleKeyPress(key) {
40 switch (key) {
41 case Qt.Key_Right:
42 return selectChild(currentIndex+1)
43 case Qt.Key_Left:
44 return selectChild(currentIndex-1)
45 case Qt.Key_Up:
46 return selectChild(currentIndex-cellsPerRow)
47 case Qt.Key_Down:
48 return selectChild(currentIndex+cellsPerRow)
49 }
50 return false
51 }
52
27 /* Compute the number of cells per row and column so that:53 /* Compute the number of cells per row and column so that:
28 - the items are always centered horizontally within the grid (= same54 - the items are always centered horizontally within the grid (= same
29 margins on the right and left sides of the grid) when flow is set55 margins on the right and left sides of the grid) when flow is set
3056
=== modified file 'places/Home.qml'
--- places/Home.qml 2011-07-26 16:36:51 +0000
+++ places/Home.qml 2011-07-27 19:03:19 +0000
@@ -19,7 +19,7 @@
19import QtQuick 1.019import QtQuick 1.0
20import Unity2d 1.0 /* Necessary for SortFilterProxyModel and for the ImageProvider serving image://icons/theme_name/icon_name */20import Unity2d 1.0 /* Necessary for SortFilterProxyModel and for the ImageProvider serving image://icons/theme_name/icon_name */
2121
22Item {22FocusScope {
23 property variant model: PageModel {23 property variant model: PageModel {
24 /* model.entrySearchQuery is copied over to all place entries's globalSearchQuery property */24 /* model.entrySearchQuery is copied over to all place entries's globalSearchQuery property */
25 onEntrySearchQueryChanged: {25 onEntrySearchQueryChanged: {
@@ -96,26 +96,39 @@
96 ListViewWithScrollbar {96 ListViewWithScrollbar {
97 id: globalSearch97 id: globalSearch
9898
99 focus: globalSearchActive
99 opacity: globalSearchActive ? 1 : 0100 opacity: globalSearchActive ? 1 : 0
100 anchors.fill: parent101 anchors.fill: parent
101102
102 list.model: dash.places103 model: dash.places
103104
104 list.delegate: UnityDefaultRenderer {105 bodyDelegate: UnityDefaultRenderer {
105 width: ListView.view.width106 placeEntryModel: model.item
106107 displayName: model.item.name
107 parentListView: list108 iconHint: model.item.icon
108 placeEntryModel: item109
109 displayName: item.name110 group_model: model.item.globalResultsModel
110 iconHint: item.icon111 property bool focusable: group_model != undefined && group_model.count > 0
111112 }
112 model: item.globalResultsModel113
114 headerDelegate: GroupHeader {
115 visible: body.needHeader && body.focusable
116 height: visible ? 32 : 0
117
118 property bool foldable: body.folded != undefined
119 availableCount: foldable && body.group_model != null ? body.group_model.count - body.cellsPerRow : 0
120 folded: foldable ? body.folded : false
121 onClicked: if(foldable) body.folded = !body.folded
122
123 icon: body.iconHint
124 label: body.displayName
113 }125 }
114 }126 }
115127
116 Rectangle {128 FocusScope {
117 id: shortcuts129 id: shortcuts
118130
131 focus: !globalSearchActive
119 opacity: (!globalSearchActive && (shortcutsActive || dashView.dashMode == DashDeclarativeView.FullScreenMode)) ? 1 : 0132 opacity: (!globalSearchActive && (shortcutsActive || dashView.dashMode == DashDeclarativeView.FullScreenMode)) ? 1 : 0
120 anchors.horizontalCenter: parent.horizontalCenter133 anchors.horizontalCenter: parent.horizontalCenter
121 anchors.verticalCenter: parent.verticalCenter134 anchors.verticalCenter: parent.verticalCenter
@@ -123,11 +136,14 @@
123 width: 888136 width: 888
124 height: 466137 height: 466
125138
126 radius: 5139 Rectangle {
127 border.width: 1140 anchors.fill: parent
128 /* FIXME: wrong colors */141 radius: 5
129 border.color: Qt.rgba(1, 1, 1, 0.2)142 border.width: 1
130 color: Qt.rgba(0, 0, 0, 0.3)143 /* FIXME: wrong colors */
144 border.color: Qt.rgba(1, 1, 1, 0.2)
145 color: Qt.rgba(0, 0, 0, 0.3)
146 }
131147
132 Button {148 Button {
133 id: closeShortcutsButton149 id: closeShortcutsButton
@@ -158,11 +174,13 @@
158 on the default version if a custom one doesn’t exist. */174 on the default version if a custom one doesn’t exist. */
159 Loader {175 Loader {
160 id: customShortcutsLoader176 id: customShortcutsLoader
177 focus: status == Loader.Ready
161 anchors.fill: parent178 anchors.fill: parent
162 source: "HomeShortcutsCustomized.qml"179 source: "HomeShortcutsCustomized.qml"
163 }180 }
164 Loader {181 Loader {
165 id: defaultShortcutsLoader182 id: defaultShortcutsLoader
183 focus: !customShortcutsLoader.focus
166 anchors.fill: parent184 anchors.fill: parent
167 source: (customShortcutsLoader.status == Loader.Error) ? "HomeShortcuts.qml" : ""185 source: (customShortcutsLoader.status == Loader.Error) ? "HomeShortcuts.qml" : ""
168 }186 }
169187
=== modified file 'places/HomeShortcuts.qml'
--- places/HomeShortcuts.qml 2011-07-04 23:07:20 +0000
+++ places/HomeShortcuts.qml 2011-07-27 19:03:19 +0000
@@ -19,16 +19,41 @@
19import QtQuick 1.019import QtQuick 1.0
20import Unity2d 1.0 /* Necessary for the ImageProvider serving image://icons/theme_name/icon_name */20import Unity2d 1.0 /* Necessary for the ImageProvider serving image://icons/theme_name/icon_name */
2121
22Flow {22Grid {
23 anchors.fill: parent23 anchors.fill: parent
24 anchors.topMargin: 2624 anchors.topMargin: 26
25 anchors.bottomMargin: 3525 anchors.bottomMargin: 35
26 anchors.leftMargin: 3226 anchors.leftMargin: 32
27 anchors.rightMargin: 3227 anchors.rightMargin: 32
28 spacing: 6128 spacing: 61
29 columns: 4
30 rows: 2
31
32 function selectChild(index) {
33 if (index < 0 || index >= children.length) return false
34 currentIndex = index
35 children[index].focus = true
36 return true
37 }
38
39 property int currentIndex: 0
40 Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
41 function handleKeyPress(key) {
42 switch (key) {
43 case Qt.Key_Right:
44 return selectChild(currentIndex+1)
45 case Qt.Key_Left:
46 return selectChild(currentIndex-1)
47 case Qt.Key_Up:
48 return selectChild(currentIndex-columns)
49 case Qt.Key_Down:
50 return selectChild(currentIndex+columns)
51 }
52 }
2953
30 /* FIXME: dummy icons need to be replaced by design's */54 /* FIXME: dummy icons need to be replaced by design's */
31 HomeButton {55 HomeButton {
56 focus: true
32 label: u2d.tr("Media Apps")57 label: u2d.tr("Media Apps")
33 icon: "artwork/find_media_apps.png"58 icon: "artwork/find_media_apps.png"
34 onClicked: activatePlaceEntry("/usr/share/unity/places/applications.place", "Files", 9)59 onClicked: activatePlaceEntry("/usr/share/unity/places/applications.place", "Files", 9)
3560
=== added file 'places/ListViewWithHeaders.qml'
--- places/ListViewWithHeaders.qml 1970-01-01 00:00:00 +0000
+++ places/ListViewWithHeaders.qml 2011-07-27 19:03:19 +0000
@@ -0,0 +1,277 @@
1/*
2 * This file is part of unity-2d
3 *
4 * Copyright 2010-2011 Canonical Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 1.0
20
21/*
22 List item that behaves similarly to a ListView but supports adding headers
23 before every delegate.
24 It works around the lack of flexibility in section headers positioning of
25 ListView (cf. http://bugreports.qt.nokia.com/browse/QTBUG-12880). It also
26 supports delegates that are flickable by flicking their content properly
27 depending on where you are in the list.
28
29 To use it the following properties need to be set:
30 - bodyDelegate: Component used as a template for each item of the model; it
31 must have the following properties:
32 - 'contentY': same definition as a Flickable's
33 - 'totalHeight': the total height of the content of the body
34 - 'currentItem': a reference to the item of the body (e.g. a delegate of
35 a ListView) currently focused by the body
36 - headerDelegate: Component used as a template for the header preceding each body
37
38 Two behaviours are available for the headers positioning:
39 - normal: the headers are always positioned just before the body
40 - accordion: the headers are stacked at the top and bottom of the list
41
42 Currently, it only works in a vertical layout.
43*/
44FocusScope {
45 id: list
46
47 property alias flickable: mouse
48 property alias model: repeater.model
49 property bool accordion: false
50 /* bodyDelegate must be an item that has the following properties:
51 - 'contentY'
52 - 'totalHeight'
53 - 'currentItem'
54 */
55 property Component bodyDelegate
56 property Component headerDelegate
57 property int currentIndex: -1
58 /* FIXME: Should be read-only but it is not possible to define a read-only property from QML.
59 Ref.: https://bugreports.qt.nokia.com//browse/QTBUG-15257
60 */
61 property variant currentItem: items.childFromIndex(currentIndex)
62
63
64 clip: true
65
66 /* updateMouseContentY() needs to be called when any of the variable
67 involved in the computation of the 'y' property of the currentSubItem changes.
68
69 if accordion is false:
70 - heightFirstChildren(index)
71 - headerLoader.height
72 - list.height
73
74 if accordion is true:
75 - heightFirstChildren(index)
76 - items.availableHeight
77 - heightFirstHeaders(index)
78
79 items.contentHeight seems to depend on all of those so it's enough to
80 depend only on it.
81 */
82 function updateMouseContentY() {
83 if (currentSubItem != undefined) {
84 mouse.contentY = Math.max(currentSubItem.mapToItem(mouse.contentItem, 0, 0).y -list.height/2, 0)
85 }
86 }
87 property variant currentSubItem: currentItem != undefined ? currentItem.bodyLoader.item.currentItem : undefined
88 onCurrentSubItemChanged: updateMouseContentY()
89
90 Connections {
91 target: items
92 onContentHeightChanged: updateMouseContentY()
93 }
94
95 FocusScope {
96 id: items
97
98 property int availableHeight: list.height - heightFirstHeaders(repeater.count)
99 property int contentHeight: items.heightFirstChildren(repeater.count)
100 property real value: mouse.contentY
101
102 anchors.fill: parent
103
104 function heightFirstChildren(n) {
105 var i
106 var totalHeight = 0
107 /* items.children contains both the repeated items and the repeater
108 itself. Skip and ignore the repeater. */
109 for (i=0; i<n && i<children.length; i++) {
110 if(children[i] == repeater) {n += 1; continue}
111 totalHeight += children[i].height
112 }
113 return totalHeight
114 }
115
116 function heightFirstHeaders(n) {
117 var i
118 var totalHeight = 0
119 /* items.children contains both the repeated items and the repeater
120 itself. Skip and ignore the repeater. */
121 for (i=0; i<n && i<children.length; i++) {
122 if(children[i] == repeater) {n += 1; continue}
123 totalHeight += children[i].headerLoader.height
124 }
125 return totalHeight
126 }
127
128 function clamp(x, min, max) {
129 return Math.max(Math.min(x, max), min)
130 }
131
132
133 /* Keyboard navigation */
134 function isIndexValid(index) {
135 /* Assuming that children contains exactly one item that is not a child (repeater) */
136 return index >= 0 && index < children.length-1
137 }
138
139 focus: true
140 Keys.onPressed: if (handleKeyPress(event.key)) event.accepted = true
141 function handleKeyPress(key) {
142 switch (key) {
143 case Qt.Key_Down:
144 return selectNextEnabled()
145 case Qt.Key_Up:
146 return selectPreviousEnabled()
147 }
148 }
149
150 function childFromIndex(index) {
151 var indexInChildren = 0
152 for(var i=0; i<children.length; i++) {
153 if (children[i] != repeater) {
154 if (indexInChildren == index) return children[i]
155 indexInChildren++
156 }
157 }
158
159 return undefined
160 }
161
162 function selectNextEnabled() {
163 var index = currentIndex
164 do {
165 index += 1
166 if (!isIndexValid(index)) return false
167 } while(!childFromIndex(index).focusable)
168 currentIndex = index
169 return true
170 }
171
172 function selectPreviousEnabled() {
173 var index = currentIndex
174 do {
175 index -= 1
176 if (!isIndexValid(index)) return false
177 } while(!childFromIndex(index).focusable)
178 currentIndex = index
179 return true
180 }
181
182 property bool needsFocus: false
183 onChildrenChanged: {
184 /* FIXME: this workarounds the fact that list.focus is set to false
185 when the child with focus is destroyed
186 */
187 if (needsFocus) {
188 list.focus = true
189 needsFocus = false
190 }
191 /* Assuming that children contains exactly one item that is not a child (repeater) */
192 if(children.length <= 1) {
193 list.currentIndex = -1
194 } else {
195 list.currentIndex = -1
196 selectNextEnabled()
197 }
198 }
199
200 Repeater {
201 id: repeater
202
203 FocusScope {
204 property alias bodyLoader: bodyLoader
205 property alias headerLoader: headerLoader
206
207 focus: index == list.currentIndex
208 Component.onDestruction: items.needsFocus = true
209
210 width: list.width
211 height: headerLoader.height + bodyLoader.item.totalHeight
212 property bool focusable: bodyLoader.item.focusable
213
214 property int pmin: pmax - (ymax - ymin)
215 property int pmax: items.heightFirstChildren(index) - ymin
216 property int ymin: list.accordion ? items.heightFirstHeaders(index) : -headerLoader.height
217 property int ymax: list.accordion ? ymin + items.availableHeight : list.height
218 y: items.clamp(-items.value + ymax + pmin, ymin, ymax)
219
220 Loader {
221 id: headerLoader
222
223 focus: visible
224 KeyNavigation.down: bodyLoader
225 sourceComponent: headerDelegate
226 onLoaded: item.focus = true
227 width: parent.width
228
229 /* Workaround Qt bug http://bugreports.qt.nokia.com/browse/QTBUG-18857
230 More documentation at http://bugreports.qt.nokia.com/browse/QTBUG-18011
231 */
232 property int index
233 Binding { target: headerLoader; property: "index"; value: index }
234 property variant model
235 Binding { target: headerLoader; property: "model"; value: model }
236 property variant body
237 Binding { target: headerLoader; property: "body"; value: bodyLoader.item }
238 }
239
240 Loader {
241 id: bodyLoader
242
243 focus: !headerLoader.focus
244 KeyNavigation.up: headerLoader
245 sourceComponent: list.bodyDelegate
246 onLoaded: item.focus = true
247 width: parent.width
248 anchors.top: headerLoader.bottom
249 height: items.clamp(parent.ymax - parent.y, 0, item.totalHeight)
250
251 Binding {
252 target: bodyLoader.item
253 property: "contentY"
254 value: Math.max(items.value - pmax, 0)
255 }
256
257 /* Workaround Qt bug http://bugreports.qt.nokia.com/browse/QTBUG-18857
258 More documentation at http://bugreports.qt.nokia.com/browse/QTBUG-18011
259 */
260 property int index
261 Binding { target: bodyLoader; property: "index"; value: index }
262 property variant model
263 Binding { target: bodyLoader; property: "model"; value: model }
264 }
265 }
266 }
267 }
268
269 Flickable {
270 id: mouse
271
272 z: -1
273 anchors.fill: parent
274 contentWidth: parent.width
275 contentHeight: items.contentHeight
276 }
277}
0278
=== modified file 'places/ListViewWithScrollbar.qml'
--- places/ListViewWithScrollbar.qml 2011-06-23 17:08:53 +0000
+++ places/ListViewWithScrollbar.qml 2011-07-27 19:03:19 +0000
@@ -18,44 +18,21 @@
1818
19import QtQuick 1.019import QtQuick 1.0
2020
21Item {21FocusScope {
22 property alias list: list
23 property alias scrollbar: scrollbar22 property alias scrollbar: scrollbar
23 property alias model: list.model
24 property alias bodyDelegate: list.bodyDelegate
25 property alias headerDelegate: list.headerDelegate
2426
25 ListView {27 ListViewWithHeaders {
26 id: list28 id: list
2729
30 focus: true
28 anchors.top: parent.top31 anchors.top: parent.top
29 anchors.bottom: parent.bottom32 anchors.bottom: parent.bottom
30 anchors.left: parent.left33 anchors.left: parent.left
31 anchors.right: scrollbar.left34 anchors.right: scrollbar.left
32 anchors.rightMargin: 1535 anchors.rightMargin: 15
33
34 clip: true
35 /* FIXME: proper spacing cannot be set because of the hack in Group.qml
36 whereby empty groups are still in the list but invisible and of
37 height 0.
38 */
39 //spacing: 31
40
41 orientation: ListView.Vertical
42
43 /* WARNING - HACK - FIXME
44 Issue:
45 User wise annoying jumps in the list are observable if cacheBuffer is
46 set to 0 (which is the default value). States such as 'folded' are
47 lost when scrolling a lot.
48
49 Explanation:
50 The height of the Group delegate depends on its content. However its
51 content is not known until the delegate is instantiated because it
52 depends on the number of results displayed by its GridView.
53
54 Resolution:
55 We set the cacheBuffer to the biggest possible int in order to make
56 sure all delegates are always instantiated.
57 */
58 cacheBuffer: 2147483647
59 }36 }
6037
61 Scrollbar {38 Scrollbar {
@@ -67,7 +44,7 @@
67 anchors.bottomMargin: 1044 anchors.bottomMargin: 10
68 anchors.right: parent.right45 anchors.right: parent.right
6946
70 targetFlickable: list47 targetFlickable: list.flickable
7148
72 /* Hide the scrollbar if there is less than a page of results */49 /* Hide the scrollbar if there is less than a page of results */
73 opacity: targetFlickable.visibleArea.heightRatio < 1.0 ? 1.0 : 0.050 opacity: targetFlickable.visibleArea.heightRatio < 1.0 ? 1.0 : 0.0
7451
=== modified file 'places/PlaceEntryView.qml'
--- places/PlaceEntryView.qml 2011-07-19 07:46:03 +0000
+++ places/PlaceEntryView.qml 2011-07-27 19:03:19 +0000
@@ -19,7 +19,7 @@
19import QtQuick 1.019import QtQuick 1.0
20import Unity2d 1.0 /* Necessary for SortFilterProxyModel */20import Unity2d 1.0 /* Necessary for SortFilterProxyModel */
2121
22Item {22FocusScope {
23 id: placeEntryView23 id: placeEntryView
2424
25 /* An instance of PlaceEntryModel */25 /* An instance of PlaceEntryModel */
@@ -33,7 +33,7 @@
33 var placeEntry, i33 var placeEntry, i
34 for (i=0; i<placeEntryView.model.entryGroupsModel.count; i=i+1) {34 for (i=0; i<placeEntryView.model.entryGroupsModel.count; i=i+1) {
35 firstGroupModel.groupId = i35 firstGroupModel.groupId = i
36 if (firstGroupModel.count() != 0) {36 if (firstGroupModel.count != 0) {
37 var firstResult = firstGroupModel.get(0)37 var firstResult = firstGroupModel.get(0)
38 /* Places give back the uri of the item in 'column_0' per specification */38 /* Places give back the uri of the item in 'column_0' per specification */
39 var uri = firstResult.column_039 var uri = firstResult.column_0
@@ -62,6 +62,7 @@
62 ListViewWithScrollbar {62 ListViewWithScrollbar {
63 id: results63 id: results
6464
65 focus: true
65 anchors.fill: parent66 anchors.fill: parent
6667
67 /* The group's delegate is chosen dynamically depending on what68 /* The group's delegate is chosen dynamically depending on what
@@ -74,10 +75,10 @@
74 If groupRenderer == 'UnityShowcaseRenderer' then it will look for75 If groupRenderer == 'UnityShowcaseRenderer' then it will look for
75 the file 'UnityShowcaseRenderer.qml' and use it to render the group.76 the file 'UnityShowcaseRenderer.qml' and use it to render the group.
76 */77 */
77 list.delegate: Loader {78 bodyDelegate: Loader {
78 property string groupRenderer: column_079 property string groupRenderer: model.column_0
79 property string displayName: column_180 property string displayName: model.column_1
80 property string iconHint: column_281 property string iconHint: model.column_2
81 property int groupId: index82 property int groupId: index
8283
83 source: groupRenderer ? groupRenderer+".qml" : ""84 source: groupRenderer ? groupRenderer+".qml" : ""
@@ -86,12 +87,8 @@
86 console.log("Failed to load renderer", groupRenderer)87 console.log("Failed to load renderer", groupRenderer)
87 }88 }
8889
89 width: ListView.view.width
90
91 /* Model that will be used by the group's delegate */90 /* Model that will be used by the group's delegate */
92 SortFilterProxyModel {91 property variant group_model: SortFilterProxyModel {
93 id: group_model
94
95 model: placeEntryView.model.entryResultsModel92 model: placeEntryView.model.entryResultsModel
9693
97 /* resultsModel contains data for all the groups of a given Place.94 /* resultsModel contains data for all the groups of a given Place.
@@ -102,16 +99,37 @@
102 filterRegExp: RegExp("^%1$".arg(groupId)) /* exact match */99 filterRegExp: RegExp("^%1$".arg(groupId)) /* exact match */
103 }100 }
104101
105 onLoaded: {102 /* Required by ListViewWithHeaders when the loaded Renderer is a Flickable.
106 item.parentListView = results.list103 In that case the list view scrolls the Flickable appropriately.
107 item.displayName = displayName104 */
108 item.iconHint = iconHint105 property int totalHeight: item.totalHeight != undefined ? item.totalHeight : 0
109 item.groupId = groupId106 property int contentY
110 item.model = group_model107 Binding { target: item; property: "contentY"; value: contentY }
111 item.placeEntryModel = placeEntryView.model108 property bool focusable: group_model.count > 0
112 }109 property variant currentItem: item.currentItem
113 }110
114111 Binding { target: item; property: "displayName"; value: displayName }
115 list.model: placeEntryView.model != undefined ? placeEntryView.model.entryGroupsModel : undefined112 Binding { target: item; property: "iconHint"; value: iconHint }
113 Binding { target: item; property: "groupId"; value: groupId }
114 Binding { target: item; property: "group_model"; value: group_model }
115 Binding { target: item; property: "placeEntryModel"; value: placeEntryView.model }
116
117 onLoaded: item.focus = true
118 }
119
120 headerDelegate: GroupHeader {
121 visible: body.item.needHeader && body.focusable
122 height: visible ? 32 : 0
123
124 property bool foldable: body.item.folded != undefined
125 availableCount: foldable ? body.group_model.count - body.item.cellsPerRow : 0
126 folded: foldable ? body.item.folded : false
127 onClicked: if(foldable) body.item.folded = !body.item.folded
128
129 icon: body.iconHint
130 label: body.displayName
131 }
132
133 model: placeEntryView.model != undefined ? placeEntryView.model.entryGroupsModel : undefined
116 }134 }
117}135}
118136
=== modified file 'places/Renderer.qml'
--- places/Renderer.qml 2011-06-23 17:08:53 +0000
+++ places/Renderer.qml 2011-07-27 19:03:19 +0000
@@ -26,11 +26,11 @@
26 itself. A typical renderer is the UnityDefaultRender that lays out the items26 itself. A typical renderer is the UnityDefaultRender that lays out the items
27 in a grid of icons with the item's title underneath it.27 in a grid of icons with the item's title underneath it.
28*/28*/
29Item {29FocusScope {
30 property string displayName /* Name of the group typically displayed in the header */30 property string displayName /* Name of the group typically displayed in the header */
31 property string iconHint /* Icon id of the group */31 property string iconHint /* Icon id of the group */
32 property int groupId /* Index of the group */32 property int groupId /* Index of the group */
33 property variant model /* List model containing the items to be displayed by the renderer */33 property variant group_model /* List model containing the items to be displayed by the renderer */
34 property variant placeEntryModel /* Reference to the place entry the group belongs to */34 property variant placeEntryModel /* Reference to the place entry the group belongs to */
35 property variant parentListView /* Reference to the ListView the renderer is nested into */35 property bool needHeader: false /* Whether or not the renderer requires a header to be displayed */
36}36}
3737
=== modified file 'places/RendererGrid.qml'
--- places/RendererGrid.qml 2011-06-26 15:41:06 +0000
+++ places/RendererGrid.qml 2011-07-27 19:03:19 +0000
@@ -28,6 +28,11 @@
28Renderer {28Renderer {
29 id: renderer29 id: renderer
3030
31 needHeader: true
32 property alias cellsPerRow: results.cellsPerRow
33 property alias contentY: results.contentY
34 property alias currentItem: results.currentItem
35
31 property variant cellRenderer36 property variant cellRenderer
32 property bool folded37 property bool folded
33 folded: {38 folded: {
@@ -44,85 +49,23 @@
44 property int horizontalSpacing: 2649 property int horizontalSpacing: 26
45 property int verticalSpacing: 2650 property int verticalSpacing: 26
4651
47 /* Using results.contentHeight produces binding loop warnings and potential52 /* FIXME: using results_layout.anchors.topMargin in the following expression
48 rendering issues. We compute the height manually.53 causes QML to think they might be an anchor loop. */
49 */54 property int totalHeight: results.count > 0 ? results_layout.anchors.topMargin + results.totalHeight : 0
50 /* FIXME: tricking the system by making the delegate of height 0 and invisible
51 is no good: the item in the model still exists and some things
52 such as keyboard selection break.
53 */
54 visible: results.model.totalCount > 0
55 height: visible ? header.height + results_layout.anchors.topMargin + results.totalHeight : 0
56 //Behavior on height {NumberAnimation {duration: 200}}
57
58 GroupHeader {
59 id: header
60
61 availableCount: results.model.totalCount - results.cellsPerRow
62 folded: parent.folded
63 anchors.top: parent.top
64 anchors.left: parent.left
65 anchors.right: parent.right
66 height: 32
67 icon: parent.iconHint
68 label: parent.displayName
69
70 onClicked: parent.folded = !parent.folded
71 }
7255
73 Item {56 Item {
74 id: results_layout57 id: results_layout
7558
76 anchors.top: header.bottom59 anchors.fill: parent
77 anchors.topMargin: 2260 anchors.topMargin: 12
78 anchors.left: parent.left
79 anchors.leftMargin: 261 anchors.leftMargin: 2
80 anchors.right: parent.right
81 anchors.bottom: parent.bottom
8262
83 CenteredGridView {63 CenteredGridView {
84 id: results64 id: results
8565
86 /* FIXME: this is a gross hack compensating for the lack of sections66 focus: true
87 in GridView (see ListView.section).67
8868 anchors.fill: parent
89 We nest GridViews inside a ListView and add headers manually
90 (GroupHeader). The total height of each Group is computed
91 manually and given back to the ListView. However that size cannot
92 be used by the individual GridViews because it would make them
93 load all of their delegates at once using far too much memory and
94 processing power. Instead we constrain the height of the GridViews
95 and compute their position manually to compensate for the position
96 changes when flicking the ListView.
97
98 We assume that renderer.parentListView is the ListView we nest our
99 GridView into.
100 */
101 property variant flickable: renderer.parentListView.contentItem
102
103 /* flickable.contentY*0 is equal to 0 but is necessary in order to
104 have the entire expression being evaluated at the right moment.
105 */
106 property int inFlickableY: flickable.contentY*0+parent.mapToItem(flickable, 0, 0).y
107 /* note: testing for flickable.height < 0 is probably useless since it is
108 unlikely flickable.height will ever be negative.
109 */
110 property int compensateY: inFlickableY > 0 || flickable.height < 0 || totalHeight < flickable.height ? 0 : -inFlickableY
111
112 /* Synchronise the position and content's position of the GridView
113 with the current position of flickable's visibleArea */
114 function synchronisePosition() {
115 y = compensateY
116 contentY = compensateY
117 }
118
119 onCompensateYChanged: synchronisePosition()
120 /* Any change in content needs to trigger a synchronisation */
121 onCountChanged: synchronisePosition()
122 onModelChanged: synchronisePosition()
123
124 width: flickable.width
125 height: Math.min(totalHeight, flickable.height)
12669
127 property int totalHeight: results.cellHeight*Math.ceil(count/cellsPerRow)70 property int totalHeight: results.cellHeight*Math.ceil(count/cellsPerRow)
12871
@@ -134,7 +77,7 @@
134 interactive: false77 interactive: false
135 clip: true78 clip: true
13679
137 delegate: Item {80 delegate: FocusScope {
13881
139 width: results.cellWidth82 width: results.cellWidth
140 height: results.cellHeight83 height: results.cellHeight
@@ -155,6 +98,7 @@
155 height: results.delegateHeight98 height: results.delegateHeight
156 anchors.horizontalCenter: parent.horizontalCenter99 anchors.horizontalCenter: parent.horizontalCenter
157100
101 focus: true
158 sourceComponent: cellRenderer102 sourceComponent: cellRenderer
159 onLoaded: {103 onLoaded: {
160 item.uri = uri104 item.uri = uri
@@ -162,13 +106,14 @@
162 item.mimetype = mimetype106 item.mimetype = mimetype
163 item.displayName = displayName107 item.displayName = displayName
164 item.comment = comment108 item.comment = comment
109 item.focus = true
165 }110 }
166 }111 }
167 }112 }
168113
169 /* Only display one line of items when folded */114 /* Only display one line of items when folded */
170 model: SortFilterProxyModel {115 model: SortFilterProxyModel {
171 model: renderer.model != undefined ? renderer.model : null116 model: renderer.group_model != undefined ? renderer.group_model : null
172 limit: folded ? results.cellsPerRow : -1117 limit: folded ? results.cellsPerRow : -1
173 }118 }
174 }119 }
175120
=== modified file 'places/Scrollbar.qml'
--- places/Scrollbar.qml 2011-06-26 15:16:43 +0000
+++ places/Scrollbar.qml 2011-07-27 19:03:19 +0000
@@ -83,7 +83,7 @@
83 when: !dragMouseArea.drag.active83 when: !dragMouseArea.drag.active
84 }84 }
8585
86 height: Math.max(minimalHeight, targetFlickable.visibleArea.heightRatio * scrollbar.height)86 height: Math.min(scrollbar.height, Math.max(minimalHeight, targetFlickable.visibleArea.heightRatio * scrollbar.height))
8787
88 Behavior on height {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}88 Behavior on height {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
8989
9090
=== modified file 'places/SearchEntry.qml'
--- places/SearchEntry.qml 2011-06-23 17:08:53 +0000
+++ places/SearchEntry.qml 2011-07-27 19:03:19 +0000
@@ -19,7 +19,7 @@
19import QtQuick 1.019import QtQuick 1.0
20import Effects 1.020import Effects 1.0
2121
22FocusScope {22AbstractButton {
23 property string searchQuery23 property string searchQuery
2424
25 /* Cancels current search when the dash becomes invisible */25 /* Cancels current search when the dash becomes invisible */
@@ -37,6 +37,8 @@
37 /* Keys forwarded to the search entry are forwarded to the text input. */37 /* Keys forwarded to the search entry are forwarded to the text input. */
38 Keys.forwardTo: [search_input]38 Keys.forwardTo: [search_input]
3939
40 opacity: state == "selected" ? 1.0 : 0.7
41
40 BorderImage {42 BorderImage {
41 anchors.fill: parent43 anchors.fill: parent
42 source: "artwork/search_background.sci"44 source: "artwork/search_background.sci"
4345
=== modified file 'places/SearchRefine.qml'
--- places/SearchRefine.qml 2011-06-23 17:08:53 +0000
+++ places/SearchRefine.qml 2011-07-27 19:03:19 +0000
@@ -26,6 +26,9 @@
26 property int headerHeight26 property int headerHeight
27 property variant placeEntryModel27 property variant placeEntryModel
2828
29 /* Give the focus to header when folded */
30 onFoldedChanged: if (folded) header.focus = true
31
29 AbstractButton {32 AbstractButton {
30 id: header33 id: header
3134
@@ -49,6 +52,9 @@
4952
50 focus: true53 focus: true
5154
55 /* Do not navigate down to the options if they are folded */
56 KeyNavigation.down: !searchRefine.folded ? options : header
57
52 anchors.left: parent.left58 anchors.left: parent.left
53 anchors.right: parent.right59 anchors.right: parent.right
54 anchors.top: parent.top60 anchors.top: parent.top
@@ -61,6 +67,7 @@
61 text: u2d.tr("Refine search")67 text: u2d.tr("Refine search")
62 font.bold: true68 font.bold: true
63 font.pixelSize: 1669 font.pixelSize: 16
70 font.underline: parent.state == "selected"
6471
65 anchors.top: parent.top72 anchors.top: parent.top
66 anchors.left: parent.left73 anchors.left: parent.left
@@ -92,6 +99,8 @@
92 opacity: folded ? 0.0 : 1.099 opacity: folded ? 0.0 : 1.0
93 Behavior on opacity {NumberAnimation {duration: 100; easing.type: Easing.InOutQuad}}100 Behavior on opacity {NumberAnimation {duration: 100; easing.type: Easing.InOutQuad}}
94101
102 KeyNavigation.up: header
103
95 anchors.left: parent.left104 anchors.left: parent.left
96 anchors.right: parent.right105 anchors.right: parent.right
97 anchors.top: header.bottom106 anchors.top: header.bottom
98107
=== modified file 'places/SearchRefineOptionType.qml'
--- places/SearchRefineOptionType.qml 2011-07-26 17:05:50 +0000
+++ places/SearchRefineOptionType.qml 2011-07-27 19:03:19 +0000
@@ -21,9 +21,11 @@
21SearchRefineOption {21SearchRefineOption {
22 id: searchRefineOption22 id: searchRefineOption
2323
24 AbstractButton {24 Item {
25 id: header25 id: header
2626
27 KeyNavigation.down: filters
28
27 focus: true29 focus: true
28 anchors.top: parent.top30 anchors.top: parent.top
29 anchors.left: parent.left31 anchors.left: parent.left
@@ -39,6 +41,7 @@
39 text: searchRefineOption.title41 text: searchRefineOption.title
40 font.pixelSize: 1642 font.pixelSize: 16
41 font.bold: true43 font.bold: true
44 font.underline: parent.state == "selected"
42 }45 }
43 }46 }
4447
@@ -63,6 +66,7 @@
6366
64 /* Make sure the first item is selected when getting the focus for the first time */67 /* Make sure the first item is selected when getting the focus for the first time */
65 currentIndex: 068 currentIndex: 0
69 KeyNavigation.up: header
6670
67 delegate: TickBox {71 delegate: TickBox {
68 height: filters.cellHeight72 height: filters.cellHeight
6973
=== modified file 'places/UnityEmptySearchRenderer.qml'
--- places/UnityEmptySearchRenderer.qml 2011-06-23 17:08:53 +0000
+++ places/UnityEmptySearchRenderer.qml 2011-07-27 19:03:19 +0000
@@ -40,7 +40,7 @@
40 boundsBehavior: ListView.StopAtBounds40 boundsBehavior: ListView.StopAtBounds
41 orientation: ListView.Vertical41 orientation: ListView.Vertical
4242
43 model: renderer.model43 model: renderer.group_model
44 delegate: Button {44 delegate: Button {
45 property string uri: column_045 property string uri: column_0
46 property string iconHint: column_146 property string iconHint: column_1
4747
=== modified file 'places/dash.qml'
--- places/dash.qml 2011-07-05 19:01:18 +0000
+++ places/dash.qml 2011-07-27 19:03:19 +0000
@@ -30,7 +30,16 @@
30 value: (currentPage && currentPage.expanded != undefined) ? currentPage.expanded : true30 value: (currentPage && currentPage.expanded != undefined) ? currentPage.expanded : true
31 }31 }
3232
33 /* Unload the current page when closing the dash */
34 Connections {
35 target: dashView
36 onActiveChanged: if (!dashView.active) pageLoader.source = ""
37 }
38
33 function activatePage(page) {39 function activatePage(page) {
40 /* Always give the focus to the search entry when switching pages */
41 search_entry.focus = true
42
34 if (page == currentPage) {43 if (page == currentPage) {
35 return44 return
36 }45 }
@@ -40,12 +49,6 @@
40 }49 }
41 currentPage = page50 currentPage = page
42 currentPage.visible = true51 currentPage.visible = true
43 /* FIXME: For some reason currentPage gets the focus when it becomes
44 visible. Reset the focus to the search_bar instead.
45 It could be due to Qt bug QTBUG-13380:
46 "Listview gets focus when it becomes visible"
47 */
48 search_entry.focus = true
49 }52 }
5053
51 function activatePlaceEntry(fileName, groupName, section) {54 function activatePlaceEntry(fileName, groupName, section) {
@@ -121,13 +124,19 @@
121 /* Unhandled keys will always be forwarded to the search bar. That way124 /* Unhandled keys will always be forwarded to the search bar. That way
122 the user can type and search from anywhere in the interface without125 the user can type and search from anywhere in the interface without
123 necessarily focusing the search bar first. */126 necessarily focusing the search bar first. */
124 Keys.forwardTo: [search_entry]127 /* FIXME: deactivated because it makes the user lose the focus very often */
128 //Keys.forwardTo: [search_entry]
125129
126130
127 SearchEntry {131 SearchEntry {
128 id: search_entry132 id: search_entry
129133
130 focus: true134 focus: true
135 /* FIXME: check on visible necessary; fixed in Qt Quick 1.1
136 ref: http://bugreports.qt.nokia.com/browse/QTBUG-15862
137 */
138 KeyNavigation.right: refine_search.visible ? refine_search : search_entry
139 KeyNavigation.down: pageLoader
131140
132 anchors.top: parent.top141 anchors.top: parent.top
133 anchors.topMargin: 10142 anchors.topMargin: 10
@@ -142,6 +151,8 @@
142 SearchRefine {151 SearchRefine {
143 id: refine_search152 id: refine_search
144153
154 KeyNavigation.left: search_entry
155
145 /* SearchRefine is only to be displayed for places, not in the home page */156 /* SearchRefine is only to be displayed for places, not in the home page */
146 visible: dashView.activePlaceEntry != ""157 visible: dashView.activePlaceEntry != ""
147 placeEntryModel: visible && currentPage != undefined ? currentPage.model : undefined158 placeEntryModel: visible && currentPage != undefined ? currentPage.model : undefined
@@ -158,6 +169,12 @@
158 Loader {169 Loader {
159 id: pageLoader170 id: pageLoader
160171
172 /* FIXME: check on visible necessary; fixed in Qt Quick 1.1
173 ref: http://bugreports.qt.nokia.com/browse/QTBUG-15862
174 */
175 KeyNavigation.right: refine_search.visible && !refine_search.folded ? refine_search : pageLoader
176 KeyNavigation.up: search_entry
177
161 anchors.top: search_entry.bottom178 anchors.top: search_entry.bottom
162 anchors.topMargin: 2179 anchors.topMargin: 2
163 anchors.bottom: parent.bottom180 anchors.bottom: parent.bottom
@@ -165,6 +182,7 @@
165 anchors.leftMargin: 20182 anchors.leftMargin: 20
166 anchors.right: !refine_search.visible || refine_search.folded ? parent.right : refine_search.left183 anchors.right: !refine_search.visible || refine_search.folded ? parent.right : refine_search.left
167 anchors.rightMargin: !refine_search.visible || refine_search.folded ? 0 : 15184 anchors.rightMargin: !refine_search.visible || refine_search.folded ? 0 : 15
185 onLoaded: item.focus = true
168 }186 }
169 }187 }
170188

Subscribers

People subscribed via source and target branches