Merge lp:~zsombi/ubuntu-ui-toolkit/listitem_expansion into lp:ubuntu-ui-toolkit/staging
- listitem_expansion
- Merge into staging
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 |
Related bugs: | |
Related blueprints: |
SDK: Design a new ListItem and layouts
(Undefined)
|
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
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
Zsombor Egri (zsombi) wrote : | # |
Must fix tst_listitem.qml to use the proper version.
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1636
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 1637. By Zsombor Egri
-
commented out import breaking tests restored
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1637
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
+ state: (listItemStyle.
The .completed property feels a bit.. hackish - why not use Component.
ViewItems.
ViewItems.
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?
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1640
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
You forgot to update components.api!
- 1641. By Zsombor Egri
-
API updated
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1641
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
Looking good. Thanks a lot!
Preview Diff
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 |
FAILED: Continuous integration, rev:1631 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2196/ jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/924 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/926 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/926/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/923
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2196/ rebuild
http://