Merge lp:~zsombi/ubuntu-ui-toolkit/80-selection-mode into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
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
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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Cris Dywan (kalikiana) wrote :

> 747 + self.wait_select_single('QQuickItem', objectName='selection_panel0')

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/listitems/ListItemTest.qml for testing a bit, 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 test_listitem.ListItemTestCase.qml has "onPressAndHold: listView.ViewItems.selectMode = true", I'm thinking this should be available in the gallery and manual test case as well.

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.

review: Needs Fixing
Revision history for this message
Zsombor Egri (zsombi) wrote :
Download full text (3.5 KiB)

> > 747 + self.wait_select_single('QQuickItem', objectName='selection_panel0')
>
> 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/listitems/ListItemTest.qml for testing a bit,
> 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.ListItemTestCase.qml has "onPressAndHold:
> listView.ViewItems.selectMode = true", I'm thinking this should be available
> 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...

Read more...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Cris Dywan (kalikiana) wrote :

So… "selectable" is now available… I expected ViewItems.selectMode to work on 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.

review: Needs Information
Revision history for this message
Zsombor Egri (zsombi) wrote :

> So… "selectable" is now available… I expected ViewItems.selectMode to work on
> 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.selectMode to work on ListItem would mean you would have to have it attachable to the ListItem, right? That is equivalent on having the selectable property available, which we have now. Unless you meant to be "available" through its parent, in which case that is doable by having the "if (ListView.view != null) { ListView.view.ViewItems.selectMode } else { parent.ViewItems.selectMode }" setup.

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.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1378. By Zsombor Egri

documentation fixed

1379. By Zsombor Egri

staging merge

Revision history for this message
Cris Dywan (kalikiana) wrote :

Looking good!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches