Merge lp:~zsombi/ubuntu-ui-toolkit/80-selection-mode into lp:ubuntu-ui-toolkit/staging
- 80-selection-mode
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Cris Dywan |
Approved revision: | 1379 |
Merged at revision: | 1413 |
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/80-selection-mode |
Merge into: | lp:ubuntu-ui-toolkit/staging |
Prerequisite: | lp:~zsombi/ubuntu-ui-toolkit/79b-styling-reshufled |
Diff against target: |
1314 lines (+640/-69) 16 files modified
components.api (+5/-0) examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml (+1/-0) examples/ubuntu-ui-toolkit-gallery/NewListItems.qml (+28/-13) modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml (+86/-16) modules/Ubuntu/Components/plugin/uclistitem.cpp (+128/-13) modules/Ubuntu/Components/plugin/uclistitem.h (+17/-0) modules/Ubuntu/Components/plugin/uclistitem_p.h (+17/-2) modules/Ubuntu/Components/plugin/uclistitemstyle.cpp (+20/-0) modules/Ubuntu/Components/plugin/uclistitemstyle.h (+6/-0) modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp (+83/-5) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py (+22/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_uclistitem.py (+6/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml (+3/-1) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.py (+12/-0) tests/resources/listitems/ListItemTest.qml (+18/-2) tests/unit_x11/tst_components/tst_listitem.qml (+188/-17) |
To merge this branch: | bzr merge lp:~zsombi/ubuntu-ui-toolkit/80-selection-mode |
Related bugs: | |
Related blueprints: |
SDK: Design a new ListItem and layouts
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Cris Dywan | Approve | ||
Review via email: mp+249499@code.launchpad.net |
Commit message
Introducing select/multiselect mode in ListItem.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1369
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
> 747 + self.wait_
A proper error message in a ToolkitException is desirable here I think.
The "new list items" section in the gallery prints "qml: Highlighting list item" when tapping, overlooked debugging? More importantly I don't see selection mode demonstrated at all.
I've used tests/resources
The test_listitem.
Visual observation: the animation from toggling select mode flickers extremely. Is this expected?
> 973 - flick(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
> 974 + swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
Why is this? Work-around? Something to document?
> 268 + * attached property. This property is attached to each parent item of the ListItem
I don't really understand the explanation and the example doesn't demonstrate what I think it says. There's no ListView here. The Flickable has the attached property but Column supposedly won't work. I would expect Column to be the parent and have the property. Aside from clarifying the wording I'd suggest two examples because of the special case.
Zsombor Egri (zsombi) wrote : | # |
> > 747 + self.wait_
>
> A proper error message in a ToolkitException is desirable here I think.
Ok, I'll add that.
>
> The "new list items" section in the gallery prints "qml: Highlighting list
> item" when tapping, overlooked debugging? More importantly I don't see
> selection mode demonstrated at all.
That is Tim's playground, he used to update that part when I have an MR bringing new features to ListItem, that's the way he used to test the new features and eventually suggest API changes, extra API. That's why I don't have anything there. The print is an expected extra for the gallery.
>
> I've used tests/resources
> unfortunately it doesn't scroll so I'm not sure if I can see everything in
> there. At the bottom there's a very narrow item with a switch.
The whole view doesn't scroll, true. It's a bit crowded to be honest... It has a ListItem and a Flickable, testing attached behavior in both. Perhaps I should add a separate sample for select mode...
>
> The test_listitem.
> listView.
> in the gallery and manual test case as well.
Right. As said, Tim used to test the API, perhaps you can also do an MR based on this and then we submit that. It's better if someone else tries also the API to make sure it's understandable and easy to use.
>
> Visual observation: the animation from toggling select mode flickers
> extremely. Is this expected?
>
> > 973 - flick(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
> > 974 + swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
>
> Why is this? Work-around? Something to document?
I think the swipe() function documents it... It is intentional, as we discussed in one of the standups last week, I have to move slower than the test function otherwise does, so the Repeater can create all the elements based on the model, otherwise the swipe is not reliable.
>
> > 268 + * attached property. This property is attached to each parent item
> of the ListItem
>
> I don't really understand the explanation and the example doesn't demonstrate
> what I think it says. There's no ListView here. The Flickable has the attached
> property but Column supposedly won't work. I would expect Column to be the
> parent and have the property. Aside from clarifying the wording I'd suggest
> two examples because of the special case.
No. ListItem's parents are the Column, and that will get the ViewItems attached property. Exception is the ListView, where the contentItem would be the one to have the attached property, but the ListItem detects it and attaches top ListView.
So, ViewItems is automatically attached to the ListItem's parent exception being when used as delegate of ListView, when will be attached to the ListView itself. So Column gets it automatically, and all you set in Column it will have effect to the ListItems in Column, but the one declared in Flickable won't work. It will not give error, but won't do anything. And unfortunately we cannot drive or detect whether...
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1372
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1373
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
So… "selectable" is now available… I expected ViewItems.
Zsombor Egri (zsombi) wrote : | # |
> So… "selectable" is now available… I expected ViewItems.
> ListItem which isn't the case. Using "selectable" works but seems wrong
> because it appears to be a property of each item when it's for the whole
> group.
Expecting ViewItems.
In case this setup seems miserable, we can restrict to have the ViewItems available only for ListView, in which case we lose the flexibility of the ListItem. Dragging for instance will be available only on ListView, as it cannot handle the proper rearranging of the ListItems without the displaced animation, as Repeater or Column/Row for instance does not provide any abilities to animate the position changes.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1377
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1378. By Zsombor Egri
-
documentation fixed
- 1379. By Zsombor Egri
-
staging merge
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'components.api' |
2 | --- components.api 2015-02-18 12:40:42 +0000 |
3 | +++ components.api 2015-02-19 12:17:51 +0000 |
4 | @@ -935,6 +935,8 @@ |
5 | Property { name: "contentMoving"; type: "bool"; isReadonly: true } |
6 | Property { name: "color"; type: "QColor" } |
7 | Property { name: "highlightColor"; type: "QColor" } |
8 | + Property { name: "selected"; type: "bool" } |
9 | + Property { name: "selectMode"; type: "bool" } |
10 | Property { name: "action"; type: "UCAction"; isPointer: true } |
11 | Property { name: "listItemData"; type: "QObject"; isList: true; isReadonly: true } |
12 | Property { name: "listItemChildren"; type: "QQuickItem"; isList: true; isReadonly: true } |
13 | @@ -958,6 +960,7 @@ |
14 | prototype: "QQuickItem" |
15 | exports: ["Ubuntu.Components.Styles/ListItemStyle 1.2"] |
16 | Property { name: "snapAnimation"; type: "QQuickAbstractAnimation"; isPointer: true } |
17 | + Property { name: "animatePanels"; type: "bool"; isReadonly: true } |
18 | Method { |
19 | name: "swipeEvent" |
20 | Parameter { name: "event"; type: "UCSwipeEvent"; isPointer: true } |
21 | @@ -1074,6 +1077,8 @@ |
22 | name: "UCViewItemsAttached" |
23 | prototype: "QObject" |
24 | exports: ["ViewItems 1.2"] |
25 | + Property { name: "selectMode"; type: "bool" } |
26 | + Property { name: "selectedIndices"; type: "QList<int>" } |
27 | name: "UbuntuI18n" |
28 | prototype: "QObject" |
29 | exports: ["i18n 0.1", "i18n 1.0"] |
30 | |
31 | === modified file 'examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml' |
32 | --- examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-01-05 20:34:54 +0000 |
33 | +++ examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-02-19 12:17:51 +0000 |
34 | @@ -27,4 +27,5 @@ |
35 | verticalCenter: parent.verticalCenter |
36 | } |
37 | } |
38 | + onPressAndHold: selectMode = !selectMode |
39 | } |
40 | |
41 | === modified file 'examples/ubuntu-ui-toolkit-gallery/NewListItems.qml' |
42 | --- examples/ubuntu-ui-toolkit-gallery/NewListItems.qml 2015-02-12 17:25:35 +0000 |
43 | +++ examples/ubuntu-ui-toolkit-gallery/NewListItems.qml 2015-02-19 12:17:51 +0000 |
44 | @@ -34,21 +34,37 @@ |
45 | // clip the action delegates while swiping left/right |
46 | clip: true |
47 | |
48 | - ListItemWithLabel { |
49 | - text: i18n.tr("Basic") |
50 | - } |
51 | - ListItemWithLabel { |
52 | - text: i18n.tr("Colored divider") |
53 | - divider { |
54 | - colorFrom: UbuntuColors.red |
55 | - colorTo: UbuntuColors.green |
56 | + ListView { |
57 | + height: units.gu(20) |
58 | + width: parent.width |
59 | + |
60 | + model: [ i18n.tr("Basic"), i18n.tr("Colored divider"), i18n.tr("No divider") ] |
61 | + delegate: ListItemWithLabel { |
62 | + text: modelData |
63 | + divider { |
64 | + colorFrom: modelData == i18n.tr("Colored divider") ? UbuntuColors.red : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
65 | + colorTo: modelData == i18n.tr("Colored divider") ? UbuntuColors.green : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
66 | + visible: modelData != i18n.tr("No divider") |
67 | + } |
68 | } |
69 | } |
70 | - ListItemWithLabel { |
71 | - text: i18n.tr("No divider") |
72 | - divider.visible: false |
73 | + } |
74 | + |
75 | + TemplateSection { |
76 | + className: "ListItem" |
77 | + // no spacing between the list items in the Column |
78 | + spacing: 0 |
79 | + Item { |
80 | + // compensate for the spacing of 0 by adding this |
81 | + // Item inbetween the title and the list items. |
82 | + height: units.gu(3) |
83 | + width: parent.width |
84 | } |
85 | - ListItemWithLabel { |
86 | + |
87 | + // clip the action delegates while swiping left/right |
88 | + clip: true |
89 | + |
90 | + ListItemWithLabel { |
91 | color: UbuntuColors.blue |
92 | text: i18n.tr("Colored") |
93 | } |
94 | @@ -56,7 +72,6 @@ |
95 | text: i18n.tr("Highlight color") |
96 | highlightColor: UbuntuColors.orange |
97 | // no highlight without clicked() or leading/trailing actions |
98 | - onClicked: print(i18n.tr("Highlighting list item")) |
99 | } |
100 | |
101 | ListItemActions { |
102 | |
103 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml' |
104 | --- modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml 2015-02-12 17:25:35 +0000 |
105 | +++ modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml 2015-02-19 12:17:51 +0000 |
106 | @@ -44,13 +44,12 @@ |
107 | Rectangle { |
108 | id: panel |
109 | objectName: "ListItemPanel" + (leading ? "Leading" : "Trailing") |
110 | - property bool leading: false |
111 | readonly property real panelWidth: actionsRow.width |
112 | |
113 | // FIXME use theme palette colors once stabilized |
114 | color: leading ? UbuntuColors.red : "white" |
115 | anchors.fill: parent |
116 | - width: parent.width |
117 | + width: parent ? parent.width : 0 |
118 | |
119 | readonly property ListItemActions itemActions: leading ? styledItem.leadingActions : styledItem.trailingActions |
120 | |
121 | @@ -64,7 +63,7 @@ |
122 | leftMargin: spacing |
123 | } |
124 | |
125 | - property real maxItemWidth: parent.width / itemActions.actions.length |
126 | + readonly property real maxItemWidth: parent.width / itemActions.actions.length |
127 | |
128 | Repeater { |
129 | model: itemActions.actions |
130 | @@ -124,6 +123,46 @@ |
131 | } |
132 | } |
133 | } |
134 | + // the selection/multiselection panel |
135 | + Component { |
136 | + id: selectionDelegate |
137 | + Item { |
138 | + id: selectPanel |
139 | + objectName: "selection_panel" + listItemIndex |
140 | + anchors.fill: parent ? parent : undefined |
141 | + |
142 | + CheckBox { |
143 | + id: checkbox |
144 | + opacity: 0 |
145 | + // for unit and autopilot tests |
146 | + objectName: "listitem_select" |
147 | + anchors.centerIn: parent |
148 | + // for the initial value |
149 | + checked: styledItem.selected |
150 | + onCheckedChanged: styledItem.selected = checked; |
151 | + } |
152 | + |
153 | + states: State { |
154 | + name: "enabled" |
155 | + when: loaded && styledItem.selectMode |
156 | + PropertyChanges { |
157 | + target: checkbox |
158 | + opacity: 1.0 |
159 | + } |
160 | + } |
161 | + transitions: Transition { |
162 | + from: "" |
163 | + to: "*" |
164 | + reversible: true |
165 | + enabled: listItemStyle.animatePanels |
166 | + OpacityAnimator { |
167 | + easing: UbuntuAnimation.StandardEasing |
168 | + duration: UbuntuAnimation.FastDuration |
169 | + } |
170 | + } |
171 | + } |
172 | + } |
173 | + |
174 | |
175 | // leading panel loader |
176 | Loader { |
177 | @@ -135,13 +174,46 @@ |
178 | right: parent.left |
179 | } |
180 | width: parent.width |
181 | - sourceComponent: styledItem.leadingActions && styledItem.leadingActions.actions.length > 0 ? |
182 | + sourceComponent: internals.swiped && styledItem.leadingActions && styledItem.leadingActions.actions.length > 0 ? |
183 | panelComponent : null |
184 | - onItemChanged: { |
185 | - if (item) { |
186 | - item.leading = true; |
187 | - } |
188 | - } |
189 | + // context properties used in delegates |
190 | + readonly property bool leading: true |
191 | + readonly property bool loaded: status == Loader.Ready |
192 | + |
193 | + // panel states |
194 | + states: [ |
195 | + State { |
196 | + name: "selectable" |
197 | + when: styledItem.selectMode |
198 | + PropertyChanges { |
199 | + target: leadingLoader |
200 | + sourceComponent: selectionDelegate |
201 | + width: units.gu(5) |
202 | + } |
203 | + PropertyChanges { |
204 | + target: listItemStyle |
205 | + anchors.leftMargin: 0 |
206 | + } |
207 | + PropertyChanges { |
208 | + target: styledItem.contentItem |
209 | + anchors.leftMargin: units.gu(5) |
210 | + } |
211 | + } |
212 | + ] |
213 | + transitions: [ |
214 | + Transition { |
215 | + from: "" |
216 | + to: "selectable" |
217 | + reversible: true |
218 | + enabled: listItemStyle.animatePanels |
219 | + PropertyAnimation { |
220 | + target: styledItem.contentItem |
221 | + properties: "anchors.leftMargin" |
222 | + easing: UbuntuAnimation.StandardEasing |
223 | + duration: UbuntuAnimation.FastDuration |
224 | + } |
225 | + } |
226 | + ] |
227 | } |
228 | // trailing panel loader |
229 | Loader { |
230 | @@ -153,13 +225,10 @@ |
231 | left: parent.right |
232 | } |
233 | width: parent.width |
234 | - sourceComponent: styledItem.trailingActions && styledItem.trailingActions.actions.length > 0 ? |
235 | + sourceComponent: internals.swiped && styledItem.trailingActions && styledItem.trailingActions.actions.length > 0 ? |
236 | panelComponent : null |
237 | - onItemChanged: { |
238 | - if (item) { |
239 | - item.leading = false; |
240 | - } |
241 | - } |
242 | + // context properties used in delegates |
243 | + readonly property bool leading: false |
244 | } |
245 | |
246 | // internals |
247 | @@ -168,10 +237,11 @@ |
248 | // action triggered |
249 | property Action selectedAction |
250 | // swipe handling |
251 | + readonly property bool swiped: listItemStyle.x != styledItem.x && !styledItem.selectMode |
252 | readonly property Item swipedPanel: listItemStyle.x > 0 ? leadingLoader.item : trailingLoader.item |
253 | readonly property bool leadingPanel: listItemStyle.x > 0 |
254 | readonly property real swipedOffset: leadingPanel ? listItemStyle.x : -listItemStyle.x |
255 | - readonly property real panelWidth: swipedPanel ? swipedPanel.panelWidth : 0 |
256 | + readonly property real panelWidth: swipedPanel && swipedPanel.hasOwnProperty("panelWidth") ? swipedPanel.panelWidth : 0 |
257 | property real prevX: 0.0 |
258 | property real snapChangerLimit: 0.0 |
259 | readonly property real threshold: units.gu(1.5) |
260 | |
261 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem.cpp' |
262 | --- modules/Ubuntu/Components/plugin/uclistitem.cpp 2015-02-19 08:31:38 +0000 |
263 | +++ modules/Ubuntu/Components/plugin/uclistitem.cpp 2015-02-19 12:17:51 +0000 |
264 | @@ -305,6 +305,15 @@ |
265 | setContentMoving(styleItem->m_snapAnimation->isRunning()); |
266 | } |
267 | |
268 | +// synchronizes selection mode, initializes the style if has not been done yet, |
269 | +// which in turn reveals the selection panels |
270 | +void UCListItemPrivate::_q_syncSelectMode() |
271 | +{ |
272 | + initStyleItem(); |
273 | + Q_Q(UCListItem); |
274 | + Q_EMIT q->selectModeChanged(); |
275 | +} |
276 | + |
277 | /*! |
278 | * \qmlproperty Component ListItem::style |
279 | * Holds the style of the component defining the components visualizing the leading/ |
280 | @@ -375,8 +384,9 @@ |
281 | } |
282 | } |
283 | |
284 | -// creates the style item |
285 | -void UCListItemPrivate::initStyleItem() |
286 | +// creates the style item, with altered default value of the animatePanels style property |
287 | +// the property is turned on after the panel initialization. |
288 | +void UCListItemPrivate::initStyleItem(bool withAnimatedPanels) |
289 | { |
290 | if (!ready || styleItem) { |
291 | return; |
292 | @@ -411,8 +421,11 @@ |
293 | return; |
294 | } |
295 | QQml_setParent_noEvent(styleItem, q); |
296 | + styleItem->setAnimatePanels(withAnimatedPanels); |
297 | styleItem->setParentItem(q); |
298 | delegate->completeCreate(); |
299 | + // turn animations on |
300 | + styleItem->setAnimatePanels(true); |
301 | Q_EMIT q->__styleInstanceChanged(); |
302 | } |
303 | |
304 | @@ -459,7 +472,8 @@ |
305 | Q_Q(UCListItem); |
306 | QPointF myPos = q->mapToItem(contentItem, event->localPos()); |
307 | QQuickItem *child = contentItem->childAt(myPos.x(), myPos.y()); |
308 | - bool activeComponent = child && (child->acceptedMouseButtons() & event->button()) && !qobject_cast<QQuickText*>(child); |
309 | + bool activeComponent = child && (child->acceptedMouseButtons() & event->button()) && |
310 | + child->isEnabled() && !qobject_cast<QQuickText*>(child); |
311 | // do highlight if not pressed above the active component, and the component has either |
312 | // action, leading or trailing actions list or at least an active child component declared |
313 | QQuickMouseArea *ma = q->findChild<QQuickMouseArea*>(); |
314 | @@ -672,7 +686,8 @@ |
315 | * Being an Item, all properties can be accessed or altered. However, make sure you |
316 | * never change \c x, \c y, \c width, \c height or \c anchors properties as those are |
317 | * controlled by the ListItem itself when leading or trailing actions are revealed |
318 | - * and thus might cause the component to misbehave. Anchors margins are free to alter. |
319 | + * or when selectable and draggable mode is turned on, and thus might cause the |
320 | + * component to misbehave. Anchors margins are free to alter. |
321 | * |
322 | * Each ListItem has a thin divider shown on the bottom of the component. This |
323 | * divider can be configured through the \c divider grouped property, which can |
324 | @@ -680,7 +695,7 @@ |
325 | * When used in \c ListView or \l UbuntuListView, the last list item will not |
326 | * show the divider no matter of the visible property value set. |
327 | * |
328 | - * ListItem can handle actions that can get swiped from front to back of the item. |
329 | + * ListItem can handle actions that can get swiped from front or back of the item. |
330 | * These actions are Action elements visualized in panels attached to the front |
331 | * or to the back of the item, and are revealed by swiping the item horizontally. |
332 | * The swipe is started only after the mouse/touch move had passed a given threshold. |
333 | @@ -738,6 +753,48 @@ |
334 | * of the ListItem. However not all properties are valid in all the circumstances. |
335 | * |
336 | * The component is styled using the \l ListItemStyle style interface. |
337 | + * |
338 | + * \section3 Selection mode |
339 | + * The selection mode of a ListItem is controlled by the \l ViewItems::selectMode |
340 | + * attached property. This property is attached to each parent item of the ListItem |
341 | + * exception being when used as delegate in ListView, where the property is attached |
342 | + * to the view itself. |
343 | + * \qml |
344 | + * import QtQuick 2.3 |
345 | + * import Ubuntu.Components 1.2 |
346 | + * |
347 | + * Flickable { |
348 | + * width: units.gu(40) |
349 | + * height: units.gu(50) |
350 | + * |
351 | + * // this will not have any effect |
352 | + * ViewItems.selectMode: true |
353 | + * Column { |
354 | + * // this will work |
355 | + * ViewItems.selectMode: false |
356 | + * width: parent.width |
357 | + * Repeater { |
358 | + * model: 25 |
359 | + * ListItem { |
360 | + * Label { |
361 | + * text: "ListItem in Flickable #" + index |
362 | + * } |
363 | + * } |
364 | + * } |
365 | + * } |
366 | + * } |
367 | + * \endqml |
368 | + * The indices selected are stored in \l ViewItems::selectedIndices attached property, |
369 | + * attached the same way as the \l ViewItems::selectMode property is. This is a |
370 | + * read/write property, meaning that initial selected item indices can be set up. |
371 | + * The list contains the indices added in the order of selection, not sorted in |
372 | + * any form. |
373 | + * |
374 | + * \note When in selectable mode, the ListItem content is not disabled and \l clicked |
375 | + * and \l pressAndHold signals are also emitted. The only restriction the component |
376 | + * implies is that leading and trailing actions cannot be swiped in. \ selectable |
377 | + * property can be used to implement different behavior when \l clicked or \l |
378 | + * pressAndHold. |
379 | */ |
380 | |
381 | /*! |
382 | @@ -746,16 +803,16 @@ |
383 | * is set. The signal is not emitted if the ListItem content is swiped or when used in |
384 | * Flickable (or ListView, GridView) and the Flickable gets moved. |
385 | * |
386 | - * If the ListItem contains a component which contains a MouseArea, the clicked |
387 | - * signal will be supressed. |
388 | + * If the ListItem contains a component which contains an active MouseArea, the clicked |
389 | + * signal will be supressed when clicked over this area. |
390 | */ |
391 | |
392 | /*! |
393 | * \qmlsignal ListItem::pressAndHold() |
394 | * The signal is emitted when the list item is long pressed. |
395 | * |
396 | - * If the ListItem contains a component which contains a MouseArea, the pressAndHold |
397 | - * signal will be supressed. |
398 | + * If the ListItem contains a component which contains an active MouseArea, the pressAndHold |
399 | + * signal will be supressed when pressed over this area. |
400 | */ |
401 | |
402 | UCListItem::UCListItem(QQuickItem *parent) |
403 | @@ -795,6 +852,20 @@ |
404 | this, SLOT(_q_updateIndex()), Qt::DirectConnection); |
405 | update(); |
406 | } |
407 | + |
408 | + if (d->parentAttached) { |
409 | + // connect selectedIndicesChanged |
410 | + connect(d->parentAttached, &UCViewItemsAttached::selectedIndicesChanged, |
411 | + this, &UCListItem::selectedChanged); |
412 | + // sync selectModeChanged() |
413 | + connect(d->parentAttached, SIGNAL(selectModeChanged()), |
414 | + this, SLOT(_q_syncSelectMode())); |
415 | + |
416 | + // if selection mode is on, initialize style |
417 | + if (d->parentAttached->selectMode()) { |
418 | + d->initStyleItem(false); |
419 | + } |
420 | + } |
421 | } |
422 | |
423 | void UCListItem::itemChange(ItemChange change, const ItemChangeData &data) |
424 | @@ -830,10 +901,9 @@ |
425 | |
426 | if (parentAttachee) { |
427 | QObject::connect(parentAttachee, SIGNAL(widthChanged()), this, SLOT(_q_updateSize()), Qt::DirectConnection); |
428 | + // update size |
429 | + d->_q_updateSize(); |
430 | } |
431 | - |
432 | - // update size |
433 | - d->_q_updateSize(); |
434 | } |
435 | } |
436 | |
437 | @@ -936,7 +1006,10 @@ |
438 | Q_D(UCListItem); |
439 | UCStyledItemBase::mouseMoveEvent(event); |
440 | |
441 | -// qDebug() << "MOVE" << event->localPos(); |
442 | + if (d->selectMode()) { |
443 | + // no move is allowed while selectable mode is on |
444 | + return; |
445 | + } |
446 | |
447 | // accept the tugging only if the move is within the threshold |
448 | if (d->highlighted && !d->swiped && (d->leadingActions || d->trailingActions)) { |
449 | @@ -1261,6 +1334,48 @@ |
450 | } |
451 | |
452 | /*! |
453 | + * |
454 | + * \qmlproperty bool ListItem::selected |
455 | + * The property drives whether a list item is selected or not. Defaults to false. |
456 | + * |
457 | + * \sa ListItem::selectMode, ViewItems::selectMode |
458 | + */ |
459 | +bool UCListItemPrivate::isSelected() |
460 | +{ |
461 | + Q_Q(UCListItem); |
462 | + return UCViewItemsAttachedPrivate::get(parentAttached)->isItemSelected(q); |
463 | +} |
464 | +void UCListItemPrivate::setSelected(bool value) |
465 | +{ |
466 | + Q_Q(UCListItem); |
467 | + if (value) { |
468 | + UCViewItemsAttachedPrivate::get(parentAttached)->addSelectedItem(q); |
469 | + } else { |
470 | + UCViewItemsAttachedPrivate::get(parentAttached)->removeSelectedItem(q); |
471 | + } |
472 | +} |
473 | + |
474 | +/*! |
475 | + * \qmlproperty bool ListItem::selectMode |
476 | + * The property reports whether the component and the view using the component |
477 | + * is in selectable state. While selectable, the ListItem's leading- and trailing |
478 | + * panels cannot be swiped in. \l clicked and \l pressAndHold signals are also |
479 | + * triggered. Selectable mode can be set either through this property or through |
480 | + * the parent attached \l ViewItems::selectMode property. |
481 | + */ |
482 | +bool UCListItemPrivate::selectMode() |
483 | +{ |
484 | + UCViewItemsAttachedPrivate *attached = UCViewItemsAttachedPrivate::get(parentAttached); |
485 | + return attached ? attached->selectable : false; |
486 | +} |
487 | +void UCListItemPrivate::setSelectMode(bool selectable) |
488 | +{ |
489 | + if (parentAttached) { |
490 | + parentAttached->setSelectMode(selectable); |
491 | + } |
492 | +} |
493 | + |
494 | +/*! |
495 | * \qmlproperty Action ListItem::action |
496 | * The property holds the action which will be triggered when the ListItem is |
497 | * clicked. ListItem will not visualize the action, that is the responsibility |
498 | |
499 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem.h' |
500 | --- modules/Ubuntu/Components/plugin/uclistitem.h 2015-02-11 13:35:34 +0000 |
501 | +++ modules/Ubuntu/Components/plugin/uclistitem.h 2015-02-19 12:17:51 +0000 |
502 | @@ -36,6 +36,8 @@ |
503 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool contentMoving READ contentMoving NOTIFY contentMovingChanged) |
504 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) |
505 | Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setHighlightColor RESET resetHighlightColor NOTIFY highlightColorChanged) |
506 | + Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool selected READ isSelected WRITE setSelected NOTIFY selectedChanged) |
507 | + Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool selectMode READ selectMode WRITE setSelectMode NOTIFY selectModeChanged) |
508 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), UCAction *action READ action WRITE setAction NOTIFY actionChanged DESIGNABLE false) |
509 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), QQmlListProperty<QObject> listItemData READ data DESIGNABLE false) |
510 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), QQmlListProperty<QQuickItem> listItemChildren READ children NOTIFY listItemChildrenChanged DESIGNABLE false) |
511 | @@ -78,6 +80,8 @@ |
512 | void contentMovingChanged(); |
513 | void colorChanged(); |
514 | void highlightColorChanged(); |
515 | + void selectedChanged(); |
516 | + void selectModeChanged(); |
517 | void actionChanged(); |
518 | void listItemChildrenChanged(); |
519 | |
520 | @@ -100,6 +104,7 @@ |
521 | Q_PRIVATE_SLOT(d_func(), void _q_updateSize()) |
522 | Q_PRIVATE_SLOT(d_func(), void _q_updateIndex()) |
523 | Q_PRIVATE_SLOT(d_func(), void _q_contentMoving()) |
524 | + Q_PRIVATE_SLOT(d_func(), void _q_syncSelectMode()) |
525 | }; |
526 | |
527 | class UCListItemDividerPrivate; |
528 | @@ -134,6 +139,8 @@ |
529 | class UCViewItemsAttached : public QObject |
530 | { |
531 | Q_OBJECT |
532 | + Q_PROPERTY(bool selectMode READ selectMode WRITE setSelectMode NOTIFY selectModeChanged) |
533 | + Q_PROPERTY(QList<int> selectedIndices READ selectedIndices WRITE setSelectedIndices NOTIFY selectedIndicesChanged) |
534 | public: |
535 | explicit UCViewItemsAttached(QObject *owner); |
536 | ~UCViewItemsAttached(); |
537 | @@ -145,8 +152,18 @@ |
538 | bool isMoving(); |
539 | bool isBoundTo(UCListItem *item); |
540 | |
541 | + // getter/setter |
542 | + bool selectMode() const; |
543 | + void setSelectMode(bool value); |
544 | + QList<int> selectedIndices() const; |
545 | + void setSelectedIndices(const QList<int> &list); |
546 | private Q_SLOTS: |
547 | void unbindItem(); |
548 | + |
549 | +Q_SIGNALS: |
550 | + void selectModeChanged(); |
551 | + void selectedIndicesChanged(); |
552 | + |
553 | private: |
554 | Q_DECLARE_PRIVATE(UCViewItemsAttached) |
555 | QScopedPointer<UCViewItemsAttachedPrivate> d_ptr; |
556 | |
557 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem_p.h' |
558 | --- modules/Ubuntu/Components/plugin/uclistitem_p.h 2015-02-12 17:25:35 +0000 |
559 | +++ modules/Ubuntu/Components/plugin/uclistitem_p.h 2015-02-19 12:17:51 +0000 |
560 | @@ -57,6 +57,7 @@ |
561 | void _q_updateSize(); |
562 | void _q_updateIndex(); |
563 | void _q_contentMoving(); |
564 | + void _q_syncSelectMode(); |
565 | int index(); |
566 | bool canHighlight(QMouseEvent *event); |
567 | void setHighlighted(bool pressed); |
568 | @@ -101,8 +102,12 @@ |
569 | QQmlComponent *style() const; |
570 | void setStyle(QQmlComponent *delegate); |
571 | void resetStyle(); |
572 | - void initStyleItem(); |
573 | + void initStyleItem(bool withAnimatedPanels = true); |
574 | QQuickItem *styleInstance() const; |
575 | + bool isSelected(); |
576 | + void setSelected(bool value); |
577 | + bool selectMode(); |
578 | + void setSelectMode(bool selectable); |
579 | UCAction *action() const; |
580 | void setAction(UCAction *action); |
581 | }; |
582 | @@ -115,13 +120,23 @@ |
583 | UCViewItemsAttachedPrivate(UCViewItemsAttached *qq); |
584 | ~UCViewItemsAttachedPrivate(); |
585 | |
586 | + static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item) |
587 | + { |
588 | + return item ? item->d_func() : 0; |
589 | + } |
590 | + |
591 | void clearFlickablesList(); |
592 | void buildFlickablesList(); |
593 | void clearChangesList(); |
594 | void buildChangesList(const QVariant &newValue); |
595 | + bool addSelectedItem(UCListItem *item); |
596 | + bool removeSelectedItem(UCListItem *item); |
597 | + bool isItemSelected(UCListItem *item); |
598 | |
599 | UCViewItemsAttached *q_ptr; |
600 | - bool globalDisabled; |
601 | + bool globalDisabled:1; |
602 | + bool selectable:1; |
603 | + QSet<int> selectedList; |
604 | QList< QPointer<QQuickFlickable> > flickables; |
605 | QList< PropertyChange* > changes; |
606 | QPointer<UCListItem> boundItem; |
607 | |
608 | === modified file 'modules/Ubuntu/Components/plugin/uclistitemstyle.cpp' |
609 | --- modules/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-02-12 17:25:35 +0000 |
610 | +++ modules/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-02-19 12:17:51 +0000 |
611 | @@ -38,6 +38,7 @@ |
612 | UCListItemStyle::UCListItemStyle(QQuickItem *parent) |
613 | : QQuickItem(parent) |
614 | , m_snapAnimation(0) |
615 | + , m_animatePanels(true) |
616 | { |
617 | } |
618 | |
619 | @@ -120,3 +121,22 @@ |
620 | * \qmlproperty Animation ListItemStyle::snapAnimation |
621 | * Holds the behavior used in animating when snapped in or out. |
622 | */ |
623 | + |
624 | +/*! |
625 | + * \qmlproperty bool ListItemStyle::animatePanels |
626 | + * The property drives the different panel animations in the style. Panels should |
627 | + * not be animated when created upon scrolling a view. |
628 | + */ |
629 | +bool UCListItemStyle::animatePanels() const |
630 | +{ |
631 | + return m_animatePanels; |
632 | +} |
633 | +// the setter is used by the ListItem to drive animation state |
634 | +void UCListItemStyle::setAnimatePanels(bool animate) |
635 | +{ |
636 | + if (m_animatePanels == animate) { |
637 | + return; |
638 | + } |
639 | + m_animatePanels = animate; |
640 | + Q_EMIT animatePanelsChanged(); |
641 | +} |
642 | |
643 | === modified file 'modules/Ubuntu/Components/plugin/uclistitemstyle.h' |
644 | --- modules/Ubuntu/Components/plugin/uclistitemstyle.h 2015-02-12 17:25:35 +0000 |
645 | +++ modules/Ubuntu/Components/plugin/uclistitemstyle.h 2015-02-19 12:17:51 +0000 |
646 | @@ -64,14 +64,18 @@ |
647 | { |
648 | Q_OBJECT |
649 | Q_PROPERTY(QQuickAbstractAnimation *snapAnimation MEMBER m_snapAnimation NOTIFY snapAnimationChanged) |
650 | + Q_PROPERTY(bool animatePanels READ animatePanels NOTIFY animatePanelsChanged) |
651 | public: |
652 | explicit UCListItemStyle(QQuickItem *parent = 0); |
653 | |
654 | void invokeSwipeEvent(UCSwipeEvent *event); |
655 | void invokeRebound(); |
656 | + bool animatePanels() const; |
657 | + void setAnimatePanels(bool animate); |
658 | |
659 | Q_SIGNALS: |
660 | void snapAnimationChanged(); |
661 | + void animatePanelsChanged(); |
662 | |
663 | public Q_SLOTS: |
664 | void swipeEvent(UCSwipeEvent *event); |
665 | @@ -85,6 +89,8 @@ |
666 | QMetaMethod m_swipeEvent; |
667 | QMetaMethod m_rebound; |
668 | QQuickAbstractAnimation *m_snapAnimation; |
669 | + bool m_animatePanels:1; |
670 | + |
671 | friend class UCListItemPrivate; |
672 | friend class UCActionPanel; |
673 | friend class ListItemAnimator; |
674 | |
675 | === modified file 'modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp' |
676 | --- modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-02-10 10:27:30 +0000 |
677 | +++ modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-02-19 12:17:51 +0000 |
678 | @@ -32,6 +32,7 @@ |
679 | UCViewItemsAttachedPrivate::UCViewItemsAttachedPrivate(UCViewItemsAttached *qq) |
680 | : q_ptr(qq) |
681 | , globalDisabled(false) |
682 | + , selectable(false) |
683 | { |
684 | } |
685 | |
686 | @@ -76,11 +77,7 @@ |
687 | void UCViewItemsAttachedPrivate::clearChangesList() |
688 | { |
689 | // clear property change objects |
690 | - Q_FOREACH(PropertyChange *change, changes) { |
691 | - // deleting PropertyChange will restore the saved property |
692 | - // to its original binding/value |
693 | - delete change; |
694 | - } |
695 | + qDeleteAll(changes); |
696 | changes.clear(); |
697 | } |
698 | |
699 | @@ -104,6 +101,17 @@ |
700 | } |
701 | } |
702 | |
703 | +/*! |
704 | + * \qmltype ViewItems |
705 | + * \instantiates UCViewItemsAttached |
706 | + * \inqmlmodule Ubuntu.Components 1.2 |
707 | + * \ingroup unstable-ubuntu-listitems |
708 | + * \since Ubuntu.Components 1.2 |
709 | + * \brief A set of properties attached to the ListItem's parent item or ListView. |
710 | + * |
711 | + * These properties are attached to the parent item of the ListItem, or to |
712 | + * ListView, when the component is used as delegate. |
713 | + */ |
714 | UCViewItemsAttached::UCViewItemsAttached(QObject *owner) |
715 | : QObject(owner) |
716 | , d_ptr(new UCViewItemsAttachedPrivate(this)) |
717 | @@ -205,3 +213,73 @@ |
718 | // clear binding list |
719 | d->clearFlickablesList(); |
720 | } |
721 | + |
722 | +/*! |
723 | + * \qmlattachedproperty bool ViewItems::selectMode |
724 | + * The property drives whether list items are selectable or not. |
725 | + * |
726 | + * When set, the ListItems of the Item the property is attached to will enter into |
727 | + * selection state. ListItems provide a visual clue which can be used to toggle |
728 | + * the selection state of each, which in order will be reflected in the |
729 | + * \l {ViewItems::selectedIndices}{ViewItems.selectedIndices} list. |
730 | + */ |
731 | +bool UCViewItemsAttached::selectMode() const |
732 | +{ |
733 | + Q_D(const UCViewItemsAttached); |
734 | + return d->selectable; |
735 | +} |
736 | +void UCViewItemsAttached::setSelectMode(bool value) |
737 | +{ |
738 | + Q_D(UCViewItemsAttached); |
739 | + if (d->selectable == value) { |
740 | + return; |
741 | + } |
742 | + d->selectable = value; |
743 | + Q_EMIT selectModeChanged(); |
744 | +} |
745 | + |
746 | +/*! |
747 | + * \qmlattachedproperty list<int> ViewItems::selectedIndices |
748 | + * The property contains the indexes of the ListItems marked as selected. The |
749 | + * indexes are model indexes when used in ListView, and child indexes in other |
750 | + * components. The property being writable, initial selection configuration |
751 | + * can be provided for a view, and provides ability to save the selection state. |
752 | + */ |
753 | +QList<int> UCViewItemsAttached::selectedIndices() const |
754 | +{ |
755 | + Q_D(const UCViewItemsAttached); |
756 | + return d->selectedList.toList(); |
757 | +} |
758 | +void UCViewItemsAttached::setSelectedIndices(const QList<int> &list) |
759 | +{ |
760 | + Q_D(UCViewItemsAttached); |
761 | + if (d->selectedList.toList() == list) { |
762 | + return; |
763 | + } |
764 | + d->selectedList = QSet<int>::fromList(list); |
765 | + Q_EMIT selectedIndicesChanged(); |
766 | +} |
767 | + |
768 | +bool UCViewItemsAttachedPrivate::addSelectedItem(UCListItem *item) |
769 | +{ |
770 | + int index = UCListItemPrivate::get(item)->index(); |
771 | + if (!selectedList.contains(index)) { |
772 | + selectedList.insert(index); |
773 | + Q_EMIT q_ptr->selectedIndicesChanged(); |
774 | + return true; |
775 | + } |
776 | + return false; |
777 | +} |
778 | +bool UCViewItemsAttachedPrivate::removeSelectedItem(UCListItem *item) |
779 | +{ |
780 | + if (selectedList.remove(UCListItemPrivate::get(item)->index()) > 0) { |
781 | + Q_EMIT q_ptr->selectedIndicesChanged(); |
782 | + return true; |
783 | + } |
784 | + return false; |
785 | +} |
786 | + |
787 | +bool UCViewItemsAttachedPrivate::isItemSelected(UCListItem *item) |
788 | +{ |
789 | + return selectedList.contains(UCListItemPrivate::get(item)->index()); |
790 | +} |
791 | |
792 | === modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py' |
793 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-08-21 06:29:28 +0000 |
794 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2015-02-19 12:17:51 +0000 |
795 | @@ -85,3 +85,25 @@ |
796 | child = self.select_single(objectName=object_name) |
797 | containers = self._get_containers() |
798 | return self._is_child_visible(child, containers) |
799 | + |
800 | + def _get_first_item(self): |
801 | + """Returns the first item from the ListView.""" |
802 | + items = self.get_children_by_type('QQuickItem')[0].get_children() |
803 | + items = sorted(items, key=lambda item: item.globalRect.y) |
804 | + return items[0] |
805 | + |
806 | + @autopilot_logging.log_action(logger.info) |
807 | + def enable_select_mode(self): |
808 | + """Default implementation to enable select mode. Performs a long tap |
809 | + over the first list item in the ListView. The delegates must be |
810 | + the new ListItem components. |
811 | + """ |
812 | + self.swipe_to_top() |
813 | + first_item = self._get_first_item() |
814 | + self.pointing_device.click_object(first_item, press_duration=2) |
815 | + try: |
816 | + self.wait_select_single('QQuickItem', |
817 | + objectName='selection_panel0') |
818 | + except dbus.StateNotFoundError: |
819 | + raise _common.ToolkitException( |
820 | + 'ListView delegate is not a ListItem or not in selectMode') |
821 | |
822 | === modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_uclistitem.py' |
823 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_uclistitem.py 2015-01-07 06:42:09 +0000 |
824 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_uclistitem.py 2015-02-19 12:17:51 +0000 |
825 | @@ -86,3 +86,9 @@ |
826 | """ |
827 | self._click_on_panel_action('trailing', action_objectName, |
828 | wait_function) |
829 | + |
830 | + @autopilot_logging.log_action(logger.info) |
831 | + def toggle_selected(self): |
832 | + """Toggles selected state of the ListItem. """ |
833 | + toggle = self.select_single(objectName='listitem_select') |
834 | + self.pointing_device.click_object(toggle) |
835 | |
836 | === modified file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml' |
837 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml 2015-02-04 16:27:27 +0000 |
838 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml 2015-02-19 12:17:51 +0000 |
839 | @@ -28,13 +28,15 @@ |
840 | Page { |
841 | id: testPage |
842 | objectName: "test_page" |
843 | - title: "No action triggered" |
844 | + title: listView.ViewItems.selectMode ? "In selection mode" : "No action triggered" |
845 | ListView { |
846 | id: listView |
847 | + objectName: "test_view" |
848 | anchors.fill: parent |
849 | model: 25 |
850 | delegate: ListItem { |
851 | objectName: "listitem" + index |
852 | + onPressAndHold: listView.ViewItems.selectMode = true |
853 | leadingActions: ListItemActions { |
854 | actions: [ |
855 | Action { |
856 | |
857 | === modified file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.py' |
858 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.py 2014-12-08 14:37:40 +0000 |
859 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.py 2015-02-19 12:17:51 +0000 |
860 | @@ -28,6 +28,8 @@ |
861 | |
862 | def setUp(self): |
863 | super(ListItemTestCase, self).setUp() |
864 | + self.list_view = self.main_view.select_single( |
865 | + ubuntuuitoolkit.QQuickListView, objectName='test_view') |
866 | self.test_listitem = self.main_view.select_single( |
867 | 'UCListItem', objectName='listitem0') |
868 | self.test_page = self.main_view.select_single( |
869 | @@ -84,3 +86,13 @@ |
870 | 'this_action_does_not_exist') |
871 | self.assertEqual(str(error), |
872 | 'The requested action not found on trailing side') |
873 | + |
874 | + def test_select_items(self): |
875 | + self.list_view.enable_select_mode() |
876 | + self.test_listitem.toggle_selected() |
877 | + self.assertTrue(self.test_listitem.selected) |
878 | + # select an other one |
879 | + listItem3 = self.main_view.select_single( |
880 | + 'UCListItem', objectName='listitem3') |
881 | + listItem3.toggle_selected() |
882 | + self.assertTrue(listItem3.selected) |
883 | |
884 | === modified file 'tests/resources/listitems/ListItemTest.qml' |
885 | --- tests/resources/listitems/ListItemTest.qml 2015-02-11 13:35:34 +0000 |
886 | +++ tests/resources/listitems/ListItemTest.qml 2015-02-19 12:17:51 +0000 |
887 | @@ -31,7 +31,10 @@ |
888 | id: stock |
889 | iconName: "starred" |
890 | text: "Staaaar" |
891 | - onTriggered: print(iconName, "triggered", value) |
892 | + onTriggered: { |
893 | + print(iconName, "triggered", value) |
894 | + view.ViewItems.selectedIndices = [0, 2, 9]; |
895 | + } |
896 | } |
897 | |
898 | ListItemActions { |
899 | @@ -66,6 +69,7 @@ |
900 | ] |
901 | } |
902 | |
903 | + property bool selectable: false |
904 | property list<Action> leadingArray: [ |
905 | Action { |
906 | iconName: "delete" |
907 | @@ -93,6 +97,11 @@ |
908 | right: parent.right |
909 | } |
910 | |
911 | + Button { |
912 | + text: "Selectable " + (selectable ? "OFF" : "ON") |
913 | + onClicked: selectable = !selectable |
914 | + } |
915 | + |
916 | ListItem { |
917 | id: testItem |
918 | objectName: "single" |
919 | @@ -161,6 +170,9 @@ |
920 | height: units.gu(28) |
921 | model: 25 |
922 | pressDelay: 0 |
923 | + ViewItems.selectMode: main.selectable |
924 | + ViewItems.selectedIndices: [9,3,4,1] |
925 | + ViewItems.onSelectedIndicesChanged: print("LISTVIEW INDEXES=", ViewItems.selectedIndices) |
926 | delegate: ListItem { |
927 | objectName: "ListItem" + index |
928 | id: listItem |
929 | @@ -179,6 +191,10 @@ |
930 | text: "This is one Label split in two lines.\n" + |
931 | "The second line - item #" + modelData |
932 | } |
933 | + Button { |
934 | + text: "Pressme..." |
935 | + anchors.centerIn: parent |
936 | + } |
937 | |
938 | states: State { |
939 | name: "override" |
940 | @@ -203,11 +219,11 @@ |
941 | id: trailing |
942 | actions: leading.actions |
943 | } |
944 | - |
945 | Column { |
946 | id: column |
947 | width: flicker.width |
948 | property alias count: repeater.count |
949 | + ViewItems.selectMode: main.selectable |
950 | Repeater { |
951 | id: repeater |
952 | model: 10 |
953 | |
954 | === modified file 'tests/unit_x11/tst_components/tst_listitem.qml' |
955 | --- tests/unit_x11/tst_components/tst_listitem.qml 2015-02-19 08:31:38 +0000 |
956 | +++ tests/unit_x11/tst_components/tst_listitem.qml 2015-02-19 12:17:51 +0000 |
957 | @@ -67,6 +67,7 @@ |
958 | } |
959 | |
960 | Column { |
961 | + id: testColumn |
962 | width: parent.width |
963 | ListItem { |
964 | id: defaults |
965 | @@ -107,6 +108,7 @@ |
966 | height: units.gu(28) |
967 | clip: true |
968 | model: 10 |
969 | + ViewItems.selectMode: false |
970 | delegate: ListItem { |
971 | objectName: "listItem" + index |
972 | color: "lightgray" |
973 | @@ -134,6 +136,24 @@ |
974 | } |
975 | } |
976 | } |
977 | + Flickable { |
978 | + id: flickable |
979 | + width: parent.width |
980 | + height: units.gu(14) |
981 | + clip: true |
982 | + contentHeight: column.height |
983 | + Column { |
984 | + id: column |
985 | + width: parent.width |
986 | + Repeater { |
987 | + model: 10 |
988 | + ListItem { |
989 | + objectName: "listItem" + index |
990 | + color: "lightgreen" |
991 | + } |
992 | + } |
993 | + } |
994 | + } |
995 | } |
996 | |
997 | UbuntuTestCase { |
998 | @@ -183,6 +203,12 @@ |
999 | } |
1000 | } |
1001 | |
1002 | + // delayed swipe, gives few millisecond timeout between each move |
1003 | + // so Repeater has time to create the panel actions in style |
1004 | + function swipe(item, x, y, dx, dy) { |
1005 | + flick(item, x, y, dx, dy, 0, 0, undefined, undefined, 100); |
1006 | + } |
1007 | + |
1008 | function initTestCase() { |
1009 | TestExtras.registerTouchDevice(); |
1010 | waitForRendering(main); |
1011 | @@ -191,6 +217,11 @@ |
1012 | function cleanup() { |
1013 | testItem.action = null; |
1014 | testItem.contentItem.anchors.margins = 0; |
1015 | + testItem.selected = false; |
1016 | + testColumn.ViewItems.selectMode = false; |
1017 | + waitForRendering(testItem.contentItem, 200); |
1018 | + controlItem.selected = false; |
1019 | + waitForRendering(controlItem.contentItem, 200); |
1020 | movingSpy.clear(); |
1021 | highlightedSpy.clear(); |
1022 | clickSpy.clear(); |
1023 | @@ -199,6 +230,7 @@ |
1024 | buttonSpy.clear(); |
1025 | interactiveSpy.clear(); |
1026 | listView.interactive = true; |
1027 | + listView.ViewItems.selectMode = false; |
1028 | // make sure we collapse |
1029 | mouseClick(defaults, 0, 0) |
1030 | movingSpy.target = null; |
1031 | @@ -229,6 +261,10 @@ |
1032 | compare(defaults.action, null, "No action by default."); |
1033 | compare(defaults.style, null, "Style is loaded upon first use."); |
1034 | compare(defaults.__styleInstance, null, "__styleInstance must be null."); |
1035 | + compare(defaults.selected, false, "Not selected by default"); |
1036 | + compare(defaults.selectMode, false, "Not selectable by default"); |
1037 | + compare(testColumn.ViewItems.selectMode, false, "The parent attached property is not selectable by default"); |
1038 | + compare(testColumn.ViewItems.selectedIndices.length, 0, "No item is selected by default"); |
1039 | |
1040 | compare(actionsDefault.delegate, null, "ListItemActions has no delegate set by default."); |
1041 | compare(actionsDefault.actions.length, 0, "ListItemActions has no actions set."); |
1042 | @@ -277,7 +313,7 @@ |
1043 | clickSpy.target = item; |
1044 | clickSpy.clear(); |
1045 | movingSpy.target = item; |
1046 | - flick(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0); |
1047 | + swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0); |
1048 | movingSpy.wait(); |
1049 | |
1050 | // click over the contentItem |
1051 | @@ -292,7 +328,7 @@ |
1052 | pressAndHoldSpy.target = item; |
1053 | pressAndHoldSpy.clear(); |
1054 | movingSpy.target = item; |
1055 | - flick(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0); |
1056 | + swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0); |
1057 | movingSpy.wait(); |
1058 | |
1059 | // press and hold |
1060 | @@ -363,7 +399,7 @@ |
1061 | listView.positionViewAtBeginning(); |
1062 | movingSpy.target = data.item; |
1063 | if (data.mouse) { |
1064 | - flick(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1065 | + swipe(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1066 | } else { |
1067 | TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0)); |
1068 | } |
1069 | @@ -392,7 +428,7 @@ |
1070 | listView.positionViewAtBeginning(); |
1071 | movingSpy.target = data.item; |
1072 | if (data.mouse) { |
1073 | - flick(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1074 | + swipe(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1075 | } else { |
1076 | TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0)); |
1077 | } |
1078 | @@ -419,7 +455,7 @@ |
1079 | movingSpy.target = data.item; |
1080 | interactiveSpy.target = listView; |
1081 | if (data.mouse) { |
1082 | - flick(data.item, data.pos.x, data.pos.y, data.dx, data.dy); |
1083 | + swipe(data.item, data.pos.x, data.pos.y, data.dx, data.dy); |
1084 | } else { |
1085 | TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, data.dy)); |
1086 | } |
1087 | @@ -444,7 +480,7 @@ |
1088 | } |
1089 | function test_visualized_actions(data) { |
1090 | movingSpy.target = data.item; |
1091 | - flick(data.item, centerOf(data.item).x, centerOf(data.item).y, data.leading ? units.gu(20) : -units.gu(20), 0); |
1092 | + swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.leading ? units.gu(20) : -units.gu(20), 0); |
1093 | movingSpy.wait(); |
1094 | |
1095 | // check if the action is visible |
1096 | @@ -468,7 +504,7 @@ |
1097 | function test_listitem_margins(data) { |
1098 | data.item.contentItem.anchors.margins = units.gu(1); |
1099 | movingSpy.target = data.item; |
1100 | - flick(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0); |
1101 | + swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0); |
1102 | movingSpy.wait(); |
1103 | var panel = panelItem(data.item, data.leading); |
1104 | verify(panel && panel.visible, "Panel not visible."); |
1105 | @@ -489,7 +525,7 @@ |
1106 | listView.positionViewAtBeginning(); |
1107 | movingSpy.target = data.item; |
1108 | if (data.mouse) { |
1109 | - flick(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1110 | + swipe(data.item, data.pos.x, data.pos.y, data.dx, 0); |
1111 | } else { |
1112 | TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0)); |
1113 | } |
1114 | @@ -516,7 +552,7 @@ |
1115 | listView.positionViewAtBeginning(); |
1116 | var item = findChild(listView, "listItem0"); |
1117 | movingSpy.target = item; |
1118 | - flick(item, centerOf(item).x, centerOf(item).y, -units.gu(20), 0); |
1119 | + swipe(item, centerOf(item).x, centerOf(item).y, -units.gu(20), 0); |
1120 | var panel = panelItem(item, false); |
1121 | verify(panel, "Panel is not visible"); |
1122 | var custom = findChild(panel, "custom_delegate"); |
1123 | @@ -534,16 +570,16 @@ |
1124 | return [ |
1125 | // the list snaps out if the panel is dragged in > overshoot GU (hardcoded for now) |
1126 | {tag: "Snap out leading", item: listItem, dx: units.gu(2), snapIn: false}, |
1127 | - {tag: "Snap in leading", item: listItem, dx: units.gu(4), snapIn: true}, |
1128 | + {tag: "Snap in leading", item: listItem, dx: units.gu(6), snapIn: true}, |
1129 | {tag: "Snap out trailing", item: listItem, dx: -units.gu(2), snapIn: false}, |
1130 | - {tag: "Snap in trailing", item: listItem, dx: -units.gu(4), snapIn: true}, |
1131 | + {tag: "Snap in trailing", item: listItem, dx: -units.gu(6), snapIn: true}, |
1132 | ]; |
1133 | } |
1134 | function test_snap(data) { |
1135 | movingSpy.target = data.item; |
1136 | - flick(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0); |
1137 | + swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0); |
1138 | movingSpy.wait(); |
1139 | - waitForRendering(data.item, 400); |
1140 | + waitForRendering(data.item.contentItem, 400); |
1141 | movingSpy.clear(); |
1142 | if (data.snapIn) { |
1143 | verify(data.item.contentItem.x != 0.0, "Not snapped to be visible"); |
1144 | @@ -595,8 +631,6 @@ |
1145 | listView.positionViewAtBeginning(); |
1146 | var item0 = findChild(listView, "listItem0"); |
1147 | var item1 = findChild(listView, "listItem1"); |
1148 | - var item2 = findChild(listView, "listItem2"); |
1149 | - var item3 = findChild(listView, "listItem3"); |
1150 | return [ |
1151 | // testItem is the child item @index 3 in the topmost Column. |
1152 | {tag: "Standalone item, child index 3", item: testItem, result: 3}, |
1153 | @@ -607,8 +641,9 @@ |
1154 | function test_verify_action_value(data) { |
1155 | // tug actions in |
1156 | movingSpy.target = data.item; |
1157 | - flick(data.item, centerOf(data.item).x, centerOf(data.item).y, units.gu(20), 0); |
1158 | + swipe(data.item, 1, centerOf(data.item).y, units.gu(40), 0); |
1159 | movingSpy.wait(); |
1160 | + wait(2000); |
1161 | verify(data.item.contentItem.x != data.item.contentItem.anchors.leftMargin, "Not snapped in"); |
1162 | |
1163 | var panel = panelItem(data.item, "Leading"); |
1164 | @@ -709,7 +744,7 @@ |
1165 | interactiveSpy.target = testFlickable; |
1166 | movingSpy.target = listItem; |
1167 | // tug leading |
1168 | - flick(listItem, centerOf(listItem).x, centerOf(listItem).y, listItem.width / 2, 0); |
1169 | + swipe(listItem, centerOf(listItem).x, centerOf(listItem).y, listItem.width / 2, 0); |
1170 | movingSpy.wait(); |
1171 | // check if interactive got changed |
1172 | interactiveSpy.wait(); |
1173 | @@ -743,5 +778,141 @@ |
1174 | compare(clickSpy.count, 0, "Click must be suppressed."); |
1175 | compare(actionSpy.count, 0, "Action triggered must be suppressed"); |
1176 | } |
1177 | + |
1178 | + function test_select_indices_updates_selected_items() { |
1179 | + listView.ViewItems.selectedIndices = [0,1,2]; |
1180 | + listView.ViewItems.selectMode = true; |
1181 | + waitForRendering(listView, 500); |
1182 | + for (var i in listView.ViewItems.selectedIndices) { |
1183 | + var index = listView.ViewItems.selectedIndices[i]; |
1184 | + var listItem = findChild(listView, "listItem" + index); |
1185 | + compare(listItem.selected, true, "ListItem at index " + index + " is not selected!"); |
1186 | + } |
1187 | + listView.ViewItems.selectMode = false; |
1188 | + listView.ViewItems.selectedIndices = []; |
1189 | + waitForRendering(listView, 500); |
1190 | + } |
1191 | + |
1192 | + function test_toggle_selectMode_data() { |
1193 | + return [ |
1194 | + {tag: "When not selected", index: 0, selected: false}, |
1195 | + {tag: "When selected", index: 0, selected: true}, |
1196 | + ] |
1197 | + } |
1198 | + function test_toggle_selectMode(data) { |
1199 | + var listItem = findChild(listView, "listItem" + data.index) |
1200 | + verify(listItem, "Cannot get test item"); |
1201 | + listItem.selected = data.selected; |
1202 | + listView.ViewItems.selectMode = true; |
1203 | + // wait few milliseconds |
1204 | + wait(400); |
1205 | + // testItem is the 4th child, so index is 3 |
1206 | + verify(findChild(listItem, "selection_panel" + data.index), "Cannot find selection panel"); |
1207 | + compare(listItem.contentItem.enabled, true, "contentItem is not disabled."); |
1208 | + } |
1209 | + |
1210 | + SignalSpy { |
1211 | + id: selectedSpy |
1212 | + signalName: "selectedChanged" |
1213 | + } |
1214 | + |
1215 | + function test_toggle_selected_data() { |
1216 | + return [ |
1217 | + // item = <test-item>, clickOk: <item-to-click-on>, offsetX|Y: <clickOn offset clicked> |
1218 | + {tag: "Click over selection", selectableHolder: testColumn, item: controlItem, clickOn: "listitem_select", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: false}, |
1219 | + {tag: "Click over contentItem", selectableHolder: testColumn, item: controlItem, clickOn: "ListItemHolder", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: true}, |
1220 | + {tag: "Click over control", selectableHolder: testColumn, item: controlItem, clickOn: "button_in_list", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: true}, |
1221 | + ]; |
1222 | + } |
1223 | + function test_toggle_selected(data) { |
1224 | + // make test item selectable first, so the panel is created |
1225 | + data.selectableHolder.ViewItems.selectMode = true; |
1226 | + wait(400); |
1227 | + // get the control to click on |
1228 | + var clickOn = findChild(data.item, data.clickOn); |
1229 | + verify(clickOn, "control to be clicked on not found"); |
1230 | + // click on the selection and check selected changed |
1231 | + selectedSpy.target = data.item; |
1232 | + selectedSpy.clear(); |
1233 | + mouseClick(clickOn, data.offsetX, data.offsetY); |
1234 | + if (data.xfail) { |
1235 | + expectFail(data.tag, "Clicking anywhere else but selection panel should not toggle selection state!"); |
1236 | + } |
1237 | + selectedSpy.wait(); |
1238 | + } |
1239 | + |
1240 | + SignalSpy { |
1241 | + id: selectedIndicesSpy |
1242 | + signalName: "selectedIndicesChanged" |
1243 | + target: listView.ViewItems |
1244 | + } |
1245 | + |
1246 | + function test_selectedIndices_change() { |
1247 | + // move to the end of the view |
1248 | + listView.positionViewAtEnd(); |
1249 | + var listItem = findChild(listView, "listItem" + (listView.count - 1)); |
1250 | + verify(listItem, "Cannot get tested list item"); |
1251 | + listView.ViewItems.selectMode = true; |
1252 | + waitForRendering(listItem); |
1253 | + selectedSpy.target = listItem; |
1254 | + selectedSpy.clear(); |
1255 | + |
1256 | + listItem.selected = true; |
1257 | + selectedSpy.wait(); |
1258 | + selectedIndicesSpy.wait(); |
1259 | + } |
1260 | + |
1261 | + function test_no_tug_when_selectable() { |
1262 | + movingSpy.target = testItem; |
1263 | + testColumn.ViewItems.selectMode = true; |
1264 | + // wait till animation to selection mode ends |
1265 | + waitForRendering(testItem.contentItem); |
1266 | + |
1267 | + // try to tug leading |
1268 | + movingSpy.clear(); |
1269 | + swipe(testItem, centerOf(testItem).x, centerOf(testItem).y, units.gu(10), 0); |
1270 | + compare(movingSpy.count, 0, "No tug allowed when in selection mode"); |
1271 | + } |
1272 | + |
1273 | + function test_selectable_and_click() { |
1274 | + testColumn.ViewItems.selectMode = true; |
1275 | + // wait till animation to selection mode ends |
1276 | + waitForRendering(testItem.contentItem); |
1277 | + |
1278 | + clickSpy.target = testItem; |
1279 | + mouseClick(testItem, centerOf(testItem).x, centerOf(testItem).y); |
1280 | + clickSpy.wait(); |
1281 | + } |
1282 | + |
1283 | + function test_selectable_and_pressandhold() { |
1284 | + testColumn.ViewItems.selectMode = true; |
1285 | + // wait till animation to selection mode ends |
1286 | + waitForRendering(testItem.contentItem); |
1287 | + |
1288 | + pressAndHoldSpy.target = testItem; |
1289 | + mouseLongPress(testItem, centerOf(testItem).x, centerOf(testItem).y); |
1290 | + mouseRelease(testItem, centerOf(testItem).x, centerOf(testItem).y); |
1291 | + pressAndHoldSpy.wait(); |
1292 | + } |
1293 | + |
1294 | + function test_proper_attached_properties_data() { |
1295 | + return [ |
1296 | + {tag: "Attached to ListView", item: listView}, |
1297 | + {tag: "Attached to Column in Flickable", item: column}, |
1298 | + ]; |
1299 | + } |
1300 | + function test_proper_attached_properties(data) { |
1301 | + var listItem = findChild(data.item, "listItem0"); |
1302 | + verify(listItem, "ListItem not found!"); |
1303 | + data.item.ViewItems.selectMode = true; |
1304 | + // wait few milliseconds to get the selection panel opened |
1305 | + wait(400); |
1306 | + // check if the selection mode was activated by looking after the first selection panel |
1307 | + var panel = findChild(listItem, "selection_panel0"); |
1308 | + // turn off selection mode so we have a proper cleanup |
1309 | + data.item.ViewItems.selectMode = false; |
1310 | + wait(400); |
1311 | + verify(panel, "Selection panel not found, wrong attached property target?"); |
1312 | + } |
1313 | } |
1314 | } |
FAILED: Continuous integration, rev:1367 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1441/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1354 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/168 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/171 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/171/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/168 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 1194 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1352 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1352/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 18052
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1441/ rebuild
http://