Merge lp:~zsombi/ubuntu-ui-toolkit/listitem_expansion into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Cris Dywan
Approved revision: 1641
Merged at revision: 1627
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/listitem_expansion
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/listItemHandleUnacceptedMouseEvent
Diff against target: 2757 lines (+2288/-49)
21 files modified
components.api (+13/-0)
src/Ubuntu/Components/Themes/Ambiance/1.2/ListItemStyle.qml (+14/-1)
src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml (+65/-0)
src/Ubuntu/Components/plugin/plugin.cpp (+2/-0)
src/Ubuntu/Components/plugin/plugin.pri (+1/-0)
src/Ubuntu/Components/plugin/privates/listitemdragarea.cpp (+2/-3)
src/Ubuntu/Components/plugin/privates/listitemdragarea.h (+1/-1)
src/Ubuntu/Components/plugin/privates/listitemexpansion.cpp (+106/-0)
src/Ubuntu/Components/plugin/uclistitem.cpp (+128/-6)
src/Ubuntu/Components/plugin/uclistitem.h (+76/-1)
src/Ubuntu/Components/plugin/uclistitem_p.h (+14/-5)
src/Ubuntu/Components/plugin/uclistitemstyle.cpp (+38/-9)
src/Ubuntu/Components/plugin/uclistitemstyle.h (+6/-0)
src/Ubuntu/Components/plugin/ucviewitemsattached.cpp (+141/-1)
tests/resources/listitems/ListItemExpansion.qml (+121/-0)
tests/unit_x11/tst_components/ListItemTestCase.qml (+5/-0)
tests/unit_x11/tst_components/ListItemTestCase13.qml (+143/-0)
tests/unit_x11/tst_components/tst_listitem.qml (+1/-21)
tests/unit_x11/tst_components/tst_listitem13.qml (+1211/-0)
tests/unit_x11/tst_components/tst_listitem_expansion.qml (+199/-0)
tests/unit_x11/tst_components/tst_listitem_extras.qml (+1/-1)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/listitem_expansion
Reviewer Review Type Date Requested Status
Cris Dywan Approve
PS Jenkins bot continuous-integration Approve
Zsombor Egri Approve
Review via email: mp+269652@code.launchpad.net

Commit message

Implement list item expansion

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
Zsombor Egri (zsombi) wrote :

Must fix tst_listitem.qml to use the proper version.

review: Needs Fixing
1632. By Zsombor Egri

separate 1.2 tests from 1.3

1633. By Zsombor Egri

1.2 fixed

1634. By Zsombor Egri

1.3 specific tests

1635. By Zsombor Egri

staging merge

1636. By Zsombor Egri

API updated

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

Tests fixed

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

commented out import breaking tests restored

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

+ state: (listItemStyle.completed && styledItem.expansion.expanded) ?
The .completed property feels a bit.. hackish - why not use Component.onCompleted in the style? Why isn't that the same? I could be missing something obvious, so if this is needed maybe it can be documented a bit.

ViewItems.LockExpanded - shouldn't this be ViewItems.UnlockExpanded to prevent one from accidentally unsetting it? For instance

ViewItems.expansionFlags: ViewItems.CollapseOnOutsidePress

looks as though it only changes one flag, and you'd probably assume the actions are still locked when expanding, when in fact they won't be because the flags were replaced. If the flag was reversed that wouldn't happen.

+ property real expandedHeight//: units.gu(12)

Leftover? Remember what your mother said about leftovers, get rid of them or it will rain tomorrow ;-)

Also, can we have something in the toolkit gallery on the new list items page?

review: Needs Fixing
1638. By Zsombor Egri

commented code removed

1639. By Zsombor Egri

LockExpanded changed into UnlockExpanded, documentation updated

1640. By Zsombor Egri

Completion handled in style; refinement on the UnlockExpanded handling; minor improvement on overridden slots

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 :

You forgot to update components.api!

review: Needs Fixing
1641. By Zsombor Egri

API updated

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

Looking good. Thanks a lot!

review: Approve

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-09-03 13:27:49 +0000
3+++ components.api 2015-09-04 10:38:07 +0000
4@@ -454,6 +454,7 @@
5 property bool selected
6 property ListItemActions trailingActions
7 Ubuntu.Components.ListItem 1.3: ListItem
8+ readonly property UCListItemExpansion expansion
9 Ubuntu.Components.ListItemActions 1.2: QtObject
10 readonly property Action actions
11 default readonly property QtObject data
12@@ -473,6 +474,7 @@
13 readonly property bool animatePanels
14 property Item dragPanel
15 property PropertyAnimation dropAnimation
16+ readonly property Flickable flickable 1.3
17 readonly property int listItemIndex 1.3
18 function swipeEvent(SwipeEvent event)
19 function rebound()
20@@ -1245,6 +1247,9 @@
21 UCListItemDivider: Item
22 property color colorFrom
23 property color colorTo
24+UCListItemExpansion: QtObject
25+ property bool expanded
26+ property double height
27 UCStateSaverAttached: QtObject
28 property bool enabled
29 property string properties
30@@ -1392,6 +1397,14 @@
31 signal dragUpdated(ListItemDrag event)
32 property bool selectMode
33 property QList<int> selectedIndices
34+Ubuntu.Components.ViewItems 1.3: ViewItems
35+ property QList<int> expandedIndices
36+ property int expansionFlags
37+ signal expandedIndicesChanged(QList<int> indices)
38+Ubuntu.Components.ViewItems.ExpansionFlag: Enum
39+ CollapseOnOutsidePress
40+ Exclusive
41+ UnlockExpanded
42 Ubuntu.Components.i18n 1.0 0.1: QtObject
43 property string domain
44 property string language
45
46=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.2/ListItemStyle.qml'
47--- src/Ubuntu/Components/Themes/Ambiance/1.2/ListItemStyle.qml 2015-07-22 18:57:58 +0000
48+++ src/Ubuntu/Components/Themes/Ambiance/1.2/ListItemStyle.qml 2015-09-04 10:38:07 +0000
49@@ -25,7 +25,7 @@
50 * Take over the ListItem's index context property as repeater used in panel
51 * overrides the property.
52 */
53- readonly property int listItemIndex: index
54+ readonly property int listItemIndex: (typeof index !== "undefined") ? index : internals.childIndex()
55
56 /*
57 * Coloring properties
58@@ -343,6 +343,19 @@
59 readonly property real threshold: units.gu(1.5)
60 property bool snapIn: false
61
62+ // returns the child index of the ListItem when not used in model driven view
63+ function childIndex() {
64+ if (styledItem.parent) {
65+ for (var i = 0; i < styledItem.parent.children.length; i++) {
66+ if (styledItem.parent.children[i] == styledItem) {
67+ return i;
68+ }
69+ }
70+ } else {
71+ return -1;
72+ }
73+ }
74+
75 // update snap direction
76 function updateSnapDirection() {
77 if (prevX < listItemStyle.x && (snapChangerLimit <= listItemStyle.x)) {
78
79=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml'
80--- src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2015-07-22 18:57:58 +0000
81+++ src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2015-09-04 10:38:07 +0000
82@@ -337,6 +337,7 @@
83 property real snapChangerLimit: 0.0
84 readonly property real threshold: units.gu(1.5)
85 property bool snapIn: false
86+ property bool completed: false
87
88 // update snap direction
89 function updateSnapDirection() {
90@@ -419,4 +420,68 @@
91 function rebound() {
92 snapAnimation.snapTo(0);
93 }
94+
95+ // expansion
96+ Component.onCompleted: internals.completed = true
97+ state: (internals.completed && styledItem.expansion.expanded) ? (listItemStyle.flickable ? "expandedWithFlickable" : "expandedNoFlickable") : ""
98+ states: [
99+ State {
100+ name: "expandedNoFlickable"
101+ PropertyChanges {
102+ target: styledItem
103+ height: styledItem.expansion.height
104+ }
105+ },
106+ State {
107+ name: "expandedWithFlickable"
108+ PropertyChanges {
109+ target: styledItem
110+ height: styledItem.expansion.height
111+ }
112+ PropertyChanges {
113+ target: listItemStyle.flickable
114+ // we do not need to restore the original values
115+ restoreEntryValues: false
116+ // and we should not get any binding updates even
117+ explicit: true
118+ contentY: {
119+ var bottom = styledItem.y + styledItem.expansion.height - listItemStyle.flickable.contentY + listItemStyle.flickable.originY;
120+ var dy = bottom - listItemStyle.flickable.height;
121+ if (dy > 0) {
122+ return listItemStyle.flickable.contentY + dy - listItemStyle.flickable.originY;
123+ } else {
124+ return listItemStyle.flickable.contentY;
125+ }
126+ }
127+ }
128+ }
129+ ]
130+ transitions: [
131+ Transition {
132+ from: ""
133+ to: "expandedWithFlickable"
134+ reversible: true
135+ enabled: listItemStyle.animatePanels
136+ ParallelAnimation {
137+ UbuntuNumberAnimation {
138+ target: listItemStyle.flickable
139+ property: "contentY"
140+ }
141+ UbuntuNumberAnimation {
142+ target: styledItem
143+ property: "height"
144+ }
145+ }
146+ },
147+ Transition {
148+ from: ""
149+ to: "expandedNoFlickable"
150+ reversible: true
151+ enabled: listItemStyle.animatePanels
152+ UbuntuNumberAnimation {
153+ target: styledItem
154+ property: "height"
155+ }
156+ }
157+ ]
158 }
159
160=== modified file 'src/Ubuntu/Components/plugin/plugin.cpp'
161--- src/Ubuntu/Components/plugin/plugin.cpp 2015-08-24 16:02:50 +0000
162+++ src/Ubuntu/Components/plugin/plugin.cpp 2015-09-04 10:38:07 +0000
163@@ -224,6 +224,8 @@
164
165 // register 1.3 API
166 qmlRegisterType<UCListItem13>(uri, 1, 3, "ListItem");
167+ qmlRegisterType<UCListItemExpansion>();
168+ qmlRegisterUncreatableType<UCViewItemsAttached13>(uri, 1, 3, "ViewItems", "No create");
169 qmlRegisterType<UCTheme>(uri, 1, 3, "ThemeSettings");
170 qmlRegisterType<UCStyledItemBase, 2>(uri, 1, 3, "StyledItem");
171 qmlRegisterSingletonType<UCNamespaceV13>(uri, 1, 3, "Ubuntu", registerUbuntuNamespace13);
172
173=== modified file 'src/Ubuntu/Components/plugin/plugin.pri'
174--- src/Ubuntu/Components/plugin/plugin.pri 2015-08-20 15:34:26 +0000
175+++ src/Ubuntu/Components/plugin/plugin.pri 2015-09-04 10:38:07 +0000
176@@ -130,6 +130,7 @@
177 $$PWD/ucserviceproperties.cpp \
178 $$PWD/privates/listitemdragarea.cpp \
179 $$PWD/privates/listitemdraghandler.cpp \
180+ $$PWD/privates/listitemexpansion.cpp \
181 $$PWD/ucnamespace.cpp \
182 $$PWD/ucdeprecatedtheme.cpp \
183 $$PWD/ucdefaulttheme.cpp \
184
185=== modified file 'src/Ubuntu/Components/plugin/privates/listitemdragarea.cpp'
186--- src/Ubuntu/Components/plugin/privates/listitemdragarea.cpp 2015-04-13 11:11:59 +0000
187+++ src/Ubuntu/Components/plugin/privates/listitemdragarea.cpp 2015-09-04 10:38:07 +0000
188@@ -47,13 +47,12 @@
189 setObjectName("drag_area");
190 }
191
192-void ListItemDragArea::init()
193+void ListItemDragArea::init(UCViewItemsAttached *viewItems)
194 {
195 setParentItem(static_cast<QQuickItem*>(parent()));
196 QQuickAnchors *anchors = QQuickItemPrivate::get(this)->anchors();
197 anchors->setFill(parentItem());
198- viewAttached = static_cast<UCViewItemsAttached*>(
199- qmlAttachedPropertiesObject<UCViewItemsAttached>(listView));
200+ viewAttached = viewItems;
201 reset();
202 }
203
204
205=== modified file 'src/Ubuntu/Components/plugin/privates/listitemdragarea.h'
206--- src/Ubuntu/Components/plugin/privates/listitemdragarea.h 2015-02-24 15:57:53 +0000
207+++ src/Ubuntu/Components/plugin/privates/listitemdragarea.h 2015-09-04 10:38:07 +0000
208@@ -27,7 +27,7 @@
209 Q_OBJECT
210 public:
211 explicit ListItemDragArea(QQuickItem *parent = 0);
212- void init();
213+ void init(UCViewItemsAttached *viewItems);
214 void reset();
215
216 protected:
217
218=== added file 'src/Ubuntu/Components/plugin/privates/listitemexpansion.cpp'
219--- src/Ubuntu/Components/plugin/privates/listitemexpansion.cpp 1970-01-01 00:00:00 +0000
220+++ src/Ubuntu/Components/plugin/privates/listitemexpansion.cpp 2015-09-04 10:38:07 +0000
221@@ -0,0 +1,106 @@
222+/*
223+ * Copyright 2015 Canonical Ltd.
224+ *
225+ * This program is free software; you can redistribute it and/or modify
226+ * it under the terms of the GNU Lesser General Public License as published by
227+ * the Free Software Foundation; version 3.
228+ *
229+ * This program is distributed in the hope that it will be useful,
230+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
231+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
232+ * GNU Lesser General Public License for more details.
233+ *
234+ * You should have received a copy of the GNU Lesser General Public License
235+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
236+ */
237+
238+#include "uclistitem.h"
239+#include "uclistitem_p.h"
240+
241+UCListItemExpansion::UCListItemExpansion(QObject *parent)
242+ : QObject(parent)
243+ , m_listItem(static_cast<UCListItem13*>(parent))
244+ , m_height(0.0)
245+ , m_filtering(false)
246+{
247+}
248+
249+bool UCListItemExpansion::expandedLocked()
250+{
251+ UCListItemPrivate *listItem = UCListItemPrivate::get(m_listItem);
252+ UCViewItemsAttachedPrivate *viewItems = UCViewItemsAttachedPrivate::get(listItem->parentAttached);
253+ return expanded() && !((viewItems->expansionFlags & UCViewItemsAttached::UnlockExpanded) == UCViewItemsAttached::UnlockExpanded);
254+}
255+
256+void UCListItemExpansion::enableClickFiltering(bool enable)
257+{
258+ if (m_filtering == enable) {
259+ return;
260+ }
261+ m_filtering = enable;
262+ if (m_filtering) {
263+ m_listItem->window()->installEventFilter(this);
264+ } else {
265+ m_listItem->window()->removeEventFilter(this);
266+ }
267+}
268+
269+// event filter for external mouse presses to collapse when pressed outside
270+bool UCListItemExpansion::eventFilter(QObject *target, QEvent *event)
271+{
272+ if (event->type() == QEvent::MouseButtonPress) {
273+ QMouseEvent *mouse = static_cast<QMouseEvent*>(event);
274+ QQuickWindow *window = qobject_cast<QQuickWindow*>(target);
275+ if (window) {
276+ QPointF myPos(window->contentItem()->mapToItem(m_listItem, mouse->localPos()));
277+ if (!m_listItem->contains(myPos)) {
278+ UCListItemPrivate *listItem = UCListItemPrivate::get(m_listItem);
279+ UCViewItemsAttachedPrivate *viewItems = UCViewItemsAttachedPrivate::get(listItem->parentAttached);
280+ if (viewItems) {
281+ // collapse all as there can be only one expanded when the flag is set
282+ viewItems->collapseAll();
283+ }
284+ }
285+ }
286+ }
287+ return false;
288+}
289+
290+
291+bool UCListItemExpansion::expanded()
292+{
293+ UCListItemPrivate *listItem = UCListItemPrivate::get(m_listItem);
294+ UCViewItemsAttachedPrivate *viewItems = UCViewItemsAttachedPrivate::get(listItem->parentAttached);
295+ return (viewItems && viewItems->expansionList.contains(listItem->index()));
296+}
297+
298+void UCListItemExpansion::setExpanded(bool expanded)
299+{
300+ if (this->expanded() == expanded) {
301+ return;
302+ }
303+ UCListItemPrivate *listItem = UCListItemPrivate::get(m_listItem);
304+ UCViewItemsAttachedPrivate *viewItems = UCViewItemsAttachedPrivate::get(listItem->parentAttached);
305+ if (viewItems) {
306+ if (viewItems->expansionFlags & UCViewItemsAttached::Exclusive) {
307+ // collapse all the expanded ones
308+ viewItems->collapseAll();
309+ }
310+ if (expanded) {
311+ viewItems->expand(listItem->index(), m_listItem);
312+ } else {
313+ viewItems->collapse(listItem->index());
314+ }
315+ }
316+ UCListItemPrivate::get(m_listItem)->loadStyleItem();
317+ // no need to emit changed signal, as ViewItems.expandedIndicesChanged is connected to the change signal
318+}
319+
320+void UCListItemExpansion::setHeight(qreal height)
321+{
322+ if (m_height == height) {
323+ return;
324+ }
325+ m_height = height;
326+ Q_EMIT heightChanged();
327+}
328
329=== modified file 'src/Ubuntu/Components/plugin/uclistitem.cpp'
330--- src/Ubuntu/Components/plugin/uclistitem.cpp 2015-08-31 07:50:45 +0000
331+++ src/Ubuntu/Components/plugin/uclistitem.cpp 2015-09-04 10:38:07 +0000
332@@ -200,6 +200,7 @@
333 , leadingActions(0)
334 , trailingActions(0)
335 , mainAction(0)
336+ , expansion(Q_NULLPTR)
337 {
338 }
339 UCListItemPrivate::~UCListItemPrivate()
340@@ -325,7 +326,7 @@
341 bool UCListItemPrivate::loadStyleItem(bool animated)
342 {
343 // the style should be loaded only if one of the condition is satisfied
344- if (!swiped && !selectMode() && !dragMode()) {
345+ if (!swiped && !selectMode() && !dragMode() && !(expansion && expansion->expanded())) {
346 return false;
347 }
348
349@@ -339,6 +340,7 @@
350 preStyleChanged();
351 return false;
352 }
353+ myStyle->updateFlickable(flickable);
354 // bring the panels foreground
355 styleItem->setZ(0);
356 listItemStyle()->setAnimatePanels(true);
357@@ -866,8 +868,74 @@
358 *
359 * \sa ViewItems::dragMode, ViewItems::dragUpdated
360 *
361+ * \section2 Expansion
362+ * Since Ubuntu.Components 1.3, ListItem supports expansion. ListItems declared
363+ * in a view can expand exclusively, having leading and trailing panes locked
364+ * when expanded and to be collapsed when tapping outside of the expanded area.
365+ * The expansion is driven by the \l expansion group property, and the behavior
366+ * by the \l ViewItems::expansionFlags and \l ViewItems::expandedIndices
367+ * attached properties. Each ListItem which is required to expand should set a
368+ * proper height in the \l expansion.height property, which should be bigger
369+ * than the collapsed height of the ListItem is. The expansion itself is driven
370+ * by the \l expansion.expanded property, which can be set freely depending on
371+ * the use case, on click, on long press, etc.
372+ *
373+ * The default expansion behavior is set to be exclusive and locked, meaning
374+ * there can be only one ListItem expanded within a view and neither leading
375+ * nor trailing action panels cannot be swiped in. Expanding an other ListItem
376+ * will collapse the previosuly expanded one. There can be cases when tapping
377+ * outside of the expanded area of a ListItem we woudl need the expanded one
378+ * to collapse automatically. This can be achieved by setting \c ViewItems.CollapseOnOutsidePress
379+ * flag to \l ViewItems::expansionFlags. This flag will also turn on \c ViewItems.Exclusive
380+ * flag, as tapping outside practicly forbids more than one item to be expanded
381+ * at a time.
382+ * \qml
383+ * import QtQuick 2.4
384+ * import Ubuntu.Components 1.3
385+ *
386+ * ListView {
387+ * width: units.gu(40)
388+ * height: units.gu(71)
389+ * model: ListModel {
390+ * Component.onCompleted: {
391+ * for (var i = 0; i < 50; i++) {
392+ * append({data: i});
393+ * }
394+ * }
395+ * }
396+ * ViewItems.expansionFlags: ViewItems.CollapseOnOutsidePress
397+ * delegate: ListItem {
398+ * Label {
399+ * text: "Model item #" + modelData
400+ * }
401+ * trailingActions: ListItemActions {
402+ * actions: [
403+ * Action {
404+ * icon: "search"
405+ * },
406+ * Action {
407+ * icon: "edit"
408+ * },
409+ * Action {
410+ * icon: "copy"
411+ * }
412+ * ]
413+ * }
414+ * expansion.height: units.gu(15)
415+ * onClicked: expansion.expanded = true
416+ * }
417+ * }
418+ * \endqml
419+ * The example above collapses the expanded item whenever it is tapped or mouse
420+ * pressed outside of the expanded list item.
421+ * \note Set 0 to \l ViewItems::expansionFlags if no restrictions on expanded items
422+ * is required (i.e multiple expanded items are allowed, swiping leading/trailing
423+ * actions when expanded).
424+ * \note Do not bind \l expansion.height to the ListItem's height as is will cause
425+ * binding loops.
426+ *
427 * \section2 Note on styling
428- * ListItem's styling differs from the other component sstyling, as ListItem loads
429+ * ListItem's styling differs from the other components styling, as ListItem loads
430 * the style only when either of the leadin/trailing panels are swiped, or when the
431 * item enters in select- or drag mode. The component does not assume any visuals
432 * to be present in the style.
433@@ -904,6 +972,11 @@
434 {
435 }
436
437+QObject *UCListItem::attachedViewItems(QObject *object, bool create)
438+{
439+ return qmlAttachedPropertiesObject<UCViewItemsAttached>(object, create);
440+}
441+
442 void UCListItem::classBegin()
443 {
444 UCStyledItemBase::classBegin();
445@@ -956,7 +1029,7 @@
446 this, SLOT(_q_syncDragMode()));
447
448 // if selection or drag mode is on, initialize style, with animations turned off
449- if (d->parentAttached->selectMode() || d->parentAttached->dragMode()) {
450+ if (d->parentAttached->selectMode() || d->parentAttached->dragMode() || (d->expansion && d->expansion->expanded())) {
451 d->loadStyleItem(false);
452 }
453 // set the object name for testing purposes
454@@ -986,10 +1059,10 @@
455 QQuickItem *parentAttachee = data.item;
456 if (d->flickable && d->flickable->inherits("QQuickListView")) {
457 // attach to ListView
458- d->parentAttached = static_cast<UCViewItemsAttached*>(qmlAttachedPropertiesObject<UCViewItemsAttached>(d->flickable));
459+ d->parentAttached = static_cast<UCViewItemsAttached*>(attachedViewItems(d->flickable, true));
460 parentAttachee = d->flickable;
461 } else if (data.item) {
462- d->parentAttached = static_cast<UCViewItemsAttached*>(qmlAttachedPropertiesObject<UCViewItemsAttached>(data.item));
463+ d->parentAttached = static_cast<UCViewItemsAttached*>(attachedViewItems(data.item, true));
464 } else {
465 // mark as not ready, so no action should be performed which depends on readyness
466 d->ready = false;
467@@ -997,6 +1070,11 @@
468 d->parentAttached = 0;
469 }
470
471+ if (d->styleItem) {
472+ UCListItemStyle * myStyle = static_cast<UCListItemStyle*>(d->styleItem);
473+ myStyle->updateFlickable(d->flickable);
474+ }
475+
476 if (parentAttachee) {
477 QObject::connect(parentAttachee, SIGNAL(widthChanged()), this, SLOT(_q_updateSize()), Qt::DirectConnection);
478 // update size
479@@ -1184,7 +1262,7 @@
480 Q_D(UCListItem);
481 UCStyledItemBase::mouseMoveEvent(event);
482
483- if (d->selectMode() || d->dragMode()) {
484+ if (d->selectMode() || d->dragMode() || (d->expansion && d->expansion->expandedLocked())) {
485 // no move is allowed while selectable mode is on
486 return;
487 }
488@@ -1662,4 +1740,48 @@
489 d->defaultThemeVersion = BUILD_VERSION(1, 3);
490 }
491
492+QObject *UCListItem13::attachedViewItems(QObject *object, bool create)
493+{
494+ return qmlAttachedPropertiesObject<UCViewItemsAttached13>(object, create);
495+}
496+
497+void UCListItem13::itemChange(ItemChange change, const ItemChangeData &data)
498+{
499+ UCListItem::itemChange(change, data);
500+
501+ Q_D(UCListItem);
502+ // ViewItems drives expansion
503+ if (d->parentAttached) {
504+ connect(d->parentAttached.data(), SIGNAL(expandedIndicesChanged(QList<int>)),
505+ this, SLOT(_q_updateExpansion(QList<int>)), Qt::UniqueConnection);
506+ }
507+}
508+
509+/*!
510+ * \qmlpropertygroup ::ListItem::expansion
511+ * \qmlproperty bool ListItem::expansion.expanded
512+ * \qmlproperty real ListItem::expansion.height
513+ * \since Ubuntu.Components 1.3
514+ * The group drefines the expansion state of the ListItem.
515+ */
516+UCListItemExpansion *UCListItem13::expansion()
517+{
518+ Q_D(UCListItem);
519+ if (!d->expansion) {
520+ d->expansion = new UCListItemExpansion(this);
521+ }
522+ return d->expansion;
523+}
524+
525+void UCListItem13::_q_updateExpansion(const QList<int> &indices)
526+{
527+ Q_UNUSED(indices);
528+ Q_D(UCListItem);
529+ Q_EMIT expansion()->expandedChanged();
530+ // make sure the style is loaded
531+ if (indices.contains(d->index())) {
532+ d->loadStyleItem();
533+ }
534+}
535+
536 #include "moc_uclistitem.cpp"
537
538=== modified file 'src/Ubuntu/Components/plugin/uclistitem.h'
539--- src/Ubuntu/Components/plugin/uclistitem.h 2015-07-28 19:29:19 +0000
540+++ src/Ubuntu/Components/plugin/uclistitem.h 2015-09-04 10:38:07 +0000
541@@ -62,6 +62,7 @@
542 void resetHighlightColor();
543
544 protected:
545+ virtual QObject *attachedViewItems(QObject *object, bool create);
546 void classBegin();
547 void componentComplete();
548 QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data);
549@@ -109,17 +110,24 @@
550 Q_PRIVATE_SLOT(d_func(), void _q_syncDragMode())
551 };
552
553+class UCListItemExpansion;
554 class UCListItem13 : public UCListItem
555 {
556 Q_OBJECT
557+ Q_PROPERTY(UCListItemExpansion* expansion READ expansion CONSTANT)
558 protected:
559+ virtual QObject *attachedViewItems(QObject *object, bool create);
560+ void itemChange(ItemChange change, const ItemChangeData &data);
561 void mousePressEvent(QMouseEvent *event);
562 void mouseReleaseEvent(QMouseEvent *event);
563 private:
564+ Q_SLOT void _q_updateExpansion(const QList<int> &indices);
565 bool shouldShowContextMenu(QMouseEvent *event);
566 void popoverClosed();
567 public:
568 explicit UCListItem13(QQuickItem *parent = 0);
569+
570+ UCListItemExpansion *expansion();
571 };
572
573 class UCListItemDividerPrivate;
574@@ -158,8 +166,15 @@
575 Q_PROPERTY(bool selectMode READ selectMode WRITE setSelectMode NOTIFY selectModeChanged)
576 Q_PROPERTY(QList<int> selectedIndices READ selectedIndices WRITE setSelectedIndices NOTIFY selectedIndicesChanged)
577 Q_PROPERTY(bool dragMode READ dragMode WRITE setDragMode NOTIFY dragModeChanged)
578+ Q_ENUMS(ExpansionFlag)
579 public:
580- explicit UCViewItemsAttached(QObject *owner);
581+ enum ExpansionFlag {
582+ Exclusive = 0x01,
583+ UnlockExpanded = 0x02,
584+ CollapseOnOutsidePress = Exclusive | 0x04
585+ };
586+ Q_DECLARE_FLAGS(ExpansionFlags, ExpansionFlag)
587+ explicit UCViewItemsAttached(QObject *owner = 0);
588 ~UCViewItemsAttached();
589
590 static UCViewItemsAttached *qmlAttachedProperties(QObject *owner);
591@@ -191,8 +206,68 @@
592 private:
593 Q_DECLARE_PRIVATE(UCViewItemsAttached)
594 };
595+Q_DECLARE_OPERATORS_FOR_FLAGS(UCViewItemsAttached::ExpansionFlags)
596 QML_DECLARE_TYPEINFO(UCViewItemsAttached, QML_HAS_ATTACHED_PROPERTIES)
597
598+// FIXME keep the 1.3 properties in a separate class, workaround for bug
599+// https://bugs.launchpad.net/ubuntu/+source/qtdeclarative-opensource-src/+bug/1389721
600+// enums and flag are added to UCViewItemsAttached like normal
601+class UCViewItemsAttached13 : public UCViewItemsAttached
602+{
603+ Q_OBJECT
604+ Q_PROPERTY(QList<int> expandedIndices READ expandedIndices WRITE setExpandedIndices NOTIFY expandedIndicesChanged)
605+ Q_PROPERTY(int expansionFlags READ expansionFlags WRITE setExpansionFlags NOTIFY expansionFlagsChanged)
606+public:
607+ explicit UCViewItemsAttached13(QObject *owner = 0);
608+ static UCViewItemsAttached13 *qmlAttachedProperties(QObject *owner);
609+
610+ QList<int> expandedIndices() const;
611+ void setExpandedIndices(QList<int> indices);
612+ int expansionFlags() const;
613+ void setExpansionFlags(int flags);
614+
615+Q_SIGNALS:
616+ void expandedIndicesChanged(const QList<int> &indices);
617+ void expansionFlagsChanged();
618+
619+private:
620+ UCViewItemsAttachedPrivate *d_ptr;
621+ Q_DECLARE_PRIVATE_D(d_ptr, UCViewItemsAttached)
622+};
623+QML_DECLARE_TYPEINFO(UCViewItemsAttached13, QML_HAS_ATTACHED_PROPERTIES)
624+
625+class UCListItemExpansion : public QObject
626+{
627+ Q_OBJECT
628+ Q_PROPERTY(bool expanded READ expanded WRITE setExpanded NOTIFY expandedChanged)
629+ Q_PROPERTY(qreal height MEMBER m_height WRITE setHeight NOTIFY heightChanged)
630+public:
631+ explicit UCListItemExpansion(QObject *parent = 0);
632+
633+ bool expandedLocked();
634+ void enableClickFiltering(bool enable);
635+
636+ bool expanded();
637+ void setExpanded(bool expanded);
638+ void setHeight(qreal height);
639+
640+Q_SIGNALS:
641+ void expandedChanged();
642+ void heightChanged();
643+
644+protected:
645+ bool eventFilter(QObject *, QEvent *);
646+
647+private:
648+ UCListItem13 *m_listItem;
649+ qreal m_height;
650+ bool m_filtering:1;
651+
652+ friend class UCListItem;
653+ friend class UCListItem13;
654+ friend class UCListItemPrivate;
655+};
656+
657 class UCDragEvent : public QObject
658 {
659 Q_OBJECT
660
661=== modified file 'src/Ubuntu/Components/plugin/uclistitem_p.h'
662--- src/Ubuntu/Components/plugin/uclistitem_p.h 2015-08-31 07:50:45 +0000
663+++ src/Ubuntu/Components/plugin/uclistitem_p.h 2015-09-04 10:38:07 +0000
664@@ -95,6 +95,7 @@
665 UCListItemActions *leadingActions;
666 UCListItemActions *trailingActions;
667 UCAction *mainAction;
668+ UCListItemExpansion *expansion;
669
670 // getters/setters
671 QQmlListProperty<QObject> data();
672@@ -147,17 +148,25 @@
673 bool isDragUpdatedConnected();
674 void updateSelectedIndices(int fromIndex, int toIndex);
675
676+ // expansion
677+ void expand(int index, UCListItem13 *listItem, bool emitChangeSignal = true);
678+ void collapse(int index, bool emitChangeSignal = true);
679+ void collapseAll();
680+ void toggleExpansionFlags(bool enable);
681+
682+ QSet<int> selectedList;
683+ QMap<int, QPointer<UCListItem13> > expansionList;
684+ QList< QPointer<QQuickFlickable> > flickables;
685+ QList< PropertyChange* > changes;
686+ QPointer<UCListItem> boundItem;
687+ QPointer<UCListItem> disablerItem;
688 QQuickFlickable *listView;
689 ListItemDragArea *dragArea;
690+ UCViewItemsAttached::ExpansionFlags expansionFlags;
691 bool globalDisabled:1;
692 bool selectable:1;
693 bool draggable:1;
694 bool ready:1;
695- QSet<int> selectedList;
696- QList< QPointer<QQuickFlickable> > flickables;
697- QList< PropertyChange* > changes;
698- QPointer<UCListItem> boundItem;
699- QPointer<UCListItem> disablerItem;
700 };
701
702 #endif // UCVIEWITEM_P_H
703
704=== modified file 'src/Ubuntu/Components/plugin/uclistitemstyle.cpp'
705--- src/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-04-13 13:42:03 +0000
706+++ src/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-09-04 10:38:07 +0000
707@@ -21,6 +21,7 @@
708 #include <QtQml/QQmlContext>
709 #include <QtQml/QQmlInfo>
710 #include <QtQuick/private/qquickanimation_p.h>
711+#include <QtQuick/private/qquickflickable_p.h>
712
713 /*!
714 * \qmltype ListItemStyle
715@@ -41,6 +42,7 @@
716 , m_snapAnimation(0)
717 , m_dropAnimation(0)
718 , m_dragPanel(0)
719+ , m_flickable(Q_NULLPTR)
720 , m_animatePanels(true)
721 {
722 }
723@@ -54,21 +56,29 @@
724 setAnimatePanels(context->contextProperty("animated").toBool());
725 }
726 m_listItem = qmlContext(this)->contextProperty("styledItem").value<UCListItem*>();
727+ // get the flickable value
728+ if (m_listItem) {
729+ m_flickable = UCListItemPrivate::get(m_listItem)->flickable.data();
730+ }
731 }
732
733 void UCListItemStyle::componentComplete()
734 {
735 QQuickItem::componentComplete();
736
737- // look for overridden slots
738- for (int i = metaObject()->methodOffset(); i < metaObject()->methodCount(); i++) {
739- const QMetaMethod method = metaObject()->method(i);
740- if (method.name() == QByteArrayLiteral("swipeEvent")) {
741- m_swipeEvent = method;
742- } else if (method.name() == QByteArrayLiteral("rebound")) {
743- m_rebound = method;
744- }
745- }
746+ // look for overridden slots, indexOfMethod returns th elast index of the overridden method
747+ m_rebound = metaObject()->method(metaObject()->indexOfMethod("rebound()"));
748+ m_swipeEvent = metaObject()->method(metaObject()->indexOfMethod("swipeEvent(QVariant)"));
749+// qDebug() << m_rebound.isValid() << m_swipeEvent.isValid();
750+// for (int i = metaObject()->methodOffset(); i < metaObject()->methodCount(); i++) {
751+// const QMetaMethod method = metaObject()->method(i);
752+// if (method.name() == QByteArrayLiteral("swipeEvent")) {
753+// qDebug() << method.methodSignature();
754+// m_swipeEvent = method;
755+// } else if (method.name() == QByteArrayLiteral("rebound")) {
756+// m_rebound = method;
757+// }
758+// }
759
760 // connect snapAnimation's stopped() and the owning ListItem's contentMovementeEnded() signals
761 if (m_listItem && m_snapAnimation) {
762@@ -92,6 +102,25 @@
763 }
764
765 /*!
766+ * \qmlproperty Flickable ListItemStyle::flickable
767+ * \readonly
768+ * \since Ubuntu.Components.Styles 1.3
769+ * The property holds the Flickable (or ListView) holding the ListItem styled.
770+ */
771+QQuickFlickable *UCListItemStyle::flickable()
772+{
773+ return m_flickable;
774+}
775+void UCListItemStyle::updateFlickable(QQuickFlickable *flickable)
776+{
777+ if (m_flickable == flickable) {
778+ return;
779+ }
780+ m_flickable = flickable;
781+ Q_EMIT flickableChanged();
782+}
783+
784+/*!
785 * \qmlmethod ListItemStyle::swipeEvent(SwipeEvent event)
786 * The function is called by the ListItem when a swipe action is performed, i.e.
787 * when the swipe is started, the position is updated or the swipe ends. The
788
789=== modified file 'src/Ubuntu/Components/plugin/uclistitemstyle.h'
790--- src/Ubuntu/Components/plugin/uclistitemstyle.h 2015-04-13 13:42:03 +0000
791+++ src/Ubuntu/Components/plugin/uclistitemstyle.h 2015-09-04 10:38:07 +0000
792@@ -62,6 +62,7 @@
793 class QQuickPropertyAnimation;
794 class QQuickBehavior;
795 class UCListItem;
796+class QQuickFlickable;
797 class UCListItemStyle : public QQuickItem
798 {
799 Q_OBJECT
800@@ -70,6 +71,7 @@
801 Q_PROPERTY(bool animatePanels READ animatePanels NOTIFY animatePanelsChanged)
802 Q_PROPERTY(QQuickItem *dragPanel MEMBER m_dragPanel NOTIFY dragPanelChanged)
803 Q_PROPERTY(int listItemIndex READ index NOTIFY listItemIndexChanged FINAL REVISION 1)
804+ Q_PROPERTY(QQuickFlickable *flickable READ flickable NOTIFY flickableChanged REVISION 1)
805 public:
806 explicit UCListItemStyle(QQuickItem *parent = 0);
807
808@@ -78,6 +80,8 @@
809 bool animatePanels() const;
810 void setAnimatePanels(bool animate);
811 int index();
812+ QQuickFlickable *flickable();
813+ void updateFlickable(QQuickFlickable *flickable);
814
815 Q_SIGNALS:
816 void snapAnimationChanged();
817@@ -85,6 +89,7 @@
818 void animatePanelsChanged();
819 void dragPanelChanged();
820 Q_REVISION(1) void listItemIndexChanged();
821+ Q_REVISION(1) void flickableChanged();
822
823 public Q_SLOTS:
824 void swipeEvent(UCSwipeEvent *event);
825@@ -102,6 +107,7 @@
826 QQuickAbstractAnimation *m_snapAnimation;
827 QQuickPropertyAnimation *m_dropAnimation;
828 QQuickItem *m_dragPanel;
829+ QQuickFlickable *m_flickable;
830 bool m_animatePanels:1;
831
832 friend class UCListItemPrivate;
833
834=== modified file 'src/Ubuntu/Components/plugin/ucviewitemsattached.cpp'
835--- src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-03-17 16:48:59 +0000
836+++ src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-09-04 10:38:07 +0000
837@@ -104,6 +104,7 @@
838 : QObjectPrivate()
839 , listView(0)
840 , dragArea(0)
841+ , expansionFlags(UCViewItemsAttached::Exclusive)
842 , globalDisabled(false)
843 , selectable(false)
844 , draggable(false)
845@@ -556,7 +557,7 @@
846 return;
847 }
848 dragArea = new ListItemDragArea(listView);
849- dragArea->init();
850+ dragArea->init(q_func());
851 }
852
853 void UCViewItemsAttachedPrivate::leaveDragMode()
854@@ -611,3 +612,142 @@
855 Q_EMIT q->selectedIndicesChanged();
856 }
857 }
858+
859+
860+UCViewItemsAttached13::UCViewItemsAttached13(QObject *owner)
861+ : UCViewItemsAttached(owner)
862+{
863+ d_ptr = UCViewItemsAttachedPrivate::get(this);
864+}
865+
866+UCViewItemsAttached13 *UCViewItemsAttached13::qmlAttachedProperties(QObject *owner)
867+{
868+ return new UCViewItemsAttached13(owner);
869+}
870+
871+/*!
872+ * \qmlattachedproperty list<int> ViewItems::expandedIndices
873+ * \since Ubuntu.Components 1.3
874+ * The property contains the indexes of the ListItems marked as expanded. The
875+ * indexes are model indexes when used in ListView, and child indexes in other
876+ * components. The property being writable, initial expansion configuration
877+ * can be provided for a view, and provides ability to save the expansion state.
878+ * \note If the \l ViewItems::expansionFlags is having \c ViewItems.Exclusive
879+ * flags set, only the last item from the list will be considered and set as
880+ * expanded.
881+ */
882+QList<int> UCViewItemsAttached13::expandedIndices() const
883+{
884+ Q_D(const UCViewItemsAttached);
885+ return d->expansionList.keys();
886+}
887+void UCViewItemsAttached13::setExpandedIndices(QList<int> indices)
888+{
889+ Q_UNUSED(indices);
890+ Q_D(UCViewItemsAttached);
891+ d->collapseAll();
892+ if (indices.size() > 0) {
893+ if (d->expansionFlags & UCViewItemsAttached::Exclusive) {
894+ // take only the last one from the list
895+ d->expand(indices.last(), QPointer<UCListItem13>(), false);
896+ } else {
897+ for (int i = 0; i < indices.size(); i++) {
898+ d->expand(indices[i], QPointer<UCListItem13>(), false);
899+ }
900+ }
901+ }
902+ Q_EMIT expandedIndicesChanged(d->expansionList.keys());
903+}
904+
905+// insert listItem into the expanded indices map
906+void UCViewItemsAttachedPrivate::expand(int index, UCListItem13 *listItem, bool emitChangeSignal)
907+{
908+ expansionList.insert(index, QPointer<UCListItem13>(listItem));
909+ if (listItem && ((expansionFlags & UCViewItemsAttached::CollapseOnOutsidePress) == UCViewItemsAttached::CollapseOnOutsidePress)) {
910+ listItem->expansion()->enableClickFiltering(true);
911+ }
912+ if (emitChangeSignal) {
913+ Q_EMIT static_cast<UCViewItemsAttached13*>(q_func())->expandedIndicesChanged(expansionList.keys());
914+ }
915+}
916+
917+// collapse the item at index
918+void UCViewItemsAttachedPrivate::collapse(int index, bool emitChangeSignal)
919+{
920+ UCListItem13 *item = expansionList.take(index).data();
921+ bool wasExpanded = item && item->expansion()->expanded();
922+ if (item && ((expansionFlags & UCViewItemsAttached::CollapseOnOutsidePress) == UCViewItemsAttached::CollapseOnOutsidePress)) {
923+ item->expansion()->enableClickFiltering(false);
924+ }
925+ if (emitChangeSignal && wasExpanded) {
926+ Q_EMIT static_cast<UCViewItemsAttached13*>(q_func())->expandedIndicesChanged(expansionList.keys());
927+ }
928+}
929+
930+void UCViewItemsAttachedPrivate::collapseAll()
931+{
932+ bool emitChangedSignal = (expansionList.keys().size() > 0);
933+ while (expansionList.keys().size() > 0) {
934+ collapse(expansionList.keys().last(), false);
935+ }
936+ if (emitChangedSignal) {
937+ Q_EMIT static_cast<UCViewItemsAttached13*>(q_func())->expandedIndicesChanged(expansionList.keys());
938+ }
939+}
940+
941+/*!
942+ * \qmlattachedproperty ExpansionFlags ViewItems::expansionFlags
943+ * \since Ubuntu.Components 1.3
944+ * Flags driving the expansion behavior.
945+ * \table
946+ * \header
947+ * \li Flag
948+ * \li description
949+ * \row
950+ * \li ViewItems.Exclusive
951+ * \li When set, only one ListItem can be expanded at a time. \b {Set by default}.
952+ * \row
953+ * \li ViewItems.UnlockExpanded
954+ * \li When set, the ListItem's leading/trailing actions can be swiped in.
955+ * \row
956+ * \li ViewItems.CollapseOnOutsidePress
957+ * \li When set, the active expaned ListItem collapses automatically when clicked
958+ * outside of its area. The flag also turns \c ViewItems.Exclusive flag on.
959+ * \endtable
960+ */
961+int UCViewItemsAttached13::expansionFlags() const
962+{
963+ Q_D(const UCViewItemsAttached);
964+ return d->expansionFlags;
965+}
966+void UCViewItemsAttached13::setExpansionFlags(int flags)
967+{
968+ Q_D(UCViewItemsAttached);
969+ if (d->expansionFlags == (ExpansionFlags)flags) {
970+ return;
971+ }
972+
973+ // disable current flag based restrictions
974+ d->toggleExpansionFlags(false);
975+ d->expansionFlags = (ExpansionFlags)flags;
976+ // enable flag based restrictions
977+ d->toggleExpansionFlags(true);
978+ Q_EMIT expansionFlagsChanged();
979+}
980+
981+void UCViewItemsAttachedPrivate::toggleExpansionFlags(bool enable)
982+{
983+ bool hasClickOutsideFlag = (expansionFlags & UCViewItemsAttached::CollapseOnOutsidePress) == UCViewItemsAttached::CollapseOnOutsidePress;
984+ if (!hasClickOutsideFlag) {
985+ return;
986+ }
987+ QMapIterator<int, QPointer<UCListItem13> > i(expansionList);
988+ while (i.hasNext()) {
989+ UCListItem13 *item = i.next().value().data();
990+ // using expansion getter we will get the group created
991+ if (item && item->expansion()) {
992+ UCListItemPrivate *listItem = UCListItemPrivate::get(item);
993+ listItem->expansion->enableClickFiltering(enable);
994+ }
995+ }
996+}
997
998=== added file 'tests/resources/listitems/ListItemExpansion.qml'
999--- tests/resources/listitems/ListItemExpansion.qml 1970-01-01 00:00:00 +0000
1000+++ tests/resources/listitems/ListItemExpansion.qml 2015-09-04 10:38:07 +0000
1001@@ -0,0 +1,121 @@
1002+/*
1003+ * Copyright 2015 Canonical Ltd.
1004+ *
1005+ * This program is free software; you can redistribute it and/or modify
1006+ * it under the terms of the GNU Lesser General Public License as published by
1007+ * the Free Software Foundation; version 3.
1008+ *
1009+ * This program is distributed in the hope that it will be useful,
1010+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1011+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1012+ * GNU Lesser General Public License for more details.
1013+ *
1014+ * You should have received a copy of the GNU Lesser General Public License
1015+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1016+ */
1017+
1018+import QtQuick 2.4
1019+import QtQuick.Layouts 1.1
1020+import Ubuntu.Components 1.3
1021+
1022+MainView {
1023+ id: main
1024+ width: units.gu(40)
1025+ height: units.gu(71)
1026+
1027+ Action {
1028+ id: deleteAction
1029+ iconName: "delete"
1030+ }
1031+ property list<Action> contextualActions: [
1032+ Action {
1033+ iconName: "edit"
1034+ },
1035+ Action {
1036+ iconName: "share"
1037+ },
1038+ Action {
1039+ iconName: "stock_website"
1040+ }
1041+ ]
1042+ Page {
1043+ title: "Expansion"
1044+ Column {
1045+ anchors.fill: parent
1046+ spacing: units.dp(4)
1047+ UbuntuListView {
1048+ id: listView
1049+ onEnabledChanged: print("enabled", enabled)
1050+ width: parent.width
1051+ height: parent.height / 2
1052+ clip: true
1053+ ViewItems.onExpandedIndicesChanged: print(ViewItems.expandedIndices)
1054+ ViewItems.expansionFlags: ViewItems.CollapseOnOutsidePress
1055+
1056+ model: ListModel {
1057+ Component.onCompleted: {
1058+ for (var i = 0; i < 3; i++) {
1059+ append({label: "List item #"+i, sectionData: "Locked"});
1060+ }
1061+ for (i = 3; i < 11; i++) {
1062+ append({label: "List item #"+i, sectionData: "Limited, live move"});
1063+ }
1064+ for (i = 11; i < 25; i++) {
1065+ append({label: "List item #"+i, sectionData: "Unlimited, drag'n'drop"});
1066+ }
1067+ }
1068+ }
1069+
1070+ delegate: ListItem {
1071+ id: item
1072+ objectName: "ListItem-" + index
1073+ leadingActions: ListItemActions {
1074+ actions: deleteAction
1075+ }
1076+ trailingActions: ListItemActions {
1077+ actions: contextualActions
1078+ }
1079+ expansion.height: units.gu(15)
1080+
1081+ RowLayout {
1082+ anchors {
1083+ fill: parent
1084+ margins: units.gu(0.5)
1085+ leftMargin: anchors.rightMargin
1086+ rightMargin: units.gu(2)
1087+ }
1088+ Captions {
1089+ id: captions
1090+ title.text: label
1091+ subtitle.text: "from index #" + index
1092+ }
1093+ Button {
1094+ text: "Enable drag mode"
1095+ onClicked: listView.ViewItems.dragMode = true
1096+ }
1097+ }
1098+
1099+ onPressAndHold: {
1100+ print("expand/collapse")
1101+ expansion.expanded = !expansion.expanded
1102+ }
1103+ }
1104+ }
1105+ ListItem {
1106+ Label { text: "Standalone ListItem" }
1107+ expansion.height: units.gu(15)
1108+ onPressAndHold: expansion.expanded = !expansion.expanded;
1109+ }
1110+ ListItem {
1111+ Label { text: "Other Standalone ListItem" }
1112+ expansion.height: units.gu(15)
1113+ onPressAndHold: expansion.expanded = !expansion.expanded;
1114+ }
1115+
1116+ Button {
1117+ text: "Set ListView.ViewItems.expandedIndices"
1118+ onClicked: listView.ViewItems.expandedIndices = [0, 1, 2]
1119+ }
1120+ }
1121+ }
1122+}
1123
1124=== modified file 'tests/unit_x11/tst_components/ListItemTestCase.qml'
1125--- tests/unit_x11/tst_components/ListItemTestCase.qml 2015-08-31 07:50:45 +0000
1126+++ tests/unit_x11/tst_components/ListItemTestCase.qml 2015-09-04 10:38:07 +0000
1127@@ -135,4 +135,9 @@
1128 mouseRelease(dragArea, dragPos.x, dragPos.y + dy);
1129 spyWait();
1130 }
1131+
1132+ function expand(item, expand) {
1133+ item.expansion.expanded = expand;
1134+ wait(400);
1135+ }
1136 }
1137
1138=== added file 'tests/unit_x11/tst_components/ListItemTestCase13.qml'
1139--- tests/unit_x11/tst_components/ListItemTestCase13.qml 1970-01-01 00:00:00 +0000
1140+++ tests/unit_x11/tst_components/ListItemTestCase13.qml 2015-09-04 10:38:07 +0000
1141@@ -0,0 +1,143 @@
1142+/*
1143+ * Copyright 2015 Canonical Ltd.
1144+ *
1145+ * This program is free software; you can redistribute it and/or modify
1146+ * it under the terms of the GNU Lesser General Public License as published by
1147+ * the Free Software Foundation; version 3.
1148+ *
1149+ * This program is distributed in the hope that it will be useful,
1150+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1151+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1152+ * GNU Lesser General Public License for more details.
1153+ *
1154+ * You should have received a copy of the GNU Lesser General Public License
1155+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1156+ */
1157+
1158+import QtQuick 2.4
1159+import QtTest 1.0
1160+import Ubuntu.Test 1.0
1161+import Ubuntu.Components 1.3
1162+
1163+/*
1164+ * Common test case functions for ListItem. A CPO for unit tests.
1165+ */
1166+UbuntuTestCase {
1167+ when: windowShown
1168+
1169+ SignalSpy {
1170+ id: signalSpy
1171+ }
1172+
1173+ // set up the spy component for an item and a signal
1174+ function setupSpy(item, signalName) {
1175+ if (item.hasOwnProperty("leadingActions")) {
1176+ signalSpy.target = item;
1177+ signalSpy.signalName = signalName;
1178+ signalSpy.clear();
1179+ }
1180+ }
1181+ // wait on the previosuly set up spy
1182+ function spyWait(timeout) {
1183+ if (timeout == undefined) {
1184+ timeout = 500;
1185+ }
1186+ if (signalSpy.target) {
1187+ signalSpy.wait(timeout);
1188+ signalSpy.clear();
1189+ signalSpy.target = null;
1190+ } else {
1191+ wait(timeout);
1192+ }
1193+ }
1194+
1195+ // rebounds a ListItem
1196+ function rebound(item, watchTarget) {
1197+ if (watchTarget === undefined) {
1198+ watchTarget = item;
1199+ }
1200+
1201+ if (watchTarget.contentItem.x != watchTarget.contentItem.anchors.leftMargin) {
1202+ mouseClick(item, 1, 1);
1203+ tryCompareFunction(function() {
1204+ return watchTarget.contentItem.x == watchTarget.contentItem.anchors.leftMargin;
1205+ }, true, 500);
1206+ }
1207+ }
1208+
1209+ // delayed swipe, gives few millisecond timeout between each move
1210+ // so Repeater has time to create the panel actions in style
1211+ function swipe(item, x, y, dx, dy) {
1212+ setupSpy(item, "contentMovementEnded");
1213+ flick(item, x, y, dx, dy, 0, 0, undefined, undefined, 100);
1214+ spyWait();
1215+ }
1216+ function swipeNoWait(item, x, y, dx, dy) {
1217+ flick(item, x, y, dx, dy, 0, 0, undefined, undefined, 100);
1218+ }
1219+
1220+ function tug(item, x, y, dx, dy) {
1221+ setupSpy(item, "contentMovementEnded");
1222+ TestExtras.touchDrag(0, item, Qt.point(x, y), Qt.point(dx, dy));
1223+ spyWait();
1224+ }
1225+
1226+ // returns the leading or trailing panel item
1227+ function panelItem(item, leading) {
1228+ return findInvisibleChild(item, (leading ? "ListItemPanelLeading" : "ListItemPanelTrailing"));
1229+ }
1230+
1231+ function toggleSelectMode(view, enabled, scrollToTop) {
1232+ if (view.hasOwnProperty("positionViewAtBeginning") && scrollToTop) {
1233+ // use the topmost listItem to wait for rendering completion
1234+ view.positionViewAtBeginning();
1235+ }
1236+ var listItem = findChild(view, "listItem0");
1237+ verify(listItem);
1238+ view.ViewItems.selectMode = enabled;
1239+ // waitForRendering aint seems to be reliable here, so we wait ~400 msecs
1240+ wait(400);
1241+ }
1242+
1243+ function toggleDragMode(view, enabled) {
1244+ // use the topmost listItem to wait for rendering completion
1245+ view.positionViewAtBeginning();
1246+ var listItem = findChild(view, "listItem0");
1247+ verify(listItem);
1248+ view.ViewItems.dragMode = enabled;
1249+ // waitForRendering aint seems to be reliable here, so we wait ~400 msecs
1250+ wait(400);
1251+ }
1252+
1253+ function drag(view, from, to) {
1254+ var dragArea = findChild(view, "drag_area");
1255+ verify(dragArea, "Cannot locate drag area");
1256+
1257+ // grab the source item
1258+ view.positionViewAtBeginning(from,ListView.Beginning);
1259+ var panel = findChild(view, "drag_panel" + from);
1260+ verify(panel, "Cannot locate source panel");
1261+ var dragPos = dragArea.mapFromItem(panel, centerOf(panel).x, centerOf(panel).y);
1262+ // move the mouse
1263+ var dy = Math.abs(to - from) * panel.height + units.gu(1);
1264+ dy *= (to > from) ? 1 : -1;
1265+ mousePress(dragArea, dragPos.x, dragPos.y);
1266+ wait(100);
1267+ var draggedItem = findChild(view.contentItem, "DraggedListItem");
1268+ if (draggedItem) {
1269+ setupSpy(draggedItem.__styleInstance.dropAnimation, "stopped");
1270+ }
1271+ // use 10 steps to be sure the move is properly detected by the drag area
1272+ mouseMoveSlowly(dragArea, dragPos.x, dragPos.y, 0, dy, 10, 100);
1273+ // drop it, needs two mouse releases, this generates the Drop event also
1274+ mouseRelease(dragArea, dragPos.x, dragPos.y + dy);
1275+ // needs one more mouse release
1276+ mouseRelease(dragArea, dragPos.x, dragPos.y + dy);
1277+ spyWait();
1278+ }
1279+
1280+ function expand(item, expand) {
1281+ item.expansion.expanded = expand;
1282+ wait(400);
1283+ }
1284+}
1285
1286=== modified file 'tests/unit_x11/tst_components/tst_listitem.qml'
1287--- tests/unit_x11/tst_components/tst_listitem.qml 2015-07-31 06:55:59 +0000
1288+++ tests/unit_x11/tst_components/tst_listitem.qml 2015-09-04 10:38:07 +0000
1289@@ -17,7 +17,7 @@
1290 import QtQuick 2.4
1291 import QtTest 1.0
1292 import Ubuntu.Test 1.0
1293-import Ubuntu.Components 1.3
1294+import Ubuntu.Components 1.2
1295 import Ubuntu.Components.Styles 1.2
1296 import QtQml.Models 2.1
1297
1298@@ -304,26 +304,6 @@
1299 clickSpy.wait();
1300 }
1301
1302- SignalSpy {
1303- id: visibleSpy
1304- signalName: "visibleChanged"
1305- }
1306-
1307- function test_context_menu() {
1308- mouseClick(testItem, testItem.width / 2, testItem.height / 2, Qt.RightButton);
1309- wait(1000);
1310- compare(testItem.highlighted, true, "List item didn't highlight on right-click");
1311- var context_menu = findChild(main, "listItemContextMenu");
1312- verify(context_menu, "Context menu didn't open on right-click");
1313- waitForRendering(context_menu);
1314- var edit = findChildWithProperty(context_menu, "text", "Edit");
1315- verify(edit, "Context menu has no 'Edit' item");
1316- visibleSpy.target = context_menu;
1317- mouseClick(edit, edit.width / 2, edit.height / 2);
1318- compare(edit.text, 'Edit Again', "Item wasn't triggered'");
1319- visibleSpy.wait()
1320- }
1321-
1322 function test_no_click_when_swiped() {
1323 var item = findChild(listView, "listItem0");
1324 clickSpy.target = item;
1325
1326=== added file 'tests/unit_x11/tst_components/tst_listitem13.qml'
1327--- tests/unit_x11/tst_components/tst_listitem13.qml 1970-01-01 00:00:00 +0000
1328+++ tests/unit_x11/tst_components/tst_listitem13.qml 2015-09-04 10:38:07 +0000
1329@@ -0,0 +1,1211 @@
1330+/*
1331+ * Copyright 2015 Canonical Ltd.
1332+ *
1333+ * This program is free software; you can redistribute it and/or modify
1334+ * it under the terms of the GNU Lesser General Public License as published by
1335+ * the Free Software Foundation; version 3.
1336+ *
1337+ * This program is distributed in the hope that it will be useful,
1338+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1339+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1340+ * GNU Lesser General Public License for more details.
1341+ *
1342+ * You should have received a copy of the GNU Lesser General Public License
1343+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1344+ */
1345+
1346+import QtQuick 2.4
1347+import QtTest 1.0
1348+import Ubuntu.Test 1.0
1349+import Ubuntu.Components 1.3
1350+import Ubuntu.Components.Styles 1.3
1351+import QtQml.Models 2.1
1352+
1353+Item {
1354+ id: main
1355+ width: units.gu(50)
1356+ height: units.gu(100)
1357+
1358+ Action {
1359+ id: stockAction
1360+ iconName: "torch-on"
1361+ objectName: "stockAction"
1362+ text: 'Switch lights on'
1363+ }
1364+ ListItemActions {
1365+ id: leading
1366+ actions: [
1367+ Action {
1368+ iconName: "starred"
1369+ text: 'Bookmark'
1370+ objectName: "leading_1"
1371+ },
1372+ Action {
1373+ iconName: "edit"
1374+ text: 'Edit'
1375+ objectName: "leading_2"
1376+ onTriggered: text = 'Edit Again'
1377+ },
1378+ Action {
1379+ iconName: "camcorder"
1380+ text: 'Record'
1381+ objectName: "leading_3"
1382+ }
1383+ ]
1384+ }
1385+ ListItemActions {
1386+ id: trailing
1387+ actions: [
1388+ stockAction,
1389+ ]
1390+ }
1391+ ListItemActions {
1392+ id: actionsDefault
1393+ }
1394+ ListModel {
1395+ id: objectModel
1396+ function reset() {
1397+ clear();
1398+ for (var i = 0; i < 25; i++) {
1399+ append({data: i});
1400+ }
1401+ }
1402+ Component.onCompleted: reset()
1403+ }
1404+
1405+ Component {
1406+ id: customDelegate
1407+ Rectangle {
1408+ width: units.gu(10)
1409+ color: "green"
1410+ objectName: "custom_delegate"
1411+ }
1412+ }
1413+
1414+ Column {
1415+ id: testColumn
1416+ width: parent.width
1417+ ListItem {
1418+ id: defaults
1419+ width: parent.width
1420+ }
1421+ ListItem {
1422+ id: highlightTest
1423+ }
1424+ ListItem {
1425+ id: clickedConnected
1426+ onClicked: {}
1427+ onPressAndHold: {}
1428+ }
1429+ ListItem {
1430+ id: testItem
1431+ width: parent.width
1432+ color: "blue"
1433+ leadingActions: leading
1434+ trailingActions: trailing
1435+ Label {
1436+ id: bodyItem
1437+ anchors.fill: parent
1438+ text: "Data"
1439+ }
1440+ }
1441+ ListItem {
1442+ id: controlItem
1443+ Button {
1444+ id: button
1445+ objectName: "button_in_list"
1446+ anchors.centerIn: parent
1447+ text: "Button"
1448+ }
1449+ }
1450+ ListView {
1451+ id: listView
1452+ width: parent.width
1453+ height: units.gu(28)
1454+ clip: true
1455+ model: objectModel
1456+ ViewItems.selectMode: false
1457+ LayoutMirroring.childrenInherit: true
1458+ delegate: ListItem {
1459+ objectName: "listItem" + index
1460+ color: "lightgray"
1461+ leadingActions: leading
1462+ trailingActions: trailing
1463+ Label {
1464+ text: "Data: " + modelData + " @" + index
1465+ }
1466+ }
1467+ }
1468+ Flickable {
1469+ id: testFlickable
1470+ width: parent.width
1471+ height: units.gu(21)
1472+ ListView {
1473+ id: nestedListView
1474+ width: parent.width
1475+ height: units.gu(28)
1476+ clip: true
1477+ model: 10
1478+ delegate: ListItem {
1479+ objectName: "listItem" + index
1480+ leadingActions: leading
1481+ }
1482+ }
1483+ }
1484+ Flickable {
1485+ id: flickable
1486+ width: parent.width
1487+ height: units.gu(14)
1488+ clip: true
1489+ contentHeight: column.height
1490+ Column {
1491+ id: column
1492+ width: parent.width
1493+ Repeater {
1494+ model: 10
1495+ ListItem {
1496+ objectName: "listItem" + index
1497+ color: "lightgreen"
1498+ }
1499+ }
1500+ }
1501+ }
1502+ }
1503+
1504+ ListItemTestCase13 {
1505+ id: testCase
1506+ name: "ListItem13API"
1507+ when: windowShown
1508+
1509+ SignalSpy {
1510+ id: movingSpy
1511+ signalName: "contentMovementEnded"
1512+ }
1513+ SignalSpy {
1514+ id: highlightedSpy
1515+ signalName: "highlightedChanged"
1516+ target: testItem
1517+ }
1518+
1519+ SignalSpy {
1520+ id: clickSpy
1521+ signalName: "clicked"
1522+ target: testItem;
1523+ }
1524+
1525+ SignalSpy {
1526+ id: actionSpy
1527+ signalName: "onTriggered"
1528+ }
1529+ SignalSpy {
1530+ id: interactiveSpy
1531+ signalName: "interactiveChanged"
1532+ }
1533+
1534+ SignalSpy {
1535+ id: dropSpy
1536+ signalName: "stopped"
1537+ }
1538+
1539+ function initTestCase() {
1540+ TestExtras.registerTouchDevice();
1541+ waitForRendering(main);
1542+ }
1543+
1544+ function cleanup() {
1545+ listView.model = objectModel;
1546+ testItem.action = null;
1547+ testItem.contentItem.anchors.margins = 0;
1548+ testItem.selected = false;
1549+ testColumn.ViewItems.selectMode = false;
1550+ waitForRendering(testItem.contentItem, 200);
1551+ controlItem.selected = false;
1552+ waitForRendering(controlItem.contentItem, 200);
1553+ movingSpy.clear();
1554+ highlightedSpy.clear();
1555+ clickSpy.clear();
1556+ actionSpy.clear();
1557+ pressAndHoldSpy.clear();
1558+ buttonSpy.clear();
1559+ interactiveSpy.clear();
1560+ listView.interactive = true;
1561+ listView.ViewItems.selectMode = false;
1562+ listView.ViewItems.dragMode = false;
1563+ // make sure we collapse
1564+ mouseClick(defaults, 0, 0)
1565+ movingSpy.target = null;
1566+ movingSpy.clear();
1567+ interactiveSpy.target = null;
1568+ interactiveSpy.clear();
1569+ trailing.delegate = null;
1570+ listView.positionViewAtBeginning();
1571+ // keep additional timeout for proper cleanup
1572+ wait(200);
1573+ }
1574+
1575+ function test_0_defaults() {
1576+ verify(defaults.contentItem !== null, "Defaults is null");
1577+ compare(defaults.color, "#000000", "Transparent by default");
1578+ compare(defaults.highlightColor, theme.palette.selected.background, "theme.palette.selected.background color by default")
1579+ compare(defaults.highlighted, false, "Not highlighted by default");
1580+ compare(defaults.divider.visible, true, "divider is visible by default");
1581+ compare(defaults.divider.anchors.leftMargin, 0, "divider's left margin is 0");
1582+ compare(defaults.divider.anchors.rightMargin, 0, "divider's right margin is 0");
1583+ var mappedDividerPos = defaults.mapFromItem(defaults.divider, defaults.divider.x, defaults.divider.y);
1584+ compare(mappedDividerPos.x, 0, "divider's left anchor is wrong");
1585+ compare(mappedDividerPos.x + defaults.divider.width, defaults.width, "divider's right anchor is wrong");
1586+ compare(defaults.divider.height, units.dp(2), "divider's thickness is wrong");
1587+ compare(defaults.divider.colorFrom, "#000000", "colorFrom differs.");
1588+ fuzzyCompare(defaults.divider.colorFrom.a, 0.14, 0.01, "colorFrom alpha differs");
1589+ compare(defaults.divider.colorTo, "#ffffff", "colorTo differs.");
1590+ fuzzyCompare(defaults.divider.colorTo.a, 0.07, 0.01, "colorTo alpha differs");
1591+ compare(defaults.action, null, "No action by default.");
1592+ compare(defaults.style, null, "Style is loaded upon first use.");
1593+ compare(defaults.__styleInstance, null, "__styleInstance must be null.");
1594+ compare(defaults.selected, false, "Not selected by default");
1595+ compare(defaults.selectMode, false, "Not selectable by default");
1596+ compare(testColumn.ViewItems.selectMode, false, "The parent attached property is not selectable by default");
1597+ compare(testColumn.ViewItems.selectedIndices.length, 0, "No item is selected by default");
1598+ compare(listView.ViewItems.dragMode, false, "Drag mode is off on ListView");
1599+
1600+ compare(actionsDefault.delegate, null, "ListItemActions has no delegate set by default.");
1601+ compare(actionsDefault.actions.length, 0, "ListItemActions has no actions set.");
1602+ }
1603+
1604+ Component { id: customStyle; ListItemStyle {} }
1605+
1606+ function test_children_in_content_item() {
1607+ compare(bodyItem.parent, testItem.contentItem, "Content is not in the right holder!");
1608+ }
1609+
1610+ function test_highlightedChanged_on_click() {
1611+ highlightedSpy.target = testItem;
1612+ mousePress(testItem, testItem.width / 2, testItem.height / 2);
1613+ highlightedSpy.wait();
1614+ mouseRelease(testItem, testItem.width / 2, testItem.height / 2);
1615+ }
1616+ function test_highlightedChanged_on_tap() {
1617+ highlightedSpy.target = testItem;
1618+ TestExtras.touchPress(0, testItem, centerOf(testItem));
1619+ highlightedSpy.wait();
1620+ TestExtras.touchRelease(0, testItem, centerOf(testItem));
1621+ // local cleanup, wait few msecs to suppress double tap
1622+ wait(400);
1623+ }
1624+
1625+ function test_clicked_on_mouse() {
1626+ clickSpy.target = testItem;
1627+ mouseClick(testItem, testItem.width / 2, testItem.height / 2);
1628+ clickSpy.wait();
1629+ }
1630+ function test_clicked_on_tap() {
1631+ clickSpy.target = testItem;
1632+ TestExtras.touchClick(0, testItem, centerOf(testItem));
1633+ clickSpy.wait();
1634+ }
1635+
1636+ SignalSpy {
1637+ id: visibleSpy
1638+ signalName: "visibleChanged"
1639+ }
1640+
1641+ function test_context_menu() {
1642+ mouseClick(testItem, testItem.width / 2, testItem.height / 2, Qt.RightButton);
1643+ wait(1000);
1644+ compare(testItem.highlighted, true, "List item didn't highlight on right-click");
1645+ var context_menu = findChild(main, "listItemContextMenu");
1646+ verify(context_menu, "Context menu didn't open on right-click");
1647+ waitForRendering(context_menu);
1648+ var edit = findChildWithProperty(context_menu, "text", "Edit");
1649+ verify(edit, "Context menu has no 'Edit' item");
1650+ visibleSpy.target = context_menu;
1651+ mouseClick(edit, edit.width / 2, edit.height / 2);
1652+ compare(edit.text, 'Edit Again', "Item wasn't triggered'");
1653+ visibleSpy.wait()
1654+ }
1655+
1656+ function test_no_click_when_swiped() {
1657+ var item = findChild(listView, "listItem0");
1658+ clickSpy.target = item;
1659+ clickSpy.clear();
1660+ swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
1661+
1662+ // click over the contentItem
1663+ movingSpy.target = item;
1664+ mouseClick(item.contentItem, 1, 1);
1665+ compare(clickSpy.count, 0, "No click() should be emitted on a swiped in ListItem.");
1666+ movingSpy.wait();
1667+ }
1668+
1669+ function test_no_pressAndHold_when_swiped() {
1670+ var item = findChild(listView, "listItem0");
1671+ pressAndHoldSpy.target = item;
1672+ pressAndHoldSpy.clear();
1673+ swipe(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0);
1674+
1675+ // press and hold
1676+ movingSpy.target = item;
1677+ mouseLongPress(item.contentItem, 1, 1);
1678+ mouseRelease(item.contentItem, 1, 1);
1679+ mouseRelease(item.contentItem, 1, 1);
1680+ compare(pressAndHoldSpy.count, 0, "No pressAndHold() should be emitted on a swiped in ListItem.");
1681+ movingSpy.wait();
1682+ }
1683+
1684+ function test_vertical_listview_move_cancels_highlight_data() {
1685+ return [
1686+ {tag: "With touch", mouse: false},
1687+ {tag: "With mouse", mouse: true},
1688+ ];
1689+ }
1690+ function test_vertical_listview_move_cancels_highlight(data) {
1691+ var listItem = findChild(listView, "listItem0");
1692+ verify(listItem, "Cannot find listItem0");
1693+
1694+ // convert positions and use the listView to move
1695+ var pos = listView.mapFromItem(listItem, listItem.width / 2, 0);
1696+ if (data.mouse) {
1697+ // provide slow move
1698+ mousePress(listView, pos.x, pos.y);
1699+ for (var i = 1; i < 4; i++) {
1700+ pos.y += i * units.gu(0.5);
1701+ mouseMove(listView, pos.x, pos.y, 100);
1702+ }
1703+ compare(listItem.highlighted, false, "highlighted still!");
1704+ mouseRelease(listView, pos.x, pos.y, undefined, undefined, 100);
1705+ } else {
1706+ // convert pos to point otherwise touch functions will get (0,0) points!!!
1707+ var pt = Qt.point(pos.x, pos.y);
1708+ TestExtras.touchPress(0, listView, pt);
1709+ for (i = 1; i < 4; i++) {
1710+ pt.y += i * units.gu(0.5);
1711+ TestExtras.touchMove(0, listView, pt);
1712+ wait(100);
1713+ }
1714+ compare(listItem.highlighted, false, "highlighted still!");
1715+ TestExtras.touchRelease(0, listView, pt);
1716+ }
1717+ }
1718+
1719+ function test_background_height_change_on_divider_visible() {
1720+ // make sure the testItem's divider is shown
1721+ testItem.divider.visible = true;
1722+ var margins = testItem.contentItem.anchors.topMargin + testItem.contentItem.anchors.bottomMargin;
1723+ compare(testItem.contentItem.height, testItem.height - margins - testItem.divider.height, "ListItem's background height must be less than the item itself.");
1724+ testItem.divider.visible = false;
1725+ waitForRendering(testItem.contentItem);
1726+ compare(testItem.contentItem.height, testItem.height - margins, "ListItem's background height must be the same as the item itself.");
1727+ testItem.divider.visible = true;
1728+ }
1729+
1730+ function test_tug_actions_data() {
1731+ var item = findChild(listView, "listItem0");
1732+ return [
1733+ {tag: "Trailing, mouse", item: item, pos: centerOf(item), dx: -units.gu(20), positiveDirection: false, mouse: true},
1734+ {tag: "Leading, mouse", item: item, pos: centerOf(item), dx: units.gu(20), positiveDirection: true, mouse: true},
1735+ {tag: "Trailing, touch", item: item, pos: centerOf(item), dx: -units.gu(20), positiveDirection: false, mouse: false},
1736+ {tag: "Leading, touch", item: item, pos: centerOf(item), dx: units.gu(20), positiveDirection: true, mouse: false},
1737+ ];
1738+ }
1739+ function test_tug_actions(data) {
1740+ listView.positionViewAtBeginning();
1741+ if (data.mouse) {
1742+ swipe(data.item, data.pos.x, data.pos.y, data.dx, 0);
1743+ } else {
1744+ tug(data.item, data.pos.x, data.pos.y, data.dx, 0);
1745+ }
1746+ if (data.positiveDirection) {
1747+ verify(data.item.contentItem.x > 0, data.tag + " actions did not show up");
1748+ } else {
1749+ verify(data.item.contentItem.x < 0, data.tag + " actions did not show up");
1750+ }
1751+
1752+ // dismiss
1753+ rebound(data.item);
1754+ }
1755+
1756+ function test_tug_ignored_on_right_button() {
1757+ listView.positionViewAtBeginning();
1758+ var item = findChild(listView, "listItem0");
1759+ movingSpy.target = item;
1760+ flick(item, centerOf(item).x, centerOf(item).y, units.gu(20), 0, 0, 0, Qt.RightButton, undefined, 100);
1761+ compare(movingSpy.count, 0, "Action panel should not budge!")
1762+ }
1763+
1764+ function test_rebound_when_pressed_outside_or_clicked_data() {
1765+ var item0 = findChild(listView, "listItem0");
1766+ var item1 = findChild(listView, "listItem1");
1767+ return [
1768+ {tag: "Click on an other Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: true},
1769+ {tag: "Click on the same Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item0, mouse: true},
1770+ {tag: "Tap on an other Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: false},
1771+ {tag: "Tap on the same Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item0, mouse: false},
1772+ ];
1773+ }
1774+ function test_rebound_when_pressed_outside_or_clicked(data) {
1775+ listView.positionViewAtBeginning();
1776+ if (data.mouse) {
1777+ swipe(data.item, data.pos.x, data.pos.y, data.dx, 0);
1778+ } else {
1779+ tug(data.item, data.pos.x, data.pos.y, data.dx, 0);
1780+ }
1781+ verify(data.item.contentItem.x != 0, "The component wasn't tugged!");
1782+ // dismiss
1783+ rebound(data.clickOn, data.item)
1784+ }
1785+
1786+ function test_listview_not_interactive_while_tugged_data() {
1787+ var item0 = findChild(listView, "listItem0");
1788+ var item1 = findChild(listView, "listItem1");
1789+ return [
1790+ {tag: "Trailing", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: true},
1791+ {tag: "Leading", item: item0, pos: centerOf(item0), dx: units.gu(20), clickOn: item0.contentItem, mouse: true},
1792+ {tag: "Trailing", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: false},
1793+ {tag: "Leading", item: item0, pos: centerOf(item0), dx: units.gu(20), clickOn: item0.contentItem, mouse: false},
1794+ ];
1795+ }
1796+ function test_listview_not_interactive_while_tugged(data) {
1797+ listView.positionViewAtBeginning();
1798+ interactiveSpy.target = listView;
1799+ compare(listView.interactive, true, "ListView is not interactive");
1800+ interactiveSpy.target = listView;
1801+ if (data.mouse) {
1802+ swipe(data.item, data.pos.x, data.pos.y, data.dx, data.dy);
1803+ } else {
1804+ tug(data.item, data.pos.x, data.pos.y, data.dx, data.dy);
1805+ }
1806+ // animation should no longer be running!
1807+ compare(listView.interactive, true, "The ListView is still non-interactive!");
1808+ compare(interactiveSpy.count, 2, "Less/more times changed!");
1809+ // check if it snapped in
1810+ verify(data.item.contentItem.x != 0.0, "Not snapped in!!");
1811+ // dismiss
1812+ rebound(data.clickOn, data.item);
1813+ fuzzyCompare(data.item.contentItem.x, data.item.contentItem.anchors.leftMargin, 0.1, "Not snapped out!!");
1814+ }
1815+
1816+ function test_visualized_actions_data() {
1817+ var listItem0 = findChild(listView, "listItem0");
1818+ var listItem1 = findChild(listView, "listItem1");
1819+ return [
1820+ {tag: "Leading actions", item: listItem0, leading: true, expected: ["leading_1", "leading_2", "leading_3"]},
1821+ {tag: "Trailing actions", item: listItem0, leading: false, expected: ["stockAction"]},
1822+ ];
1823+ }
1824+ function test_visualized_actions(data) {
1825+ swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.leading ? units.gu(20) : -units.gu(20), 0);
1826+
1827+ // check if the action is visible
1828+ var panel = panelItem(data.item, data.leading);
1829+ verify(panel, "Panel not visible");
1830+ for (var i in data.expected) {
1831+ var actionItem = findChild(panel, data.expected[i]);
1832+ verify(actionItem, data.expected[i] + " action not found");
1833+ }
1834+ // dismiss
1835+ rebound(data.item);
1836+ }
1837+
1838+ function test_listitem_margins_data() {
1839+ var item = findChild(listView, "listItem1");
1840+ return [
1841+ {tag: "leading", item: item, dx: units.gu(10), leading: true},
1842+ {tag: "trailing", item: item, dx: -units.gu(10), leading: false}
1843+ ];
1844+ }
1845+ function test_listitem_margins(data) {
1846+ data.item.contentItem.anchors.margins = units.gu(1);
1847+ swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0);
1848+ var panel = panelItem(data.item, data.leading);
1849+ verify(panel && panel.visible, "Panel not visible.");
1850+ // cleanup
1851+ rebound(data.item);
1852+ compare(data.item.contentItem.x, units.gu(1), "contentItem.x differs from margin");
1853+ data.item.contentItem.anchors.margins = 0;
1854+ }
1855+
1856+ function test_selecting_action_rebounds_data() {
1857+ var item0 = findChild(listView, "listItem0");
1858+ return [
1859+ {tag: "With mouse", item: item0, pos: centerOf(item0), dx: units.gu(20), leading: true, select: "leading_1", mouse: true},
1860+ {tag: "With touch", item: item0, pos: centerOf(item0), dx: units.gu(20), leading: true, select: "leading_1", mouse: false},
1861+ ]
1862+ }
1863+ function test_selecting_action_rebounds(data) {
1864+ listView.positionViewAtBeginning();
1865+ if (data.mouse) {
1866+ swipe(data.item, data.pos.x, data.pos.y, data.dx, 0);
1867+ } else {
1868+ tug(data.item, data.pos.x, data.pos.y, data.dx, 0);
1869+ }
1870+ verify(data.item.contentItem.x > 0, "Not snapped in!");
1871+ var panel = panelItem(data.item, data.leading);
1872+ verify(panel, "panelItem not found");
1873+ var selectedAction = findChild(panel, data.select);
1874+ verify(selectedAction, "Cannot select action " + data.select);
1875+
1876+ // dismiss
1877+ movingSpy.target = data.item;
1878+ if (data.mouse) {
1879+ mouseClick(selectedAction, centerOf(selectedAction).x, centerOf(selectedAction).y);
1880+ } else {
1881+ TestExtras.touchClick(0, selectedAction, centerOf(selectedAction));
1882+ }
1883+ movingSpy.wait();
1884+ fuzzyCompare(data.item.contentItem.x, data.item.contentItem.anchors.leftMargin, 0.1, "Content not snapped out");
1885+ }
1886+
1887+ function test_custom_trailing_delegate() {
1888+ trailing.delegate = customDelegate;
1889+ listView.positionViewAtBeginning();
1890+ var item = findChild(listView, "listItem0");
1891+ movingSpy.target = item;
1892+ swipeNoWait(item, centerOf(item).x, centerOf(item).y, -units.gu(20), 0);
1893+ var panel = panelItem(item, false);
1894+ verify(panel, "Panel is not visible");
1895+ var custom = findChild(panel, "custom_delegate");
1896+ verify(custom, "Custom delegate not in use");
1897+ movingSpy.wait();
1898+ // cleanup
1899+ rebound(item);
1900+ }
1901+
1902+ // execute as last so we make sure we have the panel created
1903+ function test_snap_data() {
1904+ var listItem = findChild(listView, "listItem0");
1905+ verify(listItem, "ListItem cannot be found");
1906+
1907+ return [
1908+ // the list snaps out if the panel is dragged in > overshoot GU (hardcoded for now)
1909+ {tag: "Snap out leading", item: listItem, dx: units.gu(2), snapIn: false},
1910+ {tag: "Snap in leading", item: listItem, dx: units.gu(6), snapIn: true},
1911+ {tag: "Snap out trailing", item: listItem, dx: -units.gu(2), snapIn: false},
1912+ {tag: "Snap in trailing", item: listItem, dx: -units.gu(6), snapIn: true},
1913+ ];
1914+ }
1915+ function test_snap(data) {
1916+ swipe(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0);
1917+ waitForRendering(data.item.contentItem, 400);
1918+ if (data.snapIn) {
1919+ verify(data.item.contentItem.x != 0.0, "Not snapped to be visible");
1920+ // cleanup
1921+ rebound(data.item);
1922+ } else {
1923+ tryCompareFunction(function() { return data.item.contentItem.x; }, data.item.contentItem.anchors.leftMargin, 1000, "Not snapped back");
1924+ }
1925+ }
1926+
1927+ function test_snap_gesture_data() {
1928+ var listItem = findChild(listView, "listItem0");
1929+ var front = Qt.point(listItem.contentItem.anchors.leftMargin + units.gu(1), listItem.height / 2);
1930+ var rear = Qt.point(listItem.width - (listItem.contentItem.anchors.rightMargin + units.gu(1)), listItem.height / 2);
1931+ return [
1932+ // the first dx must be big enough to drag the panel in, it is always the last dx value
1933+ // which decides the snap direction
1934+ {tag: "Snap out, leading", item: listItem, grabPos: front, dx: [units.gu(10), -units.gu(3)], snapIn: false},
1935+ {tag: "Snap in, leading", item: listItem, grabPos: front, dx: [units.gu(10), -units.gu(1), units.gu(1.5)], snapIn: true},
1936+ // have less first dx as the trailing panel is shorter
1937+ {tag: "Snap out, trailing", item: listItem, grabPos: rear, dx: [-units.gu(5), units.gu(2)], snapIn: false},
1938+ {tag: "Snap in, trailing", item: listItem, grabPos: rear, dx: [-units.gu(5), units.gu(1), -units.gu(1.5)], snapIn: true},
1939+ ];
1940+ }
1941+ function test_snap_gesture(data) {
1942+ // performe the moves
1943+ movingSpy.target = data.item;
1944+ var pos = data.grabPos;
1945+ mousePress(data.item, pos.x, pos.y);
1946+ for (var i in data.dx) {
1947+ var dx = data.dx[i];
1948+ mouseMoveSlowly(data.item, pos.x, pos.y, data.dx[i], 0, 5, 100);
1949+ pos.x += data.dx[i];
1950+ }
1951+ mouseRelease(data.item, pos.x, pos.y);
1952+ movingSpy.wait();
1953+
1954+ if (data.snapIn) {
1955+ // the contenTitem must be dragged in (snapIn)
1956+ verify(data.item.contentItem.x != 0.0, "Not snapped in!");
1957+ // dismiss
1958+ rebound(data.item);
1959+ } else {
1960+ fuzzyCompare(data.item.contentItem.x, data.item.contentItem.anchors.leftMargin, 0.1, "Not snapped out!");
1961+ }
1962+ }
1963+
1964+ function test_verify_action_value_data() {
1965+ listView.positionViewAtBeginning();
1966+ var item0 = findChild(listView, "listItem0");
1967+ var item1 = findChild(listView, "listItem1");
1968+ return [
1969+ // testItem is the child item @index 3 in the topmost Column.
1970+ {tag: "Standalone item, child index 3", item: testItem, result: 3},
1971+ {tag: "ListView, item index 0", item: item0, result: 0},
1972+ {tag: "ListView, item index 1", item: item1, result: 1},
1973+ ];
1974+ }
1975+ function test_verify_action_value(data) {
1976+ // tug actions in
1977+ swipe(data.item, 1, centerOf(data.item).y, units.gu(40), 0);
1978+ wait(2000);
1979+ verify(data.item.contentItem.x != data.item.contentItem.anchors.leftMargin, "Not snapped in");
1980+
1981+ var panel = panelItem(data.item, "Leading");
1982+ var action = findChild(panel, "leading_2");
1983+ verify(action, "actions panel cannot be reached");
1984+ // we test the action closest to the list item's contentItem
1985+ actionSpy.target = data.item.leadingActions.actions[1];
1986+
1987+ // select the action
1988+ movingSpy.target = data.item;
1989+ mouseClick(action, centerOf(action).x, centerOf(action).y);
1990+ movingSpy.wait();
1991+
1992+ // check the action param
1993+ actionSpy.wait();
1994+ // SignalSpy.signalArguments[0] is an array of arguments, where the index is set as index 0
1995+ var param = actionSpy.signalArguments[0];
1996+ compare(param[0], data.result, "Action parameter differs");
1997+ }
1998+
1999+ function test_highlight_data() {
2000+ return [
2001+ {tag: "No actions", item: highlightTest, x: centerOf(highlightTest).x, y: centerOf(highlightTest).y, pressed: false},
2002+ {tag: "Leading/trailing actions", item: testItem, x: centerOf(testItem).x, y: centerOf(testItem).y, pressed: true},
2003+ {tag: "Active component content", item: controlItem, x: units.gu(1), y: units.gu(1), pressed: true},
2004+ {tag: "Center of active component content", item: controlItem, x: centerOf(controlItem).x, y: centerOf(controlItem).y, pressed: false},
2005+ {tag: "clicked() connected", item: clickedConnected, x: centerOf(clickedConnected).x, y: centerOf(clickedConnected).y, pressed: true},
2006+ ];
2007+ }
2008+ function test_highlight(data) {
2009+ highlightedSpy.target = data.item;
2010+ mouseClick(data.item, data.x, data.y);
2011+ if (data.pressed) {
2012+ highlightedSpy.wait();
2013+ } else {
2014+ compare(highlightedSpy.count, 0, "Should not be highlighted!");
2015+ }
2016+ }
2017+
2018+ SignalSpy {
2019+ id: pressAndHoldSpy
2020+ signalName: "pressAndHold"
2021+ }
2022+ SignalSpy {
2023+ id: buttonSpy
2024+ signalName: "clicked"
2025+ target: button
2026+ }
2027+ function test_pressandhold_suppress_click() {
2028+ var center = centerOf(testItem);
2029+ pressAndHoldSpy.target = testItem;
2030+ clickSpy.target = testItem;
2031+ clickSpy.clear();
2032+ mouseLongPress(testItem, center.x, center.y);
2033+ mouseRelease(testItem, center.x, center.y);
2034+ pressAndHoldSpy.wait();
2035+ compare(clickSpy.count, 0, "Click must be suppressed when long pressed");
2036+ }
2037+
2038+ function test_pressandhold_not_emitted_when_swiped() {
2039+ var center = centerOf(testItem);
2040+ pressAndHoldSpy.target = testItem;
2041+ // move mouse slowly from left to right, the swipe threshold is 1.5 GU!!!,
2042+ // so any value less than that will emit pressAndHold
2043+ mouseMoveSlowly(testItem, center.x, center.y, units.gu(2), 0, 10, 100);
2044+ mouseRelease(testItem, center.x + units.gu(1), center.y);
2045+ compare(pressAndHoldSpy.count, 0, "pressAndHold should not be emitted!");
2046+ // make sure we have collapsed item
2047+ rebound(testItem);
2048+ }
2049+
2050+ function test_pressandhold_not_emitted_when_pressed_over_active_component() {
2051+ var press = centerOf(button);
2052+ pressAndHoldSpy.target = controlItem;
2053+ mouseLongPress(button, press.x, press.y);
2054+ compare(pressAndHoldSpy.count, 0, "")
2055+ mouseRelease(button, press.x, press.y);
2056+ }
2057+
2058+ function test_click_on_button_suppresses_listitem_click() {
2059+ buttonSpy.target = button;
2060+ clickSpy.target = controlItem;
2061+ mouseClick(button, centerOf(button).x, centerOf(button).y);
2062+ buttonSpy.wait();
2063+ compare(clickSpy.count, 0, "ListItem clicked() must be suppressed");
2064+ }
2065+
2066+ function test_pressandhold_connected_causes_highlight() {
2067+ highlightedSpy.target = clickedConnected;
2068+ mouseLongPress(clickedConnected, centerOf(clickedConnected).x, centerOf(clickedConnected).y);
2069+ highlightedSpy.wait();
2070+ mouseRelease(clickedConnected, centerOf(clickedConnected).x, centerOf(clickedConnected).y);
2071+ }
2072+
2073+ function test_listitem_blocks_ascendant_flickables() {
2074+ var listItem = findChild(nestedListView, "listItem0");
2075+ verify(listItem, "Cannot find test item");
2076+ interactiveSpy.target = testFlickable;
2077+ // tug leading
2078+ swipe(listItem, centerOf(listItem).x, centerOf(listItem).y, listItem.width / 2, 0);
2079+ // check if interactive got changed
2080+ interactiveSpy.wait();
2081+
2082+ // cleanup!!!
2083+ rebound(listItem);
2084+ }
2085+
2086+ function test_action_type_set() {
2087+ stockAction.parameterType = Action.None;
2088+ compare(stockAction.parameterType, Action.None, "No parameter type for stockAction!");
2089+ testItem.action = stockAction;
2090+ compare(stockAction.parameterType, Action.Integer, "No parameter type for stockAction!");
2091+ }
2092+
2093+ function test_action_triggered_on_clicked() {
2094+ testItem.action = stockAction;
2095+ actionSpy.target = stockAction;
2096+ mouseClick(testItem, centerOf(testItem).x, centerOf(testItem).y);
2097+ actionSpy.wait();
2098+ }
2099+
2100+ function test_action_suppressed_on_longpress() {
2101+ testItem.action = stockAction;
2102+ actionSpy.target = stockAction;
2103+ clickSpy.target = testItem;
2104+ pressAndHoldSpy.target = testItem;
2105+ mouseLongPress(testItem, centerOf(testItem).x, centerOf(testItem).y);
2106+ mouseRelease(testItem, centerOf(testItem).x, centerOf(testItem).y);
2107+ pressAndHoldSpy.wait();
2108+ compare(clickSpy.count, 0, "Click must be suppressed.");
2109+ compare(actionSpy.count, 0, "Action triggered must be suppressed");
2110+ }
2111+
2112+ function test_select_indices_updates_selected_items() {
2113+ listView.ViewItems.selectedIndices = [0,1,2];
2114+ toggleSelectMode(listView, true);
2115+ for (var i in listView.ViewItems.selectedIndices) {
2116+ var index = listView.ViewItems.selectedIndices[i];
2117+ var listItem = findChild(listView, "listItem" + index);
2118+ compare(listItem.selected, true, "ListItem at index " + index + " is not selected!");
2119+ }
2120+ toggleSelectMode(listView, false);
2121+ listView.ViewItems.selectedIndices = [];
2122+ }
2123+
2124+ function test_toggle_selectMode_data() {
2125+ return [
2126+ {tag: "When not selected", index: 0, selected: false},
2127+ {tag: "When selected", index: 0, selected: true},
2128+ ]
2129+ }
2130+ function test_toggle_selectMode(data) {
2131+ var listItem = findChild(listView, "listItem" + data.index)
2132+ verify(listItem, "Cannot get test item");
2133+ listItem.selected = data.selected;
2134+ toggleSelectMode(listView, true);
2135+ // testItem is the 4th child, so index is 3
2136+ verify(findChild(listItem, "selection_panel" + data.index), "Cannot find selection panel");
2137+ compare(listItem.contentItem.enabled, true, "contentItem is not disabled.");
2138+ }
2139+
2140+ SignalSpy {
2141+ id: selectedSpy
2142+ signalName: "selectedChanged"
2143+ }
2144+
2145+ function test_toggle_selected_data() {
2146+ return [
2147+ // item = <test-item>, clickOk: <item-to-click-on>, offsetX|Y: <clickOn offset clicked>
2148+ {tag: "Click over selection", selectableHolder: testColumn, item: controlItem, clickOn: "listitem_select", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: false},
2149+ {tag: "Click over contentItem", selectableHolder: testColumn, item: controlItem, clickOn: "ListItemHolder", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: true},
2150+ {tag: "Click over control", selectableHolder: testColumn, item: controlItem, clickOn: "button_in_list", offsetX: units.gu(0.5), offsetY: units.gu(0.5), xfail: true},
2151+ ];
2152+ }
2153+ function test_toggle_selected(data) {
2154+ // make test item selectable first, so the panel is created
2155+ toggleSelectMode(data.selectableHolder, true);
2156+ // get the control to click on
2157+ var clickOn = findChild(data.item, data.clickOn);
2158+ verify(clickOn, "control to be clicked on not found");
2159+ // click on the selection and check selected changed
2160+ selectedSpy.target = data.item;
2161+ selectedSpy.clear();
2162+ mouseClick(clickOn, data.offsetX, data.offsetY);
2163+ if (data.xfail) {
2164+ expectFail(data.tag, "Clicking anywhere else but selection panel should not toggle selection state!");
2165+ }
2166+ selectedSpy.wait();
2167+ }
2168+
2169+ SignalSpy {
2170+ id: selectedIndicesSpy
2171+ signalName: "selectedIndicesChanged"
2172+ target: listView.ViewItems
2173+ }
2174+
2175+ function test_selectedIndices_change() {
2176+ // move to the end of the view
2177+ listView.positionViewAtEnd();
2178+ var listItem = findChild(listView, "listItem" + (listView.count - 1));
2179+ verify(listItem, "Cannot get tested list item");
2180+ toggleSelectMode(listView, true, false);
2181+ selectedSpy.target = listItem;
2182+ selectedSpy.clear();
2183+
2184+ listItem.selected = true;
2185+ selectedSpy.wait();
2186+ selectedIndicesSpy.wait();
2187+ }
2188+
2189+ function test_no_tug_when_selectable() {
2190+ movingSpy.target = testItem;
2191+ toggleSelectMode(testColumn, true);
2192+
2193+ // try to tug leading
2194+ movingSpy.clear();
2195+ swipeNoWait(testItem, centerOf(testItem).x, centerOf(testItem).y, units.gu(10), 0);
2196+ compare(movingSpy.count, 0, "No tug allowed when in selection mode");
2197+ }
2198+
2199+ function test_selectable_and_click() {
2200+ toggleSelectMode(testColumn, true);
2201+
2202+ clickSpy.target = testItem;
2203+ mouseClick(testItem, centerOf(testItem).x, centerOf(testItem).y);
2204+ clickSpy.wait();
2205+ }
2206+
2207+ function test_selectable_and_pressandhold() {
2208+ toggleSelectMode(testColumn, true);
2209+
2210+ pressAndHoldSpy.target = testItem;
2211+ mouseLongPress(testItem, centerOf(testItem).x, centerOf(testItem).y);
2212+ mouseRelease(testItem, centerOf(testItem).x, centerOf(testItem).y);
2213+ pressAndHoldSpy.wait();
2214+ }
2215+
2216+ function test_proper_attached_properties_data() {
2217+ return [
2218+ {tag: "Attached to ListView", item: listView},
2219+ {tag: "Attached to Column in Flickable", item: column},
2220+ ];
2221+ }
2222+ function test_proper_attached_properties(data) {
2223+ var listItem = findChild(data.item, "listItem0");
2224+ verify(listItem, "ListItem not found!");
2225+ toggleSelectMode(data.item, true);
2226+ // check if the selection mode was activated by looking after the first selection panel
2227+ var panel = findChild(listItem, "selection_panel0");
2228+ // turn off selection mode so we have a proper cleanup
2229+ toggleSelectMode(data.item, true);
2230+ verify(panel, "Selection panel not found, wrong attached property target?");
2231+ }
2232+
2233+ function test_dragmode_availability_data() {
2234+ return [
2235+ {tag: "Attached to Column", item: testColumn, lookupOn: testItem, xfail: true},
2236+ {tag: "Attached to ListView", item: listView, lookupOn: findChild(listView, "listItem0"), xfail: false},
2237+ ];
2238+ }
2239+ function test_dragmode_availability(data) {
2240+ if (data.xfail) {
2241+ ignoreWarning(warningFormat(85, 5, "QML Column: Dragging mode requires ListView"));
2242+ }
2243+ data.item.ViewItems.dragMode = true;
2244+ wait(400);
2245+ var panel = findChild(data.lookupOn, "drag_panel0");
2246+ if (data.xfail) {
2247+ expectFailContinue(data.tag, "There should be no drag handler shown!")
2248+ }
2249+ verify(panel, "No drag handler found!");
2250+ }
2251+
2252+ function test_drag_data() {
2253+ return [
2254+ {tag: "Live 0->1 OK", live: true, from: 0, to: 1, count: 1, accept: true, indices:[1,0,2,3,4]},
2255+ {tag: "Live 0->2 OK", live: true, from: 0, to: 2, count: 2, accept: true, indices:[1,2,0,3,4]},
2256+ {tag: "Live 0->3 OK", live: true, from: 0, to: 3, count: 3, accept: true, indices:[1,2,3,0,4]},
2257+ {tag: "Live 3->0 OK", live: true, from: 3, to: 0, count: 3, accept: true, indices:[3,0,1,2,4]},
2258+ // do not accept moves
2259+ {tag: "Live 0->1 NOK", live: true, from: 0, to: 1, count: 0, accept: false, indices:[0,1,2,3,4]},
2260+ {tag: "Live 0->2 NOK", live: true, from: 0, to: 2, count: 0, accept: false, indices:[0,1,2,3,4]},
2261+ {tag: "Live 0->3 NOK", live: true, from: 0, to: 3, count: 0, accept: false, indices:[0,1,2,3,4]},
2262+ {tag: "Live 3->0 NOK", live: true, from: 3, to: 0, count: 0, accept: false, indices:[0,1,2,3,4]},
2263+
2264+ // non-live updates
2265+ {tag: "Drop 0->1 OK", live: false, from: 0, to: 1, count: 1, accept: true, indices:[1,0,2,3,4]},
2266+ {tag: "Drop 0->2 OK", live: false, from: 0, to: 2, count: 1, accept: true, indices:[1,2,0,3,4]},
2267+ {tag: "Drop 0->3 OK", live: false, from: 0, to: 3, count: 1, accept: true, indices:[1,2,3,0,4]},
2268+ {tag: "Drop 3->0 OK", live: false, from: 3, to: 0, count: 1, accept: true, indices:[3,0,1,2,4]},
2269+ // do not accept moves
2270+ {tag: "Drop 0->1 NOK", live: false, from: 0, to: 1, count: 0, accept: false, indices:[0,1,2,3,4]},
2271+ {tag: "Drop 0->2 NOK", live: false, from: 0, to: 2, count: 0, accept: false, indices:[0,1,2,3,4]},
2272+ {tag: "Drop 0->3 NOK", live: false, from: 0, to: 3, count: 0, accept: false, indices:[0,1,2,3,4]},
2273+ {tag: "Drop 3->0 NOK", live: false, from: 3, to: 0, count: 0, accept: false, indices:[0,1,2,3,4]},
2274+ ];
2275+ }
2276+
2277+ function test_drag(data) {
2278+ var moveCount = 0;
2279+ function liveUpdate(event) {
2280+ if (event.status == ListItemDrag.Started) {
2281+ return;
2282+ }
2283+ if (data.accept) {
2284+ moveCount++;
2285+ listView.model.move(event.from, event.to, 1);
2286+ }
2287+ event.accept = data.accept;
2288+ }
2289+ function singleDrop(event) {
2290+ if (event.status == ListItemDrag.Dropped) {
2291+ if (data.accept) {
2292+ moveCount++;
2293+ listView.model.move(event.from, event.to, 1);
2294+ }
2295+ event.accept = data.accept;
2296+ } else if (event.status == ListItemDrag.Moving) {
2297+ event.accept = false;
2298+ }
2299+ }
2300+
2301+ objectModel.reset();
2302+ waitForRendering(listView);
2303+ listView.positionViewAtBeginning();
2304+ var func = data.live ? liveUpdate : singleDrop;
2305+ listView.ViewItems.dragUpdated.connect(func);
2306+
2307+ // enter drag mode
2308+ toggleDragMode(listView, true);
2309+ drag(listView, data.from, data.to);
2310+ compare(moveCount, data.count, "Move did not happen or more than one item was moved");
2311+ // compare array indices
2312+ for (var i in data.indices) {
2313+ compare(listView.model.get(i).data, data.indices[i], "data at index " + i + " is not the expected one");
2314+ }
2315+
2316+ // cleanup
2317+ listView.ViewItems.dragUpdated.disconnect(func);
2318+ toggleDragMode(listView, false);
2319+ }
2320+
2321+ // preconditions:
2322+ // the first 2 items cannot be dragged anywhere, nothing can be dropped in this area
2323+ // the 3-> items can be interchanged in between, cannot be dragged outside
2324+ function test_drag_restricted_data() {
2325+ return [
2326+ {tag: "[0,1] locked, drag 0->1 NOK", from: 0, to: 1, count: 0, indices: [0,1,2,3,4]},
2327+ {tag: "[0,1] locked, drag 1->2 NOK", from: 1, to: 2, count: 0, indices: [0,1,2,3,4]},
2328+ {tag: "[0,1] locked, drag 2->1 NOK", from: 2, to: 1, count: 0, indices: [0,1,2,3,4]},
2329+ {tag: "[0,1] locked, drag 2->0 NOK", from: 2, to: 0, count: 0, indices: [0,1,2,3,4]},
2330+ // drag
2331+ {tag: "[0,1] locked, drag 2->3 OK", from: 2, to: 3, count: 1, indices: [0,1,3,2,4]},
2332+ ];
2333+ }
2334+ function test_drag_restricted(data) {
2335+ var moveCount = 0;
2336+ function updateHandler(event) {
2337+ if (event.status == ListItemDrag.Started) {
2338+ if (event.from < 2) {
2339+ event.accept = false;
2340+ } else {
2341+ event.minimumIndex = 2;
2342+ }
2343+ } else if (event.status == ListItemDrag.Moving) {
2344+ listView.model.move(event.from, event.to, 1);
2345+ moveCount++;
2346+ }
2347+ }
2348+
2349+ objectModel.reset();
2350+ waitForRendering(listView);
2351+ listView.positionViewAtBeginning();
2352+ listView.ViewItems.dragUpdated.connect(updateHandler);
2353+
2354+ // enter drag mode
2355+ toggleDragMode(listView, true);
2356+ drag(listView, data.from, data.to);
2357+ compare(moveCount, data.count, "Move did not happen or more than one item was moved");
2358+ // compare array indices
2359+ for (var i in data.indices) {
2360+ compare(listView.model.get(i).data, data.indices[i], "data at index " + i + " is not the expected one");
2361+ }
2362+
2363+ // cleanup
2364+ listView.ViewItems.dragUpdated.disconnect(updateHandler);
2365+ toggleDragMode(listView, false);
2366+ }
2367+
2368+ function test_drag_keeps_selected_indexes_data() {
2369+ return [
2370+ {tag: "[0,1,2] selected, move 0->3, live", selected: [0,1,2], from: 0, to: 3, expected: [0,1,3], live: true},
2371+ {tag: "[1,2] selected, move 3->2, live", selected: [1,2], from: 3, to: 2, expected: [1,3], live: true},
2372+ {tag: "[1,2] selected, move 0->3, live", selected: [1,2], from: 0, to: 3, expected: [0,1], live: true},
2373+ {tag: "[1,2] selected, move 3->0, live", selected: [1,2], from: 3, to: 0, expected: [2,3], live: true},
2374+ // non-live updates
2375+ {tag: "[0,1,2] selected, move 0->3, non-live", selected: [0,1,2], from: 0, to: 3, expected: [0,1,3], live: false},
2376+ {tag: "[1,2] selected, move 3->2, non-live", selected: [1,2], from: 3, to: 2, expected: [1,3], live: false},
2377+ {tag: "[1,2] selected, move 0->3, non-live", selected: [1,2], from: 0, to: 3, expected: [0,1], live: false},
2378+ {tag: "[1,2] selected, move 3->0, non-live", selected: [1,2], from: 3, to: 0, expected: [2,3], live: false},
2379+ ];
2380+ }
2381+ function test_drag_keeps_selected_indexes(data) {
2382+ function updateHandler(event) {
2383+ if (event.status == ListItemDrag.Started) {
2384+ return;
2385+ }
2386+ if (data.live || event.status == ListItemDrag.Dropped) {
2387+ listView.model.move(event.from, event.to, 1);
2388+ } else {
2389+ event.accept = false;
2390+ }
2391+ }
2392+ objectModel.reset();
2393+ waitForRendering(listView);
2394+ listView.ViewItems.selectedIndices = data.selected;
2395+ listView.ViewItems.dragUpdated.connect(updateHandler);
2396+ toggleDragMode(listView, true);
2397+ drag(listView, data.from, data.to);
2398+ listView.ViewItems.dragUpdated.disconnect(updateHandler);
2399+ toggleDragMode(listView, false);
2400+
2401+ // NOTE: the selected indexes order is arbitrar and cannot be predicted by the test
2402+ // therefore we check the selected indexes presence in the expected list.
2403+ compare(listView.ViewItems.selectedIndices.length, data.expected.length, "The selected indexes and expected list size differs");
2404+ for (var i = 0; i < listView.ViewItems.selectedIndices.length; i++) {
2405+ var index = data.expected.indexOf(listView.ViewItems.selectedIndices[i]);
2406+ verify(index >= 0, "Index " + listView.ViewItems.selectedIndices[i] + " is not expected to be selected!");
2407+ }
2408+ }
2409+
2410+ // must run this immediately after the defaults are checked otherwise drag handler connected check will fail
2411+ function test_1_warn_missing_dragUpdated_signal_handler() {
2412+ ignoreWarning(warningFormat(121, 9, "QML ListView: ListView has no ViewItems.dragUpdated() signal handler implemented. No dragging will be possible."));
2413+ toggleDragMode(listView, true);
2414+ drag(listView, 0, 1);
2415+ toggleDragMode(listView, true);
2416+ }
2417+
2418+ DelegateModel {
2419+ id: delegateModel
2420+ delegate: ListItem {
2421+ objectName: "listItem" + index
2422+ Label { text: modelData }
2423+ }
2424+ }
2425+ ObjectModel {
2426+ id: objectModel2
2427+ Repeater {
2428+ model: 3
2429+ ListItem {
2430+ objectName: "listItem" + index
2431+ Label { text: modelData }
2432+ }
2433+ }
2434+ }
2435+ function test_warn_model_data() {
2436+ var list = [1,2,3,4,5,6,7,8,9,10];
2437+ return [
2438+ {tag: "number", model: 20, warning: "Dragging is only supported when using a QAbstractItemModel, ListModel or list."},
2439+ {tag: "list", model: list, warning: ""},
2440+ {tag: "ListModel", model: objectModel, warning: ""},
2441+ {tag: "DelegateModel with number", model: delegateModel, modelModel: 20, warning: "Dragging is only supported when using a QAbstractItemModel, ListModel or list."},
2442+ {tag: "DelegateModel with list", model: delegateModel, modelModel: list, warning: ""},
2443+ {tag: "DelegateModel with ListModel", model: delegateModel, modelModel: objectModel, warning: ""},
2444+ {tag: "ObjectModel", model: objectModel2, warning: ""},
2445+ ];
2446+ }
2447+ function test_warn_model(data) {
2448+ function dummyFunc() {}
2449+ if (data.warning !== "") {
2450+ ignoreWarning(warningFormat(121, 9, "QML ListView: " + data.warning));
2451+ }
2452+ listView.model = data.model;
2453+ if (typeof data.modelModel !== "undefined") {
2454+ listView.model.model = data.modelModel;
2455+ }
2456+ waitForRendering(listView, 500);
2457+ listView.ViewItems.dragUpdated.connect(dummyFunc);
2458+ toggleDragMode(listView, true);
2459+ toggleDragMode(listView, false);
2460+ listView.ViewItems.dragUpdated.disconnect(dummyFunc);
2461+ }
2462+
2463+ function test_rtl_actions_data() {
2464+ return [
2465+ {tag: "Leading actions", item: "listItem0", leading: true, expected: ["leading_1", "leading_2", "leading_3"]},
2466+ {tag: "Trailing actions", item: "listItem0", leading: false, expected: ["stockAction"]},
2467+ ];
2468+ }
2469+ function test_rtl_actions(data) {
2470+ listView.LayoutMirroring.enabled = true;
2471+ waitForRendering(listView, 500);
2472+ var listItem = findChild(listView, data.item);
2473+ swipe(listItem, centerOf(listItem).x, centerOf(listItem).y, data.leading ? -units.gu(20) : units.gu(20), 0);
2474+
2475+ // check if the action is visible
2476+ var panel = panelItem(listItem, data.leading);
2477+ verify(panel, "Panel not visible");
2478+ for (var i in data.expected) {
2479+ var actionItem = findChild(panel, data.expected[i]);
2480+ verify(actionItem, data.expected[i] + " action not found");
2481+ }
2482+ // dismiss
2483+ rebound(listItem);
2484+ listView.LayoutMirroring.enabled = false;
2485+ waitForRendering(listView, 500);
2486+ }
2487+
2488+ function test_rtl_selection_panel_position() {
2489+ listView.LayoutMirroring.enabled = true;
2490+ waitForRendering(listView, 500);
2491+ toggleSelectMode(listView, true);
2492+ // get the panel
2493+ var listItem = findChild(listView, "listItem0");
2494+ verify(listItem, "ListItem cannot be found");
2495+ var panel = findChild(listView, "selection_panel0");
2496+ verify(panel, "Selection panel not found.");
2497+ verify(listItem.mapFromItem(panel, panel.x, panel.y).x > centerOf(listItem).x, "Panel is not in its proper place!");
2498+ toggleSelectMode(listView, false);
2499+ listView.LayoutMirroring.enabled = false;
2500+ waitForRendering(listView, 500);
2501+ }
2502+
2503+ function test_rtl_drag_panel_position() {
2504+ listView.LayoutMirroring.enabled = true;
2505+ waitForRendering(listView, 500);
2506+ toggleDragMode(listView, true);
2507+ // get the panel
2508+ var listItem = findChild(listView, "listItem0");
2509+ verify(listItem, "ListItem cannot be found");
2510+ var panel = findChild(listView, "drag_panel0");
2511+ verify(panel, "Drag panel not found.");
2512+ verify(listItem.mapFromItem(panel, panel.x, panel.y).x < centerOf(listItem).x, "Panel is not in its proper place!");
2513+ toggleDragMode(listView, false);
2514+ listView.LayoutMirroring.enabled = false;
2515+ waitForRendering(listView, 500);
2516+ }
2517+
2518+ function test_listitem_actions_width_bug1465582_data() {
2519+ return [
2520+ {tag: "leading", dx: units.gu(5), action: "leading_1"},
2521+ {tag: "trailing", dx: -units.gu(5), action: "stockAction"},
2522+ ];
2523+ }
2524+ function test_listitem_actions_width_bug1465582(data) {
2525+ var height = testItem.height;
2526+ testItem.height = units.gu(15);
2527+
2528+ swipe(testItem, centerOf(testItem).x, centerOf(testItem).y, data.dx);
2529+
2530+ var icon = findChild(testItem, data.action);
2531+ verify(icon);
2532+ compare(icon.width, units.gu(5), "icon width should be the same no matter of the height set");
2533+
2534+ rebound(testItem);
2535+
2536+ // restore height
2537+ testItem.height = height;
2538+ }
2539+ }
2540+}
2541
2542=== added file 'tests/unit_x11/tst_components/tst_listitem_expansion.qml'
2543--- tests/unit_x11/tst_components/tst_listitem_expansion.qml 1970-01-01 00:00:00 +0000
2544+++ tests/unit_x11/tst_components/tst_listitem_expansion.qml 2015-09-04 10:38:07 +0000
2545@@ -0,0 +1,199 @@
2546+/*
2547+ * Copyright 2014-2015 Canonical Ltd.
2548+ *
2549+ * This program is free software; you can redistribute it and/or modify
2550+ * it under the terms of the GNU Lesser General Public License as published by
2551+ * the Free Software Foundation; version 3.
2552+ *
2553+ * This program is distributed in the hope that it will be useful,
2554+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2555+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2556+ * GNU Lesser General Public License for more details.
2557+ *
2558+ * You should have received a copy of the GNU Lesser General Public License
2559+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2560+ */
2561+
2562+import QtQuick 2.4
2563+import QtTest 1.0
2564+import Ubuntu.Test 1.0
2565+import Ubuntu.Components 1.3
2566+import Ubuntu.Components.Styles 1.3
2567+
2568+Item {
2569+ width: units.gu(40)
2570+ height: units.gu(71)
2571+
2572+ ListModel {
2573+ id: testModel
2574+ Component.onCompleted: reload()
2575+ function reload() {
2576+ clear();
2577+ for (var i = 0; i < 25; i++) {
2578+ append({data: i});
2579+ }
2580+ }
2581+ }
2582+
2583+ ListItemActions {
2584+ id: actions
2585+ actions: Action {
2586+ iconName: "delete"
2587+ }
2588+ }
2589+
2590+ property real expandedHeight
2591+
2592+ Column {
2593+ id: defColumn
2594+ anchors.fill: parent
2595+ spacing: units.gu(0.5)
2596+ ListItem {
2597+ id: defaults
2598+ }
2599+
2600+ ListView {
2601+ id: listView
2602+ width: parent.width
2603+ height: units.gu(28)
2604+ clip: true
2605+ model: testModel
2606+ delegate: ListItem {
2607+ objectName: "listItem" + index
2608+ Label { text: "ListItem " + modelData }
2609+ expansion.height: expandedHeight
2610+ leadingActions: actions
2611+ }
2612+ }
2613+ Flickable {
2614+ id: flickable
2615+ width: parent.width
2616+ height: units.gu(28)
2617+ clip: true
2618+ contentHeight: flickableColumn.height
2619+ Column {
2620+ id: flickableColumn
2621+ width: parent.width
2622+ Repeater {
2623+ model: testModel
2624+ ListItem {
2625+ objectName: "listItem" + index
2626+ Label { text: "ListItem " + modelData }
2627+ expansion.height: expandedHeight
2628+ leadingActions: actions
2629+ onClicked: expansion.expanded = !expansion.expanded
2630+ }
2631+ }
2632+ }
2633+ }
2634+ }
2635+
2636+ ListItemTestCase13 {
2637+ name: "ListItemExpansion"
2638+ when: windowShown
2639+
2640+ function init() {
2641+ expandedHeight = units.gu(12);
2642+ }
2643+
2644+ function cleanup() {
2645+ expandedHeight = 0;
2646+ // move both views to the top
2647+ listView.positionViewAtBeginning();
2648+ flickable.contentY = 0;
2649+
2650+ listView.ViewItems.expandedIndices = [];
2651+ flickableColumn.ViewItems.expandedIndices = [];
2652+ listView.ViewItems.expansionFlags = ViewItems.Exclusive;
2653+ flickableColumn.ViewItems.expansionFlags = ViewItems.Exclusive;
2654+ wait(200);
2655+ }
2656+
2657+ function initTestCase() {
2658+ compare(defaults.expansion.expanded, false, "Not expanded by default");
2659+ compare(defaults.expansion.height, 0, "No expansion height by default");
2660+ verify(defColumn.ViewItems.expandedIndices, "ViewItems.expandedIndices not defined");
2661+ verify(defColumn.ViewItems.expansionFlags, ViewItems.Exclusive);
2662+ }
2663+
2664+ function test_exclusive_expansion_data() {
2665+ return [
2666+ {tag: "ListView", test: listView, expand1: 0, expand2: 2},
2667+ {tag: "Flickable", test: flickableColumn, expand1: 0, expand2: 2},
2668+ ];
2669+ }
2670+ function test_exclusive_expansion(data) {
2671+ var item1 = findChild(data.test, "listItem" + data.expand1);
2672+ var item2 = findChild(data.test, "listItem" + data.expand2);
2673+ verify(item1);
2674+ verify(item2);
2675+
2676+ expand(item1, true);
2677+ compare(data.test.ViewItems.expandedIndices.length, 1, "More items expanded");
2678+ compare(data.test.ViewItems.expandedIndices[0], data.expand1, "More items expanded");
2679+ // expand the other one
2680+ expand(item2, true);
2681+ compare(data.test.ViewItems.expandedIndices.length, 1, "More items expanded");
2682+ compare(data.test.ViewItems.expandedIndices[0], data.expand2, "More items expanded");
2683+ }
2684+
2685+ function test_multiple_expanded_data() {
2686+ return [
2687+ {tag: "ListView", test: listView, expand1: 0, expand2: 2},
2688+ {tag: "Flickable", test: flickableColumn, expand1: 0, expand2: 2},
2689+ ];
2690+ }
2691+ function test_multiple_expanded(data) {
2692+ var item1 = findChild(data.test, "listItem" + data.expand1);
2693+ var item2 = findChild(data.test, "listItem" + data.expand2);
2694+ verify(item1);
2695+ verify(item2);
2696+
2697+ data.test.ViewItems.expansionFlags = 0;
2698+ expand(item1, true);
2699+ expand(item2, true);
2700+ compare(data.test.ViewItems.expandedIndices.length, 2, "Different amount of items expanded");
2701+ }
2702+
2703+ function test_locked_while_expanded_data() {
2704+ return [
2705+ {tag: "ListView, locked", test: listView, expand: 0, flags: 0, xfail: true},
2706+ {tag: "Flickable, locked", test: flickableColumn, expand: 0, flags: 0, xfail: true},
2707+ {tag: "ListView, unlocked", test: listView, expand: 0, flags: ViewItems.UnlockExpanded, xfail: false},
2708+ {tag: "Flickable, unlocked", test: flickableColumn, expand: 0, flags: ViewItems.UnlockExpanded, xfail: false},
2709+ ];
2710+ }
2711+ function test_locked_while_expanded(data) {
2712+ var item = findChild(data.test, "listItem" + data.expand);
2713+ verify(item);
2714+
2715+ data.test.ViewItems.expansionFlags = data.flags;
2716+ expand(item, true);
2717+ setupSpy(item, "contentMovementEnded");
2718+ swipeNoWait(item, centerOf(item).x, centerOf(item).y, units.gu(10), 0);
2719+ if (data.xfail) {
2720+ expectFailContinue(data.tag, "No panel swipe is allowed");
2721+ }
2722+ spyWait();
2723+ }
2724+
2725+ function test_colapse_on_external_click_data() {
2726+ return [
2727+ {tag: "ListView", test: listView, expand1: 1, clickOn: 0},
2728+ {tag: "Flickable", test: flickableColumn, expand1: 1, clickOn: 0},
2729+ ];
2730+ }
2731+ function test_colapse_on_external_click(data) {
2732+ var item = findChild(data.test, "listItem" + data.expand1);
2733+ var clickItem = findChild(data.test, "listItem" + data.clickOn);
2734+ verify(item);
2735+ verify(clickItem);
2736+ var collapsedHeight = item.height;
2737+
2738+ data.test.ViewItems.expansionFlags = ViewItems.CollapseOnOutsidePress;
2739+ expand(item, true);
2740+ mouseClick(clickItem, centerOf(clickItem).x, centerOf(clickItem).y);
2741+ tryCompareFunction(function() { return item.height; }, collapsedHeight, 500);
2742+ }
2743+ }
2744+}
2745
2746=== modified file 'tests/unit_x11/tst_components/tst_listitem_extras.qml'
2747--- tests/unit_x11/tst_components/tst_listitem_extras.qml 2015-08-31 07:50:45 +0000
2748+++ tests/unit_x11/tst_components/tst_listitem_extras.qml 2015-09-04 10:38:07 +0000
2749@@ -82,7 +82,7 @@
2750 }
2751 }
2752
2753- ListItemTestCase {
2754+ ListItemTestCase13 {
2755 name: "ListItemExtras"
2756 SignalSpy {
2757 id: clickSpy

Subscribers

People subscribed via source and target branches