Merge lp:~bzoltan/ubuntu-ui-toolkit/new_list_item into lp:ubuntu-ui-toolkit

Proposed by Zoltan Balogh
Status: Rejected
Rejected by: Zoltan Balogh
Proposed branch: lp:~bzoltan/ubuntu-ui-toolkit/new_list_item
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 4506 lines (+3994/-44) (has conflicts)
44 files modified
components.api (+37/-0)
debian/changelog (+29/-0)
examples/ubuntu-ui-toolkit-gallery/ListItems.qml (+33/-0)
export_modules_dir.sh (+3/-0)
modules/Ubuntu/Components/ListItemPanel.qml (+143/-0)
modules/Ubuntu/Components/ListItemSelectablePanel.qml (+49/-0)
modules/Ubuntu/Components/ListItems/Empty.qml (+5/-1)
modules/Ubuntu/Components/ListItems/Header.qml (+4/-1)
modules/Ubuntu/Components/ListItems/ThinDivider.qml (+0/-2)
modules/Ubuntu/Components/Themes/Ambiance/SheetForegroundStyle.qml (+2/-2)
modules/Ubuntu/Components/UbuntuListView11.qdoc (+42/-0)
modules/Ubuntu/Components/UbuntuListView11.qml (+32/-1)
modules/Ubuntu/Components/plugin/plugin.cpp (+8/-0)
modules/Ubuntu/Components/plugin/plugin.pro (+10/-2)
modules/Ubuntu/Components/plugin/propertychange_p.cpp (+76/-0)
modules/Ubuntu/Components/plugin/propertychange_p.h (+39/-0)
modules/Ubuntu/Components/plugin/quickutils.cpp (+23/-0)
modules/Ubuntu/Components/plugin/quickutils.h (+1/-0)
modules/Ubuntu/Components/plugin/uclistitem.cpp (+1116/-0)
modules/Ubuntu/Components/plugin/uclistitem.h (+92/-0)
modules/Ubuntu/Components/plugin/uclistitem_p.h (+177/-0)
modules/Ubuntu/Components/plugin/uclistitemoptions.cpp (+526/-0)
modules/Ubuntu/Components/plugin/uclistitemoptions.h (+75/-0)
modules/Ubuntu/Components/plugin/uclistitemoptions_p.h (+66/-0)
modules/Ubuntu/Components/qmldir (+2/-0)
tests/autopilot/ubuntuuitoolkit/environment.py (+19/-15)
tests/autopilot/ubuntuuitoolkit/fixture_setup.py (+19/-7)
tests/autopilot/ubuntuuitoolkit/tests/__init__.py (+14/-0)
tests/autopilot/ubuntuuitoolkit/tests/test_environment.py (+29/-4)
tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py (+90/-0)
tests/resources/listitems/InUListView.qml (+63/-0)
tests/resources/listitems/ListItemTest.qml (+186/-0)
tests/unit/tst_performance/ItemList.qml (+30/-0)
tests/unit/tst_performance/ListItemList.qml (+30/-0)
tests/unit/tst_performance/ListItemWithInlineOptionsList.qml (+39/-0)
tests/unit/tst_performance/ListItemWithLeadingOptionsList.qml (+41/-0)
tests/unit/tst_performance/ListItemWithOptionsList.qml (+41/-0)
tests/unit/tst_performance/ListItemsBaseList.qml (+29/-0)
tests/unit/tst_performance/ListItemsEmptyList.qml (+29/-0)
tests/unit/tst_performance/tst_performance.cpp (+8/-8)
tests/unit/tst_performance/tst_performance.pro (+8/-1)
tests/unit_x11/tst_components/tst_listitem.qml (+569/-0)
tests/unit_x11/tst_components/tst_listitem_ubuntulistview.qml (+159/-0)
tests/unit_x11/tst_components/tst_ubuntulistview11.qml (+1/-0)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~bzoltan/ubuntu-ui-toolkit/new_list_item
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Zsombor Egri Pending
Review via email: mp+234625@code.launchpad.net

Commit message

New List Item

Description of the change

New List Item

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

Fix changelog

1249. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/10-viewitem

1250. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/20-divider

1251. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/30-options

1252. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/35-options-panel-swipe

1253. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/40-visualize-options

1254. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/45-selected-option-rebinds

1255. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/50-custom-delegates

1256. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/55-snap-options

1257. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/60-action-value-type

1258. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/65-last-divider-invisible

1259. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/67-fix-disabled-listitem

1260. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/70-ubuntulistview-integration

1261. By Zoltan Balogh

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

1262. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/84-pressandhold

1263. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/86-exposing-status-and-connectedItem

1264. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/88-expose-panelColor

1265. By Zoltan Balogh

lp:~zsombi/ubuntu-ui-toolkit/90-update-gallery

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1266. By Zoltan Balogh

Updated lp:~zsombi/ubuntu-ui-toolkit/10-viewitem

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2014-09-05 05:49:46 +0000
3+++ components.api 2014-09-15 11:00:03 +0000
4@@ -673,6 +673,7 @@
5 UbuntuListView 1.1
6 UbuntuListView
7 property PullToRefresh pullToRefresh
8+ property ListItemOptions leadingOptions
9 UbuntuNumberAnimation 0.1 1.0
10 NumberAnimation
11 PerformanceOverlay 0.1 1.0
12@@ -814,6 +815,42 @@
13 name: "UCInverseMouse"
14 prototype: "UCMouse"
15 exports: ["InverseMouse 0.1", "InverseMouse 1.0"]
16+ name: "UCListItem"
17+ prototype: "UCStyledItemBase"
18+ exports: ["ListItem 1.1"]
19+ Property { name: "contentItem"; type: "UCListItemContent"; isReadonly: true; isPointer: true }
20+ Property { name: "divider"; type: "UCListItemDivider"; isReadonly: true; isPointer: true }
21+ Property { name: "leadingOptions"; type: "UCListItemOptions"; isPointer: true }
22+ Property { name: "trailingOptions"; type: "UCListItemOptions"; isPointer: true }
23+ Property { name: "pressed"; type: "bool"; isReadonly: true }
24+ Property { name: "selectable"; type: "bool" }
25+ Property { name: "selected"; type: "bool" }
26+ Property { name: "data"; type: "QObject"; isList: true; isReadonly: true }
27+ Property { name: "children"; type: "QQuickItem"; isList: true; isReadonly: true }
28+ Signal { name: "clicked" }
29+ Signal { name: "pressAndHold" }
30+ name: "UCListItemContent"
31+ prototype: "QQuickItem"
32+ Property { name: "color"; type: "QColor" }
33+ Property { name: "pressedColor"; type: "QColor" }
34+ name: "UCListItemDivider"
35+ prototype: "QObject"
36+ Property { name: "visible"; type: "bool" }
37+ Property { name: "leftMargin"; type: "double" }
38+ Property { name: "rightMargin"; type: "double" }
39+ Property { name: "colorFrom"; type: "QColor" }
40+ Property { name: "colorTo"; type: "QColor" }
41+ name: "UCListItemOptions"
42+ prototype: "QObject"
43+ exports: ["ListItemOptions 1.1"]
44+ name: "Status"
45+ Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
46+ Property { name: "options"; type: "QObject"; isList: true; isReadonly: true }
47+ Property { name: "panelItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
48+ Property { name: "status"; type: "Status"; isReadonly: true }
49+ Property { name: "connectedItem"; type: "UCListItem"; isReadonly: true; isPointer: true }
50+ Property { name: "panelColor"; type: "QColor" }
51+ Property { name: "iconColor"; type: "QColor" }
52 name: "UCMouse"
53 prototype: "QObject"
54 exports: ["Mouse 0.1", "Mouse 1.0"]
55
56=== modified file 'debian/changelog'
57--- debian/changelog 2014-09-08 17:53:18 +0000
58+++ debian/changelog 2014-09-15 11:00:03 +0000
59@@ -1,3 +1,4 @@
60+<<<<<<< TREE
61 ubuntu-ui-toolkit (1.1.1239+14.10.20140908-0ubuntu1) utopic; urgency=medium
62
63 [ Zsombor Egri ]
64@@ -45,6 +46,34 @@
65
66 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 08 Sep 2014 17:53:18 +0000
67
68+=======
69+ubuntu-ui-toolkit (1.1.1247+14.10.20140904-0ubuntu2) UNRELEASED; urgency=medium
70+
71+ [ Zsombor Egri ]
72+ * New ListItem component
73+ * Sheet header button space reservation made button presence
74+ dependent. Fixes: LP: #1356636
75+ * Tweaks for ListItem dividers. Header margins and font weight
76+ updated.
77+ * Exclude UbuntuColors dumped erroneously by qmlplugindump as it
78+ would be a C++ type.
79+ * Organizer audible reminder wasn't updated when Alarm sound
80+ changed.
81+
82+ [ Zoltán Balogh ]
83+ * Updated test plan execution for RTM images and for new phablet
84+ tools.
85+
86+ [ Christian Dywan ]
87+ * Specify /sbin when calling initctl just like autopilot does.
88+
89+ [ Leo Arias ]
90+ * On autopilot tests launched by upstart, use the local modules
91+ if running from a branch.
92+
93+ -- Zoltán Balogh <zoltan@bakter.hu> Mon, 15 Sep 2014 10:50:49 +0300
94+
95+>>>>>>> MERGE-SOURCE
96 ubuntu-ui-toolkit (1.1.1227+14.10.20140904-0ubuntu1) utopic; urgency=medium
97
98 [ Zsombor Egri ]
99
100=== modified file 'examples/ubuntu-ui-toolkit-gallery/ListItems.qml'
101--- examples/ubuntu-ui-toolkit-gallery/ListItems.qml 2014-07-18 12:12:05 +0000
102+++ examples/ubuntu-ui-toolkit-gallery/ListItems.qml 2014-09-15 11:00:03 +0000
103@@ -22,6 +22,39 @@
104 objectName: "listItemsTemplate"
105
106 ListItemsSection {
107+ title: i18n.tr("ListItem - without layout")
108+ className: "ListItem"
109+ clip: true
110+ delegate: Toolkit.ListItem {
111+ leadingOptions: Toolkit.ListItemOptions {
112+ Toolkit.Action {
113+ iconName: "delete"
114+ }
115+ }
116+ trailingOptions: Toolkit.ListItemOptions {
117+ Toolkit.Action {
118+ iconName: "edit"
119+ }
120+ Toolkit.Action {
121+ iconName: "contact"
122+ }
123+ Toolkit.Action {
124+ iconName: "email"
125+ }
126+ }
127+
128+ Toolkit.Label {
129+ anchors {
130+ top: parent.top
131+ bottom: parent.bottom
132+ }
133+ verticalAlignment: Text.AlignVCenter
134+ text: "Plain ListItem #" + modelData
135+ }
136+ }
137+ }
138+
139+ ListItemsSection {
140 title: i18n.tr("Standard")
141 className: "Standard"
142 delegate: ListItem.Standard {
143
144=== modified file 'export_modules_dir.sh'
145--- export_modules_dir.sh 2014-07-14 17:34:44 +0000
146+++ export_modules_dir.sh 2014-09-15 11:00:03 +0000
147@@ -18,3 +18,6 @@
148 export QML_IMPORT_PATH=$PWD/modules
149 export QML2_IMPORT_PATH=$PWD/modules
150 export UBUNTU_UI_TOOLKIT_THEMES_PATH=$PWD/modules
151+/sbin/initctl set-env --global QML_IMPORT_PATH=$PWD/modules
152+/sbin/initctl set-env --global QML2_IMPORT_PATH=$PWD/modules
153+/sbin/initctl set-env --global UBUNTU_UI_TOOLKIT_THEMES_PATH=$PWD/modules
154
155=== added file 'modules/Ubuntu/Components/ListItemPanel.qml'
156--- modules/Ubuntu/Components/ListItemPanel.qml 1970-01-01 00:00:00 +0000
157+++ modules/Ubuntu/Components/ListItemPanel.qml 2014-09-15 11:00:03 +0000
158@@ -0,0 +1,143 @@
159+/*
160+ * Copyright 2014 Canonical Ltd.
161+ *
162+ * This program is free software; you can redistribute it and/or modify
163+ * it under the terms of the GNU Lesser General Public License as published by
164+ * the Free Software Foundation; version 3.
165+ *
166+ * This program is distributed in the hope that it will be useful,
167+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
168+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
169+ * GNU Lesser General Public License for more details.
170+ *
171+ * You should have received a copy of the GNU Lesser General Public License
172+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
173+ */
174+
175+import QtQuick 2.2
176+import Ubuntu.Components 1.1
177+
178+/*
179+ This component is the holder of the ListItem options.
180+ */
181+Item {
182+ id: panel
183+ width: optionsRow.childrenRect.width
184+
185+ readonly property Item contentItem: parent ? parent.contentItem : null
186+
187+ /*
188+ Index of the ListItem, if the ListItem is inside a ListView or has been
189+ created using a Repeater.
190+ */
191+ property int listItemIndex: -1
192+
193+ /*
194+ Specifies whether the panel is used to visualize leading or trailing options.
195+ */
196+ property bool leadingPanel: false
197+ /*
198+ The delegate to be used to visualize the options
199+ */
200+ property Component delegate
201+
202+ /*
203+ Options
204+ */
205+ property var optionList
206+
207+ /*
208+ Panel and icon colors
209+ */
210+ property color panelColor
211+ property color iconColor
212+
213+ /*
214+ Emitted when action is triggered
215+ */
216+ signal selected()
217+
218+ // fire selected action when parent is removed
219+ onParentChanged: {
220+ if (!parent && selectedAction) {
221+ selectedAction.triggered(listItemIndex >= 0 ? listItemIndex : null);
222+ selectedAction = null;
223+ }
224+ }
225+ property Action selectedAction: null
226+
227+ anchors {
228+ left: contentItem ? (leadingPanel ? undefined : contentItem.right) : undefined
229+ right: contentItem ? (leadingPanel ? contentItem.left : undefined) : undefined
230+ top: contentItem ? contentItem.top : undefined
231+ bottom: contentItem ? contentItem.bottom : undefined
232+ }
233+
234+ Rectangle {
235+ anchors.fill: parent
236+ // FIXME: use Palette colors instead when available
237+ color: (panel.panelColor != "#000000") ? panel.panelColor : (leadingPanel ? UbuntuColors.red : "#00000000")
238+ }
239+
240+ Row {
241+ id: optionsRow
242+ anchors {
243+ left: parent.left
244+ top: parent.top
245+ bottom: parent.bottom
246+ leftMargin: spacing
247+ }
248+
249+ property real maxItemWidth: panel.parent ? (panel.parent.width / panel.optionList.length) : 0
250+
251+ Repeater {
252+ model: panel.optionList
253+ AbstractButton {
254+ action: modelData
255+ width: (!visible || !enabled) ?
256+ 0 : MathUtils.clamp(delegateLoader.item ? delegateLoader.item.width : 0, height, optionsRow.maxItemWidth)
257+ anchors {
258+ top: parent.top
259+ bottom: parent.bottom
260+ }
261+
262+ function trigger() {
263+ // save the action as we trigger when the rebound animation is over
264+ // to make sure we properly clean up the blockade of teh Flickables
265+ panel.selectedAction = action;
266+ panel.selected();
267+ }
268+
269+ Loader {
270+ id: delegateLoader
271+ height: parent.height
272+ sourceComponent: panel.delegate ? panel.delegate : defaultDelegate
273+ property Action option: modelData
274+ property int index: index
275+ onItemChanged: {
276+ // this is needed only for testing purposes
277+ if (item && item.objectName === "") {
278+ item.objectName = "list_option_" + index
279+ }
280+ }
281+ }
282+ }
283+ }
284+ }
285+
286+ Component {
287+ id: defaultDelegate
288+ Item {
289+ width: height
290+ Icon {
291+ width: units.gu(2.5)
292+ height: width
293+ name: option.iconName
294+ // FIXME: use Palette colors instead when available
295+ color: (panel.iconColor != "#000000") ?
296+ panel.iconColor : (panel.leadingPanel ? "white" : UbuntuColors.lightGrey)
297+ anchors.centerIn: parent
298+ }
299+ }
300+ }
301+}
302
303=== added file 'modules/Ubuntu/Components/ListItemSelectablePanel.qml'
304--- modules/Ubuntu/Components/ListItemSelectablePanel.qml 1970-01-01 00:00:00 +0000
305+++ modules/Ubuntu/Components/ListItemSelectablePanel.qml 2014-09-15 11:00:03 +0000
306@@ -0,0 +1,49 @@
307+/*
308+ * Copyright 2014 Canonical Ltd.
309+ *
310+ * This program is free software; you can redistribute it and/or modify
311+ * it under the terms of the GNU Lesser General Public License as published by
312+ * the Free Software Foundation; version 3.
313+ *
314+ * This program is distributed in the hope that it will be useful,
315+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
316+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
317+ * GNU Lesser General Public License for more details.
318+ *
319+ * You should have received a copy of the GNU Lesser General Public License
320+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
321+ */
322+
323+import QtQuick 2.2
324+import Ubuntu.Components 1.1
325+
326+/*
327+ This component is the holder of the ListItem selection checkbox.
328+ */
329+Rectangle {
330+ id: panel
331+ width: height
332+
333+ /*
334+ Internally used to link to the list item's content
335+ */
336+ readonly property Item contentItem: parent ? parent.contentItem : null
337+
338+ /*
339+ Exposed checked property linked to the listItem's selected
340+ */
341+ property alias checked: checkbox.checked
342+
343+ color: checked ? contentItem.pressedColor : contentItem.color
344+
345+ anchors {
346+ right: contentItem ? contentItem.left : undefined
347+ top: contentItem ? contentItem.top : undefined
348+ bottom: contentItem ? contentItem.bottom : undefined
349+ }
350+
351+ CheckBox {
352+ id: checkbox
353+ anchors.centerIn: parent
354+ }
355+}
356
357=== modified file 'modules/Ubuntu/Components/ListItems/Empty.qml'
358--- modules/Ubuntu/Components/ListItems/Empty.qml 2014-08-08 14:30:27 +0000
359+++ modules/Ubuntu/Components/ListItems/Empty.qml 2014-09-15 11:00:03 +0000
360@@ -307,7 +307,11 @@
361
362 ThinDivider {
363 id: bottomDividerLine
364- anchors.bottom: parent.bottom
365+ anchors {
366+ bottom: parent.bottom
367+ leftMargin: units.gu(2)
368+ rightMargin: units.gu(2)
369+ }
370 visible: showDivider && !priv.removed
371 }
372
373
374=== modified file 'modules/Ubuntu/Components/ListItems/Header.qml'
375--- modules/Ubuntu/Components/ListItems/Header.qml 2014-04-20 19:25:12 +0000
376+++ modules/Ubuntu/Components/ListItems/Header.qml 2014-09-15 11:00:03 +0000
377@@ -60,11 +60,12 @@
378 LabelVisual {
379 id: label
380 fontSize: "medium"
381+ font.weight: Text.Normal
382 anchors {
383 verticalCenter: parent.verticalCenter
384 left: parent.left
385 right: parent.right
386- margins: units.gu(1)
387+ margins: units.gu(2)
388 }
389 }
390
391@@ -73,6 +74,8 @@
392 left: parent.left
393 right: parent.right
394 bottom: parent.bottom
395+ leftMargin: units.gu(2)
396+ rightMargin: units.gu(2)
397 }
398 }
399 }
400
401=== modified file 'modules/Ubuntu/Components/ListItems/ThinDivider.qml'
402--- modules/Ubuntu/Components/ListItems/ThinDivider.qml 2014-08-27 09:26:59 +0000
403+++ modules/Ubuntu/Components/ListItems/ThinDivider.qml 2014-09-15 11:00:03 +0000
404@@ -49,8 +49,6 @@
405 anchors {
406 left: (parent) ? parent.left : null
407 right: (parent) ? parent.right : null
408- leftMargin: units.gu(2)
409- rightMargin: units.gu(2)
410 }
411 height: (visible) ? units.dp(2) : 0
412 // a private property to catch theme background color change
413
414=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/SheetForegroundStyle.qml'
415--- modules/Ubuntu/Components/Themes/Ambiance/SheetForegroundStyle.qml 2014-04-23 08:50:20 +0000
416+++ modules/Ubuntu/Components/Themes/Ambiance/SheetForegroundStyle.qml 2014-09-15 11:00:03 +0000
417@@ -60,7 +60,7 @@
418 top: parent.top
419 bottom: parent.bottom
420 }
421- width: visuals.buttonContainerWidth
422+ width: styledItem.leftButton ? visuals.buttonContainerWidth : 0
423 Component.onCompleted: header.updateButton(styledItem.leftButton, leftButtonContainer)
424 }
425
426@@ -71,7 +71,7 @@
427 top: parent.top
428 bottom: parent.bottom
429 }
430- width: visuals.buttonContainerWidth
431+ width: styledItem.rightButton ? visuals.buttonContainerWidth : 0
432 Component.onCompleted: header.updateButton(styledItem.rightButton, rightButtonContainer)
433 }
434
435
436=== modified file 'modules/Ubuntu/Components/UbuntuListView11.qdoc'
437--- modules/Ubuntu/Components/UbuntuListView11.qdoc 2014-05-22 07:05:47 +0000
438+++ modules/Ubuntu/Components/UbuntuListView11.qdoc 2014-09-15 11:00:03 +0000
439@@ -36,3 +36,45 @@
440 }
441 \endqml
442 */
443+
444+/*!
445+ * \qmlproperty ListItemOptions UbuntuListView::leadingOptions
446+ * The property holds the default leading options defined by the UbuntuListView.
447+ * The option list can be overridden either by specifying a different \l ListItemOptions
448+ * instance to the property or doing the same in \l ListItem itself. The default
449+ * option list contains a single delete action, which removes the model data from
450+ * the triggered index.
451+ * \qml
452+ * UbuntuListView {
453+ * width: units.gu(30)
454+ * height: units.gu(24)
455+ * leadingOptions: ListItemOptions {
456+ * Action {
457+ * iconName: "edit"
458+ * onTriggered: console.log("edit triggered on item index", value)
459+ * }
460+ * }
461+ * model: 10
462+ * delegate: ListItem {
463+ * }
464+ * }
465+ * \endqml
466+ * And its equivalent:
467+ * \qml
468+ * UbuntuListView {
469+ * width: units.gu(30)
470+ * height: units.gu(24)
471+ * model: 10
472+ * delegate: ListItem {
473+ * leadingOptions: ListItemOptions {
474+ * Action {
475+ * iconName: "edit"
476+ * onTriggered: console.log("edit triggered on item index", value)
477+ * }
478+ * }
479+ * }
480+ * }
481+ * \endqml
482+ * \note Setting \c leadingOptions on \l ListItem itself has precedence over the
483+ * UbuntuListView property.
484+ */
485
486=== modified file 'modules/Ubuntu/Components/UbuntuListView11.qml'
487--- modules/Ubuntu/Components/UbuntuListView11.qml 2014-06-04 07:54:35 +0000
488+++ modules/Ubuntu/Components/UbuntuListView11.qml 2014-09-15 11:00:03 +0000
489@@ -15,16 +15,47 @@
490 */
491
492 import QtQuick 2.2
493+import Ubuntu.Components 1.1
494
495 // documentation in UbuntuListView11.qdoc
496 UbuntuListView {
497-
498+ id: listView
499 /*!
500 \internal
501 \qmlproperty PullToRefresh pullToRefresh
502 */
503 property alias pullToRefresh: refreshItem
504
505+ /*!
506+ \internal
507+ \qmlproperty ListItemOptions leadingOptions
508+ Use binding so we can set it to each ListItem as binding!
509+ */
510+ property ListItemOptions leadingOptions: stockLeadingOption
511+ ListItemOptions {
512+ id: stockLeadingOption
513+ options: Action {
514+ iconName: "delete"
515+ onTriggered: {
516+ // delete the index from model if possible
517+ if (Object.prototype.toString.call(listView.model) === "[object Number]") {
518+ // the model is a number, decrement it
519+ listView.model = listView.count - 1;
520+ } else if (Object.prototype.toString.call(listView.model) === "[object Array]") {
521+ // the model is an array, remove the item from index
522+ var array = listView.model;
523+ array.splice(value, 1);
524+ listView.model = array;
525+ } else {
526+ // we can only have an object
527+ if (listView.model.hasOwnProperty("remove")) {
528+ listView.model.remove(value, 1);
529+ }
530+ }
531+ }
532+ }
533+ }
534+
535 PullToRefresh {
536 objectName: "listview_pulltorefresh"
537 id: refreshItem
538
539=== modified file 'modules/Ubuntu/Components/plugin/plugin.cpp'
540--- modules/Ubuntu/Components/plugin/plugin.cpp 2014-09-05 05:05:54 +0000
541+++ modules/Ubuntu/Components/plugin/plugin.cpp 2014-09-15 11:00:03 +0000
542@@ -50,6 +50,9 @@
543 #include "ucinversemouse.h"
544 #include "sortfiltermodel.h"
545 #include "ucstyleditembase.h"
546+#include "uclistitem.h"
547+#include "uclistitem_p.h"
548+#include "uclistitemoptions.h"
549
550 #include <sys/types.h>
551 #include <unistd.h>
552@@ -157,6 +160,11 @@
553 qmlRegisterType<QSortFilterProxyModelQML>(uri, 1, 1, "SortFilterModel");
554 qmlRegisterUncreatableType<FilterBehavior>(uri, 1, 1, "FilterBehavior", "Not instantiable");
555 qmlRegisterUncreatableType<SortBehavior>(uri, 1, 1, "SortBehavior", "Not instantiable");
556+ // ListItem and related types
557+ qmlRegisterType<UCListItem, 1>(uri, 1, 1, "ListItem");
558+ qmlRegisterType<UCListItemContent>();
559+ qmlRegisterType<UCListItemDivider>();
560+ qmlRegisterType<UCListItemOptions, 1>(uri, 1, 1, "ListItemOptions");
561 }
562
563 void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
564
565=== modified file 'modules/Ubuntu/Components/plugin/plugin.pro'
566--- modules/Ubuntu/Components/plugin/plugin.pro 2014-09-02 09:39:09 +0000
567+++ modules/Ubuntu/Components/plugin/plugin.pro 2014-09-15 11:00:03 +0000
568@@ -65,7 +65,12 @@
569 ucmouse.h \
570 unixsignalhandler_p.h \
571 ucstyleditembase.h \
572- ucstyleditembase_p.h
573+ ucstyleditembase_p.h \
574+ uclistitem.h \
575+ uclistitem_p.h \
576+ uclistitemoptions.h \
577+ uclistitemoptions_p.h \
578+ propertychange_p.h
579
580 SOURCES += plugin.cpp \
581 uctheme.cpp \
582@@ -99,7 +104,10 @@
583 ucurihandler.cpp \
584 ucmousefilters.cpp \
585 unixsignalhandler_p.cpp \
586- ucstyleditembase.cpp
587+ ucstyleditembase.cpp \
588+ uclistitem.cpp \
589+ uclistitemoptions.cpp \
590+ propertychange_p.cpp
591
592 # adapters
593 SOURCES += adapters/alarmsadapter_organizer.cpp
594
595=== added file 'modules/Ubuntu/Components/plugin/propertychange_p.cpp'
596--- modules/Ubuntu/Components/plugin/propertychange_p.cpp 1970-01-01 00:00:00 +0000
597+++ modules/Ubuntu/Components/plugin/propertychange_p.cpp 2014-09-15 11:00:03 +0000
598@@ -0,0 +1,76 @@
599+/*
600+ * Copyright 2014 Canonical Ltd.
601+ *
602+ * This program is free software; you can redistribute it and/or modify
603+ * it under the terms of the GNU Lesser General Public License as published by
604+ * the Free Software Foundation; version 3.
605+ *
606+ * This program is distributed in the hope that it will be useful,
607+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
608+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
609+ * GNU Lesser General Public License for more details.
610+ *
611+ * You should have received a copy of the GNU Lesser General Public License
612+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
613+ */
614+
615+#include "propertychange_p.h"
616+
617+#include <QtQml/private/qqmlabstractbinding_p.h>
618+#define foreach Q_FOREACH //workaround to fix private includes
619+#include <QtQml/private/qqmlbinding_p.h> // for QmlBinding
620+#undef foreach
621+
622+/*
623+ * The class is used to save properties and their bindings while the property is
624+ * altered temporarily.
625+ */
626+PropertyChange::PropertyChange(QObject *item, const char *property)
627+ : m_backedUp(false)
628+ , qmlProperty(item, property, qmlContext(item))
629+{
630+}
631+PropertyChange::~PropertyChange()
632+{
633+ restore(this);
634+}
635+
636+/*
637+ * Sets a value to the property. Will back up the original values if it wasn't yet.
638+ * This function can be called many times, it will not destroy the backed up value/binding.
639+ */
640+void PropertyChange::setValue(PropertyChange *change, const QVariant &value)
641+{
642+ if (!change) {
643+ return;
644+ }
645+ if (!change->m_backedUp) {
646+ change->backup.first = QQmlPropertyPrivate::setBinding(change->qmlProperty, 0);
647+ change->backup.second = change->qmlProperty.read();
648+ change->m_backedUp = true;
649+ }
650+ change->qmlProperty.write(value);
651+}
652+
653+/*
654+ * Restore backed up value or binding.
655+ */
656+void PropertyChange::restore(PropertyChange *change)
657+{
658+ if (!change) {
659+ return;
660+ }
661+ if (change->m_backedUp) {
662+ // if there was a binding, restore it
663+ if (change->backup.first) {
664+ QQmlAbstractBinding *prevBinding = QQmlPropertyPrivate::setBinding(change->qmlProperty, change->backup.first);
665+ if (prevBinding && prevBinding != change->backup.first) {
666+ prevBinding->destroy();
667+ }
668+ } else {
669+ // there was no binding, restore previous value
670+ change->qmlProperty.write(change->backup.second);
671+ }
672+ change->m_backedUp = false;
673+ }
674+}
675
676=== added file 'modules/Ubuntu/Components/plugin/propertychange_p.h'
677--- modules/Ubuntu/Components/plugin/propertychange_p.h 1970-01-01 00:00:00 +0000
678+++ modules/Ubuntu/Components/plugin/propertychange_p.h 2014-09-15 11:00:03 +0000
679@@ -0,0 +1,39 @@
680+/*
681+ * Copyright 2014 Canonical Ltd.
682+ *
683+ * This program is free software; you can redistribute it and/or modify
684+ * it under the terms of the GNU Lesser General Public License as published by
685+ * the Free Software Foundation; version 3.
686+ *
687+ * This program is distributed in the hope that it will be useful,
688+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
689+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
690+ * GNU Lesser General Public License for more details.
691+ *
692+ * You should have received a copy of the GNU Lesser General Public License
693+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
694+ */
695+
696+#ifndef PROPERTYCHANGE_P_H
697+#define PROPERTYCHANGE_P_H
698+
699+#include <QtCore/QVariant>
700+#include <QtCore/QObject>
701+#include <QtQml/QQmlProperty>
702+
703+class QQmlAbstractBinding;
704+class PropertyChange
705+{
706+public:
707+ PropertyChange(QObject *item, const char *property);
708+ ~PropertyChange();
709+
710+ static void setValue(PropertyChange* change, const QVariant &value);
711+ static void restore(PropertyChange* change);
712+private:
713+ bool m_backedUp;
714+ QQmlProperty qmlProperty;
715+ QPair<QQmlAbstractBinding*, QVariant> backup;
716+};
717+
718+#endif // PROPERTYCHANGE_P_H
719
720=== modified file 'modules/Ubuntu/Components/plugin/quickutils.cpp'
721--- modules/Ubuntu/Components/plugin/quickutils.cpp 2014-06-10 11:47:09 +0000
722+++ modules/Ubuntu/Components/plugin/quickutils.cpp 2014-09-15 11:00:03 +0000
723@@ -135,6 +135,29 @@
724 return result.left(result.indexOf("_QML"));
725 }
726
727+/*!
728+ * \internal
729+ * The function checks whether an item inherits a given class name.
730+ */
731+bool QuickUtils::inherits(QObject *object, const QString &fromClass)
732+{
733+ if (!object || fromClass.isEmpty()) {
734+ return false;
735+ }
736+ const QMetaObject *mo = object->metaObject();
737+ QString className;
738+ while (mo) {
739+ className = mo->className();
740+ className = className.left(className.indexOf("_QML"));
741+ if (className == fromClass) {
742+ return true;
743+ }
744+ mo = mo->superClass();
745+ }
746+ return false;
747+}
748+
749+
750
751 /*!
752 * \internal
753
754=== modified file 'modules/Ubuntu/Components/plugin/quickutils.h'
755--- modules/Ubuntu/Components/plugin/quickutils.h 2014-06-10 11:47:09 +0000
756+++ modules/Ubuntu/Components/plugin/quickutils.h 2014-09-15 11:00:03 +0000
757@@ -44,6 +44,7 @@
758 bool touchScreenAvailable() const;
759
760 Q_INVOKABLE static QString className(QObject *item);
761+ Q_REVISION(1) Q_INVOKABLE static bool inherits(QObject *object, const QString &fromClass);
762 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);
763
764 Q_SIGNALS:
765
766=== added file 'modules/Ubuntu/Components/plugin/uclistitem.cpp'
767--- modules/Ubuntu/Components/plugin/uclistitem.cpp 1970-01-01 00:00:00 +0000
768+++ modules/Ubuntu/Components/plugin/uclistitem.cpp 2014-09-15 11:00:03 +0000
769@@ -0,0 +1,1116 @@
770+/*
771+ * Copyright 2014 Canonical Ltd.
772+ *
773+ * This program is free software; you can redistribute it and/or modify
774+ * it under the terms of the GNU Lesser General Public License as published by
775+ * the Free Software Foundation; version 3.
776+ *
777+ * This program is distributed in the hope that it will be useful,
778+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
779+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
780+ * GNU Lesser General Public License for more details.
781+ *
782+ * You should have received a copy of the GNU Lesser General Public License
783+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
784+ */
785+
786+#include "ucunits.h"
787+#include "uctheme.h"
788+#include "uclistitem.h"
789+#include "uclistitem_p.h"
790+#include "uclistitemoptions.h"
791+#include "uclistitemoptions_p.h"
792+#include "ucubuntuanimation.h"
793+#include "propertychange_p.h"
794+#include "i18n.h"
795+#include "quickutils.h"
796+#include "plugin.h"
797+#include "ucmouse.h"
798+#include <QtQml/QQmlInfo>
799+#include <QtQuick/private/qquickitem_p.h>
800+#include <QtQuick/private/qquickflickable_p.h>
801+#include <QtQuick/private/qquickpositioners_p.h>
802+
803+#include <QtQml/private/qqmlabstractbinding_p.h>
804+#define foreach Q_FOREACH //workaround to fix private includes
805+#include <QtQml/private/qqmlbinding_p.h> // for QmlBinding
806+#undef foreach
807+
808+
809+#define MIN(x, y) ((x < y) ? x : y)
810+#define MAX(x, y) ((x > y) ? x : y)
811+#define CLAMP(v, min, max) (min <= max) ? MAX(min, MIN(v, max)) : MAX(max, MIN(v, min))
812+
813+QColor getPaletteColor(const char *profile, const char *color)
814+{
815+ QColor result;
816+ QObject *palette = UCTheme::instance().palette();
817+ if (palette) {
818+ QObject *paletteProfile = palette->property(profile).value<QObject*>();
819+ if (paletteProfile) {
820+ result = paletteProfile->property(color).value<QColor>();
821+ }
822+ }
823+ return result;
824+}
825+/******************************************************************************
826+ * Divider
827+ */
828+UCListItemDivider::UCListItemDivider(QObject *parent)
829+ : QObject(parent)
830+ , m_visible(true)
831+ , m_lastItem(false)
832+ , m_leftMarginChanged(false)
833+ , m_rightMarginChanged(false)
834+ , m_colorFromChanged(false)
835+ , m_colorToChanged(false)
836+ , m_thickness(0)
837+ , m_leftMargin(0)
838+ , m_rightMargin(0)
839+ , m_listItem(0)
840+{
841+ connect(&UCUnits::instance(), &UCUnits::gridUnitChanged, this, &UCListItemDivider::unitsChanged);
842+ connect(&UCTheme::instance(), &UCTheme::paletteChanged, this, &UCListItemDivider::paletteChanged);
843+ unitsChanged();
844+ paletteChanged();
845+}
846+UCListItemDivider::~UCListItemDivider()
847+{
848+}
849+
850+void UCListItemDivider::init(UCListItem *listItem)
851+{
852+ QQml_setParent_noEvent(this, listItem);
853+ m_listItem = UCListItemPrivate::get(listItem);
854+}
855+
856+void UCListItemDivider::unitsChanged()
857+{
858+ m_thickness = UCUnits::instance().dp(2);
859+ if (!m_leftMarginChanged) {
860+ m_leftMargin = UCUnits::instance().gu(2);
861+ }
862+ if (!m_rightMarginChanged) {
863+ m_rightMargin = UCUnits::instance().gu(2);
864+ }
865+ if (m_listItem) {
866+ m_listItem->update();
867+ }
868+}
869+
870+void UCListItemDivider::paletteChanged()
871+{
872+ QColor background = getPaletteColor("normal", "background");
873+ if (!background.isValid()) {
874+ return;
875+ }
876+ // FIXME: we need a palette value for divider colors, till then base on the background
877+ // luminance
878+ if (!m_colorFromChanged || !m_colorToChanged) {
879+ qreal luminance = (background.red()*212 + background.green()*715 + background.blue()*73)/1000.0/255.0;
880+ bool lightBackground = (luminance > 0.85);
881+ if (!m_colorFromChanged) {
882+ m_colorFrom = lightBackground ? QColor("#26000000") : QColor("#26FFFFFF");
883+ }
884+ if (!m_colorToChanged) {
885+ m_colorTo = lightBackground ? QColor("#14FFFFFF") : QColor("#14000000");
886+ }
887+ updateGradient();
888+ }
889+}
890+
891+void UCListItemDivider::updateGradient()
892+{
893+ m_gradient.clear();
894+ m_gradient.append(QGradientStop(0.0, m_colorFrom));
895+ m_gradient.append(QGradientStop(0.49, m_colorFrom));
896+ m_gradient.append(QGradientStop(0.5, m_colorTo));
897+ m_gradient.append(QGradientStop(1.0, m_colorTo));
898+ if (m_listItem) {
899+ m_listItem->update();
900+ }
901+}
902+
903+QSGNode *UCListItemDivider::paint(QSGNode *paintNode, const QRectF &rect)
904+{
905+ if (m_visible && !m_lastItem && (m_gradient.size() > 0)) {
906+ QSGRectangleNode *rectNode = static_cast<QSGRectangleNode *>(paintNode);
907+ if (!rectNode) {
908+ rectNode = m_listItem->sceneGraphContext()->createRectangleNode();
909+ }
910+ rectNode->setRect(QRectF(m_leftMargin, rect.height() - m_thickness,
911+ rect.width() - m_leftMargin - m_rightMargin, m_thickness));
912+ rectNode->setGradientStops(m_gradient);
913+ rectNode->update();
914+ return rectNode;
915+ } else {
916+ delete paintNode;
917+ return 0;
918+ }
919+}
920+
921+void UCListItemDivider::setVisible(bool visible)
922+{
923+ if (m_visible == visible) {
924+ return;
925+ }
926+ m_visible = visible;
927+ m_listItem->resize();
928+ m_listItem->update();
929+ Q_EMIT visibleChanged();
930+}
931+
932+void UCListItemDivider::setLeftMargin(qreal leftMargin)
933+{
934+ if (m_leftMargin == leftMargin) {
935+ return;
936+ }
937+ m_leftMargin = leftMargin;
938+ m_leftMarginChanged = true;
939+ m_listItem->update();
940+ Q_EMIT leftMarginChanged();
941+}
942+
943+void UCListItemDivider::setRightMargin(qreal rightMargin)
944+{
945+ if (m_rightMargin == rightMargin) {
946+ return;
947+ }
948+ m_rightMargin = rightMargin;
949+ m_rightMarginChanged = true;
950+ m_listItem->update();
951+ Q_EMIT rightMarginChanged();
952+}
953+
954+void UCListItemDivider::setColorFrom(const QColor &color)
955+{
956+ if (m_colorFrom == color) {
957+ return;
958+ }
959+ m_colorFrom = color;
960+ m_colorFromChanged = true;
961+ updateGradient();
962+ Q_EMIT colorFromChanged();
963+}
964+
965+void UCListItemDivider::setColorTo(const QColor &color)
966+{
967+ if (m_colorTo == color) {
968+ return;
969+ }
970+ m_colorTo = color;
971+ m_colorToChanged = true;
972+ updateGradient();
973+ Q_EMIT colorToChanged();
974+}
975+
976+/******************************************************************************
977+ * ListItemContent
978+ */
979+UCListItemContent::UCListItemContent(QQuickItem *parent)
980+ : QQuickItem(parent)
981+ , m_color(Qt::transparent)
982+ , m_pressedColor(Qt::yellow)
983+ , m_item(0)
984+{
985+ setFlag(QQuickItem::ItemHasContents);
986+ // catch theme palette changes
987+ connect(&UCTheme::instance(), &UCTheme::paletteChanged, this, &UCListItemContent::updateColors);
988+ updateColors();
989+}
990+
991+UCListItemContent::~UCListItemContent()
992+{
993+}
994+
995+void UCListItemContent::setColor(const QColor &color)
996+{
997+ if (m_color == color) {
998+ return;
999+ }
1000+ m_color = color;
1001+ update();
1002+ Q_EMIT colorChanged();
1003+}
1004+
1005+void UCListItemContent::setPressedColor(const QColor &color)
1006+{
1007+ if (m_pressedColor == color) {
1008+ return;
1009+ }
1010+ m_pressedColor = color;
1011+ // no more theme change watch
1012+ disconnect(&UCTheme::instance(), &UCTheme::paletteChanged, this, &UCListItemContent::updateColors);
1013+ update();
1014+ Q_EMIT pressedColorChanged();
1015+}
1016+
1017+void UCListItemContent::updateColors()
1018+{
1019+ m_pressedColor = getPaletteColor("selected", "background");
1020+ update();
1021+}
1022+
1023+
1024+void UCListItemContent::itemChange(ItemChange change, const ItemChangeData &data)
1025+{
1026+ if (change == ItemParentHasChanged) {
1027+ m_item = qobject_cast<UCListItem*>(data.item);
1028+ }
1029+ QQuickItem::itemChange(change, data);
1030+}
1031+
1032+QSGNode *UCListItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1033+{
1034+ Q_UNUSED(data);
1035+
1036+ UCListItemPrivate *dd = UCListItemPrivate::get(m_item);
1037+ bool pressed = (dd && dd->pressed);
1038+ bool selected = (dd && dd->selectable && dd->selected);
1039+ QColor color = (pressed || selected) ? m_pressedColor : m_color;
1040+
1041+ delete oldNode;
1042+ if (width() <= 0 || height() <= 0 || (color.alpha() == 0)) {
1043+ return 0;
1044+ }
1045+
1046+ QSGRectangleNode *rectNode = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode();
1047+ rectNode->setColor(color);
1048+ rectNode->setRect(boundingRect());
1049+ rectNode->update();
1050+ return rectNode;
1051+}
1052+
1053+
1054+/******************************************************************************
1055+ * ListItemBasePrivate
1056+ */
1057+UCListItemPrivate::UCListItemPrivate()
1058+ : UCStyledItemBasePrivate()
1059+ , pressed(false)
1060+ , moved(false)
1061+ , ready(false)
1062+ , selectable(false)
1063+ , selected(false)
1064+ , index(-1)
1065+ , xAxisMoveThresholdGU(1.5)
1066+ , reboundAnimation(0)
1067+ , flickableInteractive(0)
1068+ , disabledOpacity(0)
1069+ , contentItem(new UCListItemContent)
1070+ , divider(new UCListItemDivider)
1071+ , leadingOptions(0)
1072+ , trailingOptions(0)
1073+ , selectionPanel(0)
1074+{
1075+}
1076+UCListItemPrivate::~UCListItemPrivate()
1077+{
1078+ delete flickableInteractive;
1079+ delete disabledOpacity;
1080+}
1081+
1082+void UCListItemPrivate::init()
1083+{
1084+ Q_Q(UCListItem);
1085+ contentItem->setObjectName("ListItemHolder");
1086+ QQml_setParent_noEvent(contentItem, q);
1087+ contentItem->setParentItem(q);
1088+ divider->init(q);
1089+ // content will be redirected to the contentItem, therefore we must report
1090+ // children changes as it would come from the main component
1091+ QObject::connect(contentItem, &UCListItemContent::childrenChanged,
1092+ q, &UCListItem::childrenChanged);
1093+ q->setFlag(QQuickItem::ItemHasContents);
1094+ // turn activeFocusOnPress on
1095+ activeFocusOnPress = true;
1096+ setFocusable();
1097+
1098+ // watch size change and set implicit size;
1099+ QObject::connect(&UCUnits::instance(), SIGNAL(gridUnitChanged()), q, SLOT(_q_updateSize()));
1100+ _q_updateSize();
1101+
1102+ // watch enabledChanged()
1103+ QObject::connect(q, SIGNAL(enabledChanged()), q, SLOT(_q_dimmDisabled()), Qt::DirectConnection);
1104+
1105+ // create rebound animation
1106+ UCUbuntuAnimation animationCodes;
1107+ reboundAnimation = new QQuickPropertyAnimation(q);
1108+ reboundAnimation->setEasing(animationCodes.StandardEasing());
1109+ reboundAnimation->setDuration(animationCodes.SnapDuration());
1110+ reboundAnimation->setTargetObject(contentItem);
1111+ reboundAnimation->setProperty("x");
1112+ reboundAnimation->setAlwaysRunToEnd(true);
1113+}
1114+
1115+void UCListItemPrivate::setFocusable()
1116+{
1117+ // alsways accept mouse events
1118+ Q_Q(UCListItem);
1119+ q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
1120+ q->setFiltersChildMouseEvents(true);
1121+}
1122+
1123+void UCListItemPrivate::_q_dimmDisabled()
1124+{
1125+ Q_Q(UCListItem);
1126+ if (q->isEnabled()) {
1127+ PropertyChange::restore(disabledOpacity);
1128+ } else if (opacity() != 0.5) {
1129+ // this is the first time we need to create the property change
1130+ if (!disabledOpacity) {
1131+ disabledOpacity = new PropertyChange(q, "opacity");
1132+ }
1133+ PropertyChange::setValue(disabledOpacity, 0.5);
1134+ }
1135+}
1136+
1137+void UCListItemPrivate::_q_rebound()
1138+{
1139+ setPressed(false);
1140+ // initiate rebinding only if there were options tugged
1141+ Q_Q(UCListItem);
1142+ if (!UCListItemOptionsPrivate::isConnectedTo(leadingOptions, q) && !UCListItemOptionsPrivate::isConnectedTo(trailingOptions, q)) {
1143+ return;
1144+ }
1145+ setMoved(false);
1146+ //connect rebound completion so we can disconnect
1147+ QObject::connect(reboundAnimation, SIGNAL(stopped()), q, SLOT(_q_completeRebinding()));
1148+ // then rebound to zero
1149+ reboundTo(0);
1150+}
1151+void UCListItemPrivate::_q_completeRebinding()
1152+{
1153+ Q_Q(UCListItem);
1154+ // disconnect animation, otherwise snapping will disconnect the panel
1155+ QObject::disconnect(reboundAnimation, SIGNAL(stopped()), q, SLOT(_q_completeRebinding()));
1156+ // restore flickable's interactive and cleanup
1157+ PropertyChange::restore(flickableInteractive);
1158+ // disconnect options
1159+ grabPanel(leadingOptions, false);
1160+ grabPanel(trailingOptions, false);
1161+}
1162+
1163+void UCListItemPrivate::_q_updateIndex(QObject *ownerItem)
1164+{
1165+ Q_Q(UCListItem);
1166+ if (!ownerItem) {
1167+ ownerItem = q->sender();
1168+ }
1169+ Q_ASSERT(ownerItem);
1170+ // update the index as well
1171+ QQmlContext *context = qmlContext(q);
1172+ if (context) {
1173+ QVariant indexProperty = context->contextProperty("index");
1174+ index = indexProperty.isValid() ? indexProperty.toInt() : -1;
1175+ }
1176+ divider->m_lastItem = ready && index == (ownerItem->property("count").toInt() - 1);
1177+}
1178+
1179+void UCListItemPrivate::_q_updateSelected()
1180+{
1181+ Q_Q(UCListItem);
1182+ bool checked = selectionPanel->property("checked").toBool();
1183+ q->setSelected(checked);
1184+ contentItem->update();
1185+}
1186+
1187+// the function performs a cleanup on mouse release without any rebound animation
1188+void UCListItemPrivate::cleanup()
1189+{
1190+ setPressed(false);
1191+ setMoved(false);
1192+ _q_completeRebinding();
1193+}
1194+
1195+void UCListItemPrivate::reboundTo(qreal x)
1196+{
1197+ reboundAnimation->setFrom(contentItem->x());
1198+ reboundAnimation->setTo(x);
1199+ reboundAnimation->restart();
1200+}
1201+
1202+// set pressed flag and update background
1203+// called when units size changes
1204+void UCListItemPrivate::_q_updateSize()
1205+{
1206+ Q_Q(UCListItem);
1207+ QQuickItem *owner = flickable ? flickable : parentItem;
1208+ q->setImplicitWidth(owner ? owner->width() : UCUnits::instance().gu(40));
1209+ q->setImplicitHeight(UCUnits::instance().gu(7));
1210+}
1211+
1212+// set pressed flag and update contentItem
1213+void UCListItemPrivate::setPressed(bool pressed)
1214+{
1215+ if (this->pressed != pressed) {
1216+ this->pressed = pressed;
1217+ suppressClick = false;
1218+ contentItem->update();
1219+ Q_Q(UCListItem);
1220+ Q_EMIT q->pressedChanged();
1221+ }
1222+}
1223+// toggles the moved flag and installs/removes event filter
1224+void UCListItemPrivate::setMoved(bool moved)
1225+{
1226+ suppressClick = moved;
1227+ if (this->moved == moved) {
1228+ return;
1229+ }
1230+ this->moved = moved;
1231+ Q_Q(UCListItem);
1232+ QQuickWindow *window = q->window();
1233+ if (!window) {
1234+ return;
1235+ }
1236+ if (moved) {
1237+ window->installEventFilter(q);
1238+ } else {
1239+ window->removeEventFilter(q);
1240+ }
1241+}
1242+
1243+// sets the moved flag but also grabs the panels from the leading/trailing options
1244+bool UCListItemPrivate::grabPanel(UCListItemOptions *optionsList, bool isMoved)
1245+{
1246+ Q_Q(UCListItem);
1247+ if (isMoved) {
1248+ bool grab = UCListItemOptionsPrivate::connectToListItem(optionsList, q, (optionsList == leadingOptions));
1249+ if (grab) {
1250+ PropertyChange::setValue(flickableInteractive, false);
1251+ }
1252+ return grab;
1253+ } else {
1254+ UCListItemOptionsPrivate::disconnectFromListItem(optionsList);
1255+ return false;
1256+ }
1257+}
1258+
1259+
1260+// connects/disconnects from the Flickable anchestor to get notified when to do rebound
1261+void UCListItemPrivate::listenToRebind(bool listen)
1262+{
1263+ if (flickable.isNull()) {
1264+ return;
1265+ }
1266+ Q_Q(UCListItem);
1267+ if (listen) {
1268+ QObject::connect(flickable.data(), SIGNAL(movementStarted()), q, SLOT(_q_rebound()));
1269+ } else {
1270+ QObject::disconnect(flickable.data(), SIGNAL(movementStarted()), q, SLOT(_q_rebound()));
1271+ }
1272+}
1273+
1274+void UCListItemPrivate::resize()
1275+{
1276+ Q_Q(UCListItem);
1277+ QRectF rect(q->boundingRect());
1278+ if (divider && divider->m_visible) {
1279+ rect.setHeight(rect.height() - divider->m_thickness);
1280+ }
1281+ contentItem->setSize(rect.size());
1282+}
1283+
1284+void UCListItemPrivate::update()
1285+{
1286+ if (!ready) {
1287+ return;
1288+ }
1289+ Q_Q(UCListItem);
1290+ q->update();
1291+}
1292+
1293+void UCListItemPrivate::clampX(qreal &x, qreal dx)
1294+{
1295+ UCListItemOptionsPrivate *leading = UCListItemOptionsPrivate::get(leadingOptions);
1296+ UCListItemOptionsPrivate *trailing = UCListItemOptionsPrivate::get(trailingOptions);
1297+ x += dx;
1298+ // min cannot be less than the trailing's panel width
1299+ qreal min = (trailing && trailing->panelItem) ? -trailing->panelItem->width() : 0;
1300+ // max cannot be bigger than 0 or the leading's width in case we have leading panel
1301+ qreal max = (leading && leading->panelItem) ? leading->panelItem->width() : 0;
1302+ x = CLAMP(x, min, max);
1303+}
1304+
1305+void UCListItemPrivate::autoLeadingOptions()
1306+{
1307+ Q_Q(UCListItem);
1308+ if (flickable && flickable->property("leadingOptions").isValid() && !leadingOptions) {
1309+ QQmlProperty prop(flickable, "leadingOptions", qmlContext(flickable));
1310+ QQmlAbstractBinding *binding = QQmlPropertyPrivate::binding(prop);
1311+ // set the value first, then bind
1312+ q->setLeadingOptions(prop.read().value<UCListItemOptions*>());
1313+ if (binding) {
1314+ // set the binding to our leadingOptions property
1315+ QQmlProperty leadingOptionsProperty(q, "leadingOptions", qmlContext(q));
1316+ QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(leadingOptionsProperty, binding);
1317+ if (oldBinding && (oldBinding != binding)) {
1318+ oldBinding->destroy();
1319+ }
1320+ }
1321+ }
1322+}
1323+
1324+QQuickItem *UCListItemPrivate::createSelectionPanel()
1325+{
1326+ Q_Q(UCListItem);
1327+ if (!selectionPanel) {
1328+ QUrl panelDocument = UbuntuComponentsPlugin::pluginUrl().
1329+ resolved(QUrl::fromLocalFile("ListItemSelectablePanel.qml"));
1330+ QQmlComponent component(qmlEngine(q), panelDocument);
1331+ if (!component.isError()) {
1332+ selectionPanel = qobject_cast<QQuickItem*>(component.beginCreate(qmlContext(q)));
1333+ if (selectionPanel) {
1334+ QQml_setParent_noEvent(selectionPanel, q);
1335+ selectionPanel->setParentItem(q);
1336+ selectionPanel->setVisible(false);
1337+ selectionPanel->setProperty("checked", selected);
1338+ // complete component creation
1339+ component.completeCreate();
1340+ }
1341+ } else {
1342+ qmlInfo(q) << component.errorString();
1343+ }
1344+ }
1345+ return selectionPanel;
1346+}
1347+void UCListItemPrivate::toggleSelectionMode()
1348+{
1349+ if (!createSelectionPanel()) {
1350+ return;
1351+ }
1352+ Q_Q(UCListItem);
1353+ if (selectable) {
1354+ // move and dimm content item
1355+ selectionPanel->setVisible(true);
1356+ reboundTo(selectionPanel->width());
1357+ QObject::connect(selectionPanel, SIGNAL(checkedChanged()), q, SLOT(_q_updateSelected()));
1358+ } else {
1359+ // remove content item dimming and destroy selection panel as well
1360+ reboundTo(0.0);
1361+ selectionPanel->setVisible(false);
1362+ QObject::disconnect(selectionPanel, SIGNAL(checkedChanged()), q, SLOT(_q_updateSelected()));
1363+ }
1364+ _q_updateSelected();
1365+}
1366+
1367+/*!
1368+ * \qmltype ListItem
1369+ * \instantiates UCListItem
1370+ * \inqmlmodule Ubuntu.Components 1.1
1371+ * \ingroup ubuntu
1372+ * \brief The ListItem element provides Ubuntu design standards for list or grid
1373+ * views.
1374+ *
1375+ * The component is dedicated to be used in designs with static or dynamic lists
1376+ * (i.e. list views where each item's layout differs or in lists where the content
1377+ * is determined by a given model, thus each element has the same layout). The
1378+ * element does not define any specific layout, components can be placed in any
1379+ * ways on it. However, when used in list views, the content must be carefully
1380+ * chosen to in order to keep the kinetic behavior and the highest FPS possible.
1381+ *
1382+ * \c contentItem is an essential part of the component. Beside the fact that it
1383+ * holds all components and resources declared as child to ListItem, it can also
1384+ * configure the color of the background when in normal mode or when pressed. Being
1385+ * an item, all other properties can be accessed or altered, with the exception
1386+ * of some:
1387+ * \list A
1388+ * \li do not alter \c x, \c y, \c width or \c height properties as those are
1389+ * controlled by the ListItem itself when leading or trailing options are
1390+ * revealed and thus will destroy your logic
1391+ * \li never anchor left or right anchor lines as it will block revealing the options.
1392+ * \endlist
1393+ *
1394+ * Each ListItem has a thin divider shown on the bottom of the component. This
1395+ * divider can be configured through the \l divider grouped property, which can
1396+ * configure its margins from the edges of the ListItem as well as its visibility.
1397+ * When used in \c ListView or \l UbuntuListView, the last list item will not
1398+ * show the divider no matter of the visible property value set. In other
1399+ * circumstances declaring a \c count property in the ListItem's parent item
1400+ * or Flickable can help applying the last item detection logic.
1401+ * \qml
1402+ * Column {
1403+ * width: units.gu(40)
1404+ * // bring count to Column from Repeater
1405+ * property alias count: repeater.count
1406+ * Repeater {
1407+ * model: 10
1408+ * ListItem {
1409+ * Label {
1410+ * anchors.fill: parent
1411+ * horizontalCenter: Text.AlignHCenter
1412+ * verticalCenter: Text.AlignVCenter
1413+ * text: "Item #" + modelData
1414+ * }
1415+ * }
1416+ * }
1417+ * }
1418+ * \endqml
1419+ *
1420+ * ListItem can handle options that can ge tugged from front ot right of the item.
1421+ * These options are Action elements visualized in panels attached to the front
1422+ * or to the end of the item, and are revealed by swiping the item horizontally.
1423+ * The tug is started only after the mouse/touch move had passed a given threshold.
1424+ * These options are configured through the \l leadingOptions as well as \l
1425+ * trailingOptions properties.
1426+ * \qml
1427+ * ListItem {
1428+ * id: listItem
1429+ * leadingOptions: ListItemOptions {
1430+ * Action {
1431+ * iconName: "delete"
1432+ * onTriggered: listItem.destroy()
1433+ * }
1434+ * }
1435+ * trailingOptions: ListItemOptions {
1436+ * Action {
1437+ * iconName: "search"
1438+ * onTriggered: {
1439+ * // do some search
1440+ * }
1441+ * }
1442+ * }
1443+ * }
1444+ * \endqml
1445+ * \note A ListItem cannot use the same ListItemOption instance for both leading or
1446+ * trailing options. If it is desired to have the same action present in both leading
1447+ * and trailing options, one of the ListItemOption options list can use the other's
1448+ * list. In the following example the list item can be deleted through both option
1449+ * leading and trailing options:
1450+ * \qml
1451+ * ListItem {
1452+ * id: listItem
1453+ * leadingOptions: ListItemOptions {
1454+ * Action {
1455+ * iconName: "delete"
1456+ * onTriggered: listItem.destroy()
1457+ * }
1458+ * }
1459+ * trailingOptions: ListItemOptions {
1460+ * options: leadingOptions.options
1461+ * }
1462+ * }
1463+ * \endqml
1464+ * \sa ListItemOptions
1465+ */
1466+
1467+/*!
1468+ * \qmlsignal ListItem::clicked()
1469+ *
1470+ * The signal is emitted when the component gets released while the \l pressed property
1471+ * is set. The signal is not emitted if the ListItem content is tugged or when used in
1472+ * Flickable (or ListView, GridView) and the Flickable gets moved.
1473+ */
1474+UCListItem::UCListItem(QQuickItem *parent)
1475+ : UCStyledItemBase(*(new UCListItemPrivate), parent)
1476+{
1477+ Q_D(UCListItem);
1478+ d->init();
1479+}
1480+
1481+UCListItem::~UCListItem()
1482+{
1483+}
1484+
1485+void UCListItem::componentComplete()
1486+{
1487+ UCStyledItemBase::componentComplete();
1488+ Q_D(UCListItem);
1489+ d->ready = true;
1490+ /* We only deal with ListView, as for other cases we would need to check the children
1491+ * changes, which would have an enormous impact on performance in case of huge amount
1492+ * of items. However, if the parent item, or Flickable declares a "count" property,
1493+ * the ListItem will take use of it!
1494+ */
1495+ QQuickItem *countOwner = (d->flickable && d->flickable->property("count").isValid()) ?
1496+ d->flickable :
1497+ (d->parentItem && d->parentItem->property("count").isValid()) ? d->parentItem : 0;
1498+ if (countOwner) {
1499+ QObject::connect(countOwner, SIGNAL(countChanged()),
1500+ this, SLOT(_q_updateIndex()), Qt::DirectConnection);
1501+ d->_q_updateIndex(countOwner);
1502+ }
1503+
1504+ // check if the parent ListView has leadingOptions defined, if yes, use it!
1505+ d->autoLeadingOptions();
1506+}
1507+
1508+void UCListItem::itemChange(ItemChange change, const ItemChangeData &data)
1509+{
1510+ UCStyledItemBase::itemChange(change, data);
1511+ if (change == ItemParentHasChanged) {
1512+ Q_D(UCListItem);
1513+ // make sure we are not connected to the previous Flickable
1514+ d->listenToRebind(false);
1515+ // check if we are in a positioner, and if that positioner is in a Flickable
1516+ QQuickBasePositioner *positioner = qobject_cast<QQuickBasePositioner*>(data.item);
1517+ if (positioner && positioner->parentItem()) {
1518+ d->flickable = qobject_cast<QQuickFlickable*>(positioner->parentItem()->parentItem());
1519+ } else if (data.item && data.item->parentItem()){
1520+ // check if we are in a Flickable then
1521+ d->flickable = qobject_cast<QQuickFlickable*>(data.item->parentItem());
1522+ }
1523+
1524+ if (d->flickable) {
1525+ // create the flickableInteractive property change now
1526+ d->flickableInteractive = new PropertyChange(d->flickable, "interactive");
1527+ // connect to flickable to get width changes
1528+ QObject::connect(d->flickable, SIGNAL(widthChanged()), this, SLOT(_q_updateSize()));
1529+ } else if (data.item) {
1530+ QObject::connect(data.item, SIGNAL(widthChanged()), this, SLOT(_q_updateSize()));
1531+ } else {
1532+ // in case we had a flickableInteractive property change active, destroy it
1533+ if (d->flickableInteractive) {
1534+ delete d->flickableInteractive;
1535+ d->flickableInteractive = 0;
1536+ }
1537+ // mar as not ready, so no action should be performed which depends on readyness
1538+ d->ready = false;
1539+ }
1540+
1541+ // update size
1542+ d->_q_updateSize();
1543+ }
1544+}
1545+
1546+void UCListItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1547+{
1548+ UCStyledItemBase::geometryChanged(newGeometry, oldGeometry);
1549+ // resize contentItem item
1550+ Q_D(UCListItem);
1551+ d->resize();
1552+}
1553+
1554+QSGNode *UCListItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1555+{
1556+ Q_UNUSED(data);
1557+ Q_D(UCListItem);
1558+ if (width() <= 0 || height() <= 0 || !d->divider) {
1559+ delete oldNode;
1560+ return 0;
1561+ }
1562+ // paint divider
1563+ return d->divider->paint(oldNode, boundingRect());
1564+}
1565+
1566+void UCListItem::mousePressEvent(QMouseEvent *event)
1567+{
1568+ UCStyledItemBase::mousePressEvent(event);
1569+ Q_D(UCListItem);
1570+ if (d->selectable || (!d->flickable.isNull() && d->flickable->isMoving())) {
1571+ // while moving, we cannot select or tug any items
1572+ return;
1573+ }
1574+ d->setPressed(true);
1575+ d->lastPos = d->pressedPos = event->localPos();
1576+ // connect the Flickable to know when to rebound
1577+ d->listenToRebind(true);
1578+ // start pressandhold timer
1579+ d->pressAndHoldTimer.start(DefaultPressAndHoldDelay, this);
1580+ // accept the event so we get the rest of the events as well
1581+ event->setAccepted(true);
1582+}
1583+
1584+void UCListItem::mouseReleaseEvent(QMouseEvent *event)
1585+{
1586+ UCStyledItemBase::mouseReleaseEvent(event);
1587+ Q_D(UCListItem);
1588+ if (d->selectable) {
1589+ // no move is allowed while selectable mode is on
1590+ return;
1591+ }
1592+ d->pressAndHoldTimer.stop();
1593+ // set released
1594+ if (d->pressed) {
1595+ // disconnect the flickable
1596+ d->listenToRebind(false);
1597+
1598+ if (!d->suppressClick) {
1599+ Q_EMIT clicked();
1600+ d->_q_rebound();
1601+ } else {
1602+ // snap
1603+ qreal snapPosition = 0.0;
1604+ if (d->contentItem->x() < 0) {
1605+ snapPosition = UCListItemOptionsPrivate::snap(d->trailingOptions);
1606+ } else if (d->contentItem->x() > 0) {
1607+ snapPosition = UCListItemOptionsPrivate::snap(d->leadingOptions);
1608+ }
1609+ if (d->contentItem->x() == 0.0) {
1610+ // do a cleanup, no need to rebound, the item has been dragged back to 0
1611+ d->cleanup();
1612+ } else if (snapPosition == 0.0){
1613+ d->_q_rebound();
1614+ } else {
1615+ d->reboundTo(snapPosition);
1616+ }
1617+ }
1618+ }
1619+ d->setPressed(false);
1620+}
1621+
1622+void UCListItem::mouseMoveEvent(QMouseEvent *event)
1623+{
1624+ Q_D(UCListItem);
1625+ UCStyledItemBase::mouseMoveEvent(event);
1626+
1627+ if (d->selectable) {
1628+ // no move is allowed while selectable mode is on
1629+ return;
1630+ }
1631+
1632+ // accept the tugging only if the move is within the threshold
1633+ bool leadingAttached = UCListItemOptionsPrivate::isConnectedTo(d->leadingOptions, this);
1634+ bool trailingAttached = UCListItemOptionsPrivate::isConnectedTo(d->trailingOptions, this);
1635+ if (d->pressed && !(leadingAttached || trailingAttached)) {
1636+ // check if we can initiate the drag at all
1637+ // only X direction matters, if Y-direction leaves the threshold, but X not, the tug is not valid
1638+ qreal threshold = UCUnits::instance().gu(d->xAxisMoveThresholdGU);
1639+ qreal mouseX = event->localPos().x();
1640+ qreal pressedX = d->pressedPos.x();
1641+
1642+ if ((mouseX < (pressedX - threshold)) || (mouseX > (pressedX + threshold))) {
1643+ // the press went out of the threshold area, enable move, if the direction allows it
1644+ d->lastPos = event->localPos();
1645+ // connect both panels
1646+ leadingAttached = d->grabPanel(d->leadingOptions, true);
1647+ trailingAttached = d->grabPanel(d->trailingOptions, true);
1648+ // stop pressAndHold timer as we started to drag
1649+ d->pressAndHoldTimer.stop();
1650+ }
1651+ }
1652+
1653+ if (leadingAttached || trailingAttached) {
1654+ qreal x = d->contentItem->x();
1655+ qreal dx = event->localPos().x() - d->lastPos.x();
1656+ d->lastPos = event->localPos();
1657+
1658+ if (dx) {
1659+ // clamp X into allowed dragging area
1660+ d->clampX(x, dx);
1661+ // block flickable
1662+ d->setMoved(true);
1663+ d->contentItem->setX(x);
1664+ }
1665+ }
1666+}
1667+
1668+bool UCListItem::eventFilter(QObject *target, QEvent *event)
1669+{
1670+ QPointF myPos;
1671+ // only filter press events, and rebound when pressed outside
1672+ if (event->type() == QEvent::MouseButtonPress) {
1673+ QMouseEvent *mouse = static_cast<QMouseEvent*>(event);
1674+ QQuickWindow *window = qobject_cast<QQuickWindow*>(target);
1675+ if (window) {
1676+ myPos = window->contentItem()->mapToItem(this, mouse->localPos());
1677+ }
1678+ } else if (event->type() == QEvent::TouchBegin) {
1679+ QTouchEvent *touch = static_cast<QTouchEvent*>(event);
1680+ QQuickWindow *window = qobject_cast<QQuickWindow*>(target);
1681+ if (window) {
1682+ myPos = window->contentItem()->mapToItem(this, touch->touchPoints()[0].pos());
1683+ }
1684+ }
1685+ if (!myPos.isNull() && !contains(myPos)) {
1686+ Q_D(UCListItem);
1687+ d->_q_rebound();
1688+ // only accept event, but let it be handled by the underlying or surrounding Flickables
1689+ event->accept();
1690+ }
1691+ return UCStyledItemBase::eventFilter(target, event);
1692+}
1693+
1694+void UCListItem::timerEvent(QTimerEvent *event)
1695+{
1696+ Q_D(UCListItem);
1697+ if (isEnabled() && (event->timerId() == d->pressAndHoldTimer.timerId())) {
1698+ d->pressAndHoldTimer.stop();
1699+ d->suppressClick = true;
1700+ Q_EMIT pressAndHold();
1701+ } else {
1702+ QQuickItem::timerEvent(event);
1703+ }
1704+}
1705+
1706+/*!
1707+ * \qmlproperty ListItemOptions ListItem::leadingOptions
1708+ *
1709+ * The property holds the options and its configuration to be revealed when swiped
1710+ * from left to right.
1711+ *
1712+ * \sa trailingOptions
1713+ */
1714+UCListItemOptions *UCListItem::leadingOptions() const
1715+{
1716+ Q_D(const UCListItem);
1717+ return d->leadingOptions;
1718+}
1719+void UCListItem::setLeadingOptions(UCListItemOptions *options)
1720+{
1721+ Q_D(UCListItem);
1722+ if (d->leadingOptions == options) {
1723+ return;
1724+ }
1725+ d->leadingOptions = options;
1726+ if (d->leadingOptions == d->trailingOptions && d->leadingOptions) {
1727+ qmlInfo(this) << UbuntuI18n::tr("leadingOptions and trailingOptions cannot share the same object!");
1728+ }
1729+ Q_EMIT leadingOptionsChanged();
1730+}
1731+
1732+/*!
1733+ * \qmlproperty ListItemOptions ListItem::trailingOptions
1734+ *
1735+ * The property holds the options and its configuration to be revealed when swiped
1736+ * from right to left.
1737+ *
1738+ * \sa leadingOptions
1739+ */
1740+UCListItemOptions *UCListItem::trailingOptions() const
1741+{
1742+ Q_D(const UCListItem);
1743+ return d->trailingOptions;
1744+}
1745+void UCListItem::setTrailingOptions(UCListItemOptions *options)
1746+{
1747+ Q_D(UCListItem);
1748+ if (d->trailingOptions == options) {
1749+ return;
1750+ }
1751+ d->trailingOptions = options;
1752+ if (d->leadingOptions == d->trailingOptions && d->trailingOptions) {
1753+ qmlInfo(this) << UbuntuI18n::tr("leadingOptions and trailingOptions cannot share the same object!");
1754+ }
1755+ Q_EMIT trailingOptionsChanged();
1756+}
1757+
1758+/*!
1759+ * \qmlpropertygroup ::ListItem::contentItem
1760+ * \qmlproperty color ListItem::contentItem.color
1761+ * \qmlproperty color ListItem::contentItem.pressedColor
1762+ *
1763+ * contentItem holds the components placed on a ListItem. \c color configures
1764+ * the color of the normal contentItem, and \c pressedColor configures the color
1765+ * when pressed.
1766+ */
1767+UCListItemContent* UCListItem::contentItem() const
1768+{
1769+ Q_D(const UCListItem);
1770+ return d->contentItem;
1771+}
1772+
1773+/*!
1774+ * \qmlpropertygroup ::ListItem::divider
1775+ * \qmlproperty bool ListItem::divider.visible
1776+ * \qmlproperty real ListItem::divider.leftMargin
1777+ * \qmlproperty real ListItem::divider.rightMargin
1778+ *
1779+ * This grouped property configures the thin divider shown in the bottom of the
1780+ * component. Configures the visibility and the margins from the left and right
1781+ * of the ListItem. When tugged (swiped left or right to reveal the options),
1782+ * it is not moved together with the content.
1783+ *
1784+ * When \c visible is true, the ListItem's content size gets thinner with the
1785+ * divider's \c thickness.
1786+ *
1787+ * The default values for the properties are:
1788+ * \list
1789+ * \li \c visible: true
1790+ * \li \c leftMargin: 2 GU
1791+ * \li \c rightMargin: 2 GU
1792+ * \endlist
1793+ */
1794+UCListItemDivider* UCListItem::divider() const
1795+{
1796+ Q_D(const UCListItem);
1797+ return d->divider;
1798+}
1799+
1800+/*!
1801+ * \qmlproperty bool ListItem::pressed
1802+ * True when the item is pressed. The items stays pressed when the mouse or touch
1803+ * is moved horizontally. When in Flickable (or ListView), the item gets un-pressed
1804+ * (false) when the mouse or touch is moved towards the vertical direction causing
1805+ * the flickable to move.
1806+ */
1807+bool UCListItem::pressed() const
1808+{
1809+ Q_D(const UCListItem);
1810+ return d->pressed;
1811+}
1812+
1813+/*!
1814+ * \qmlproperty bool ListItem::selectable
1815+ * The property drives whether a list item is selectable or not. When set, the item
1816+ * will show a check box on the leading side hanving the content item pushed towards
1817+ * trailing side and dimmed. The checkbox which will reflect and drive the \l selected
1818+ * state.
1819+ * Defaults to false.
1820+ * \note it is recommended to be used with UbuntuListView which can drive all the
1821+ * ListItem's selectable.
1822+ */
1823+bool UCListItem::selectable() const
1824+{
1825+ Q_D(const UCListItem);
1826+ return d->selectable;
1827+}
1828+void UCListItem::setSelectable(bool selectable)
1829+{
1830+ Q_D(UCListItem);
1831+ if (d->selectable == selectable) {
1832+ return;
1833+ }
1834+ d->selectable = selectable;
1835+ d->toggleSelectionMode();
1836+ Q_EMIT selectableChanged();
1837+}
1838+
1839+/*!
1840+ * \qmlproperty bool ListItem::selected
1841+ * The property drives whether a list item is selected or not. While selected, the
1842+ * ListItem is dimmed and cannot be tugged. The default value is false.
1843+ */
1844+bool UCListItem::selected() const
1845+{
1846+ Q_D(const UCListItem);
1847+ return d->selected;
1848+}
1849+void UCListItem::setSelected(bool selected)
1850+{
1851+ Q_D(UCListItem);
1852+ if (d->selected == selected) {
1853+ return;
1854+ }
1855+ d->selected = selected;
1856+ // update panel as well
1857+ if (d->selectionPanel) {
1858+ d->selectionPanel->setProperty("checked", d->selected);
1859+ }
1860+ Q_EMIT selectedChanged();
1861+}
1862+
1863+
1864+/*!
1865+ * \qmlproperty list<Object> ListItem::data
1866+ * \default
1867+ * Overloaded default property containing all the children and resources.
1868+ */
1869+QQmlListProperty<QObject> UCListItem::data()
1870+{
1871+ Q_D(UCListItem);
1872+ return QQuickItemPrivate::get(d->contentItem)->data();
1873+}
1874+
1875+/*!
1876+ * \qmlproperty list<Item> ListItem::children
1877+ * Overloaded default property containing all the visible children of the item.
1878+ */
1879+QQmlListProperty<QQuickItem> UCListItem::children()
1880+{
1881+ Q_D(UCListItem);
1882+ return QQuickItemPrivate::get(d->contentItem)->children();
1883+}
1884+
1885+#include "moc_uclistitem.cpp"
1886
1887=== added file 'modules/Ubuntu/Components/plugin/uclistitem.h'
1888--- modules/Ubuntu/Components/plugin/uclistitem.h 1970-01-01 00:00:00 +0000
1889+++ modules/Ubuntu/Components/plugin/uclistitem.h 2014-09-15 11:00:03 +0000
1890@@ -0,0 +1,92 @@
1891+/*
1892+ * Copyright 2014 Canonical Ltd.
1893+ *
1894+ * This program is free software; you can redistribute it and/or modify
1895+ * it under the terms of the GNU Lesser General Public License as published by
1896+ * the Free Software Foundation; version 3.
1897+ *
1898+ * This program is distributed in the hope that it will be useful,
1899+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1900+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1901+ * GNU Lesser General Public License for more details.
1902+ *
1903+ * You should have received a copy of the GNU Lesser General Public License
1904+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1905+ */
1906+
1907+#ifndef UCLISTITEM_H
1908+#define UCLISTITEM_H
1909+
1910+#include <QtQuick/QQuickItem>
1911+#include "ucstyleditembase.h"
1912+
1913+class UCListItemContent;
1914+class UCListItemDivider;
1915+class UCListItemOptions;
1916+class UCListItemPrivate;
1917+class UCListItem : public UCStyledItemBase
1918+{
1919+ Q_OBJECT
1920+ Q_PROPERTY(UCListItemContent *contentItem READ contentItem CONSTANT)
1921+ Q_PROPERTY(UCListItemDivider *divider READ divider CONSTANT)
1922+ Q_PROPERTY(UCListItemOptions *leadingOptions READ leadingOptions WRITE setLeadingOptions NOTIFY leadingOptionsChanged DESIGNABLE false)
1923+ Q_PROPERTY(UCListItemOptions *trailingOptions READ trailingOptions WRITE setTrailingOptions NOTIFY trailingOptionsChanged DESIGNABLE false)
1924+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
1925+ Q_PROPERTY(bool selectable READ selectable WRITE setSelectable NOTIFY selectableChanged)
1926+ Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
1927+ Q_PROPERTY(QQmlListProperty<QObject> data READ data DESIGNABLE false)
1928+ Q_PROPERTY(QQmlListProperty<QQuickItem> children READ children NOTIFY childrenChanged DESIGNABLE false)
1929+ Q_CLASSINFO("DefaultProperty", "data")
1930+public:
1931+ explicit UCListItem(QQuickItem *parent = 0);
1932+ ~UCListItem();
1933+
1934+ UCListItemContent *contentItem() const;
1935+ UCListItemDivider *divider() const;
1936+ UCListItemOptions *leadingOptions() const;
1937+ void setLeadingOptions(UCListItemOptions *options);
1938+ UCListItemOptions *trailingOptions() const;
1939+ void setTrailingOptions(UCListItemOptions *options);
1940+ bool pressed() const;
1941+ bool selectable() const;
1942+ void setSelectable(bool selectable);
1943+ bool selected() const;
1944+ void setSelected(bool selected);
1945+
1946+protected:
1947+ void componentComplete();
1948+ void itemChange(ItemChange change, const ItemChangeData &data);
1949+ QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data);
1950+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry);
1951+ void mousePressEvent(QMouseEvent *event);
1952+ void mouseReleaseEvent(QMouseEvent *event);
1953+ void mouseMoveEvent(QMouseEvent *event);
1954+ bool eventFilter(QObject *, QEvent *);
1955+ void timerEvent(QTimerEvent *event);
1956+
1957+Q_SIGNALS:
1958+ void leadingOptionsChanged();
1959+ void trailingOptionsChanged();
1960+ void pressedChanged();
1961+ void selectableChanged();
1962+ void selectedChanged();
1963+ void childrenChanged();
1964+
1965+ void clicked();
1966+ void pressAndHold();
1967+
1968+public Q_SLOTS:
1969+
1970+private:
1971+ Q_DECLARE_PRIVATE(UCListItem)
1972+ QQmlListProperty<QObject> data();
1973+ QQmlListProperty<QQuickItem> children();
1974+ Q_PRIVATE_SLOT(d_func(), void _q_dimmDisabled())
1975+ Q_PRIVATE_SLOT(d_func(), void _q_rebound())
1976+ Q_PRIVATE_SLOT(d_func(), void _q_updateSize())
1977+ Q_PRIVATE_SLOT(d_func(), void _q_completeRebinding())
1978+ Q_PRIVATE_SLOT(d_func(), void _q_updateIndex(QObject *ownerItem = 0))
1979+ Q_PRIVATE_SLOT(d_func(), void _q_updateSelected())
1980+};
1981+
1982+#endif // UCLISTITEM_H
1983
1984=== added file 'modules/Ubuntu/Components/plugin/uclistitem_p.h'
1985--- modules/Ubuntu/Components/plugin/uclistitem_p.h 1970-01-01 00:00:00 +0000
1986+++ modules/Ubuntu/Components/plugin/uclistitem_p.h 2014-09-15 11:00:03 +0000
1987@@ -0,0 +1,177 @@
1988+/*
1989+ * Copyright 2014 Canonical Ltd.
1990+ *
1991+ * This program is free software; you can redistribute it and/or modify
1992+ * it under the terms of the GNU Lesser General Public License as published by
1993+ * the Free Software Foundation; version 3.
1994+ *
1995+ * This program is distributed in the hope that it will be useful,
1996+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1997+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1998+ * GNU Lesser General Public License for more details.
1999+ *
2000+ * You should have received a copy of the GNU Lesser General Public License
2001+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2002+ */
2003+
2004+#ifndef UCVIEWITEM_P_H
2005+#define UCVIEWITEM_P_H
2006+
2007+#include "uclistitem.h"
2008+#include "ucstyleditembase_p.h"
2009+#include <QtCore/QPointer>
2010+#include <QtCore/QBasicTimer>
2011+#include <QtQuick/private/qquickrectangle_p.h>
2012+
2013+class QQuickFlickable;
2014+class QQuickPropertyAnimation;
2015+class UCListItemContent;
2016+class UCListItemDivider;
2017+class UCListItemOptions;
2018+class PropertyChange;
2019+class UCListItemPrivate : public UCStyledItemBasePrivate
2020+{
2021+ Q_DECLARE_PUBLIC(UCListItem)
2022+public:
2023+ UCListItemPrivate();
2024+ virtual ~UCListItemPrivate();
2025+ void init();
2026+
2027+ static inline UCListItemPrivate *get(UCListItem *that)
2028+ {
2029+ Q_ASSERT(that);
2030+ return that->d_func();
2031+ }
2032+
2033+ // override setFocusable()
2034+ void setFocusable();
2035+
2036+ void _q_dimmDisabled();
2037+ void _q_rebound();
2038+ void _q_updateSize();
2039+ void _q_completeRebinding();
2040+ void _q_updateIndex(QObject *ownerItem = 0);
2041+ void _q_updateSelected();
2042+ void cleanup();
2043+ void reboundTo(qreal x);
2044+ void setPressed(bool pressed);
2045+ void setMoved(bool moved);
2046+ bool grabPanel(UCListItemOptions *optionList, bool isMoved);
2047+ void listenToRebind(bool listen);
2048+ void resize();
2049+ void update();
2050+ void clampX(qreal &x, qreal dx);
2051+ void autoLeadingOptions();
2052+ QQuickItem *createSelectionPanel();
2053+ void toggleSelectionMode();
2054+
2055+ bool pressed:1;
2056+ bool moved:1;
2057+ bool suppressClick:1;
2058+ bool ready:1;
2059+ bool selectable:1;
2060+ bool selected:1;
2061+ int index;
2062+ qreal xAxisMoveThresholdGU;
2063+ QBasicTimer pressAndHoldTimer;
2064+ QPointF lastPos;
2065+ QPointF pressedPos;
2066+ QPointer<QQuickFlickable> flickable;
2067+ QQuickPropertyAnimation *reboundAnimation;
2068+ PropertyChange *flickableInteractive;
2069+ PropertyChange *disabledOpacity;
2070+ UCListItemContent *contentItem;
2071+ UCListItemDivider *divider;
2072+ UCListItemOptions *leadingOptions;
2073+ UCListItemOptions *trailingOptions;
2074+ QQuickItem *selectionPanel;
2075+};
2076+
2077+class UCListItemContent : public QQuickItem
2078+{
2079+ Q_OBJECT
2080+ Q_PROPERTY(QColor color MEMBER m_color WRITE setColor NOTIFY colorChanged)
2081+ Q_PROPERTY(QColor pressedColor MEMBER m_pressedColor WRITE setPressedColor NOTIFY pressedColorChanged)
2082+public:
2083+ explicit UCListItemContent(QQuickItem *parent = 0);
2084+ ~UCListItemContent();
2085+
2086+ void setColor(const QColor &color);
2087+ void setPressedColor(const QColor &color);
2088+
2089+Q_SIGNALS:
2090+ void colorChanged();
2091+ void pressedColorChanged();
2092+
2093+protected:
2094+ void itemChange(ItemChange change, const ItemChangeData &data);
2095+ QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data);
2096+
2097+private Q_SLOTS:
2098+ void updateColors();
2099+
2100+private:
2101+ QColor m_color;
2102+ QColor m_pressedColor;
2103+ UCListItem *m_item;
2104+ bool m_pressedColorChanged:1;
2105+};
2106+
2107+class UCListItemDivider : public QObject
2108+{
2109+ Q_OBJECT
2110+ Q_PROPERTY(bool visible MEMBER m_visible WRITE setVisible NOTIFY visibleChanged)
2111+ Q_PROPERTY(qreal leftMargin MEMBER m_leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged)
2112+ Q_PROPERTY(qreal rightMargin MEMBER m_rightMargin WRITE setRightMargin NOTIFY rightMarginChanged)
2113+ Q_PROPERTY(QColor colorFrom MEMBER m_colorFrom WRITE setColorFrom NOTIFY colorFromChanged)
2114+ Q_PROPERTY(QColor colorTo MEMBER m_colorTo WRITE setColorTo NOTIFY colorToChanged)
2115+public:
2116+ explicit UCListItemDivider(QObject *parent = 0);
2117+ ~UCListItemDivider();
2118+ void init(UCListItem *listItem);
2119+
2120+Q_SIGNALS:
2121+ void visibleChanged();
2122+ void leftMarginChanged();
2123+ void rightMarginChanged();
2124+ void colorFromChanged();
2125+ void colorToChanged();
2126+
2127+protected:
2128+ QSGNode *paint(QSGNode *paintNode, const QRectF &rect);
2129+
2130+private Q_SLOTS:
2131+ void unitsChanged();
2132+ void paletteChanged();
2133+
2134+private:
2135+ void updateGradient();
2136+ void setVisible(bool visible);
2137+ void setLeftMargin(qreal leftMargin);
2138+ void setRightMargin(qreal rightMargin);
2139+ void setColorFrom(const QColor &color);
2140+ void setColorTo(const QColor &color);
2141+
2142+ bool m_visible:1;
2143+ bool m_lastItem:1;
2144+ bool m_leftMarginChanged:1;
2145+ bool m_rightMarginChanged:1;
2146+ bool m_colorFromChanged:1;
2147+ bool m_colorToChanged:1;
2148+ qreal m_thickness;
2149+ qreal m_leftMargin;
2150+ qreal m_rightMargin;
2151+ QColor m_colorFrom;
2152+ QColor m_colorTo;
2153+ QGradientStops m_gradient;
2154+ UCListItemPrivate *m_listItem;
2155+ friend class UCListItem;
2156+ friend class UCListItemPrivate;
2157+};
2158+
2159+QColor getPaletteColor(const char *profile, const char *color);
2160+
2161+QML_DECLARE_TYPE(UCListItemContent)
2162+QML_DECLARE_TYPE(UCListItemDivider)
2163+
2164+#endif // UCVIEWITEM_P_H
2165
2166=== added file 'modules/Ubuntu/Components/plugin/uclistitemoptions.cpp'
2167--- modules/Ubuntu/Components/plugin/uclistitemoptions.cpp 1970-01-01 00:00:00 +0000
2168+++ modules/Ubuntu/Components/plugin/uclistitemoptions.cpp 2014-09-15 11:00:03 +0000
2169@@ -0,0 +1,526 @@
2170+/*
2171+ * Copyright 2014 Canonical Ltd.
2172+ *
2173+ * This program is free software; you can redistribute it and/or modify
2174+ * it under the terms of the GNU Lesser General Public License as published by
2175+ * the Free Software Foundation; version 3.
2176+ *
2177+ * This program is distributed in the hope that it will be useful,
2178+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2179+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2180+ * GNU Lesser General Public License for more details.
2181+ *
2182+ * You should have received a copy of the GNU Lesser General Public License
2183+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2184+ */
2185+
2186+#include "uclistitemoptions.h"
2187+#include "uclistitemoptions_p.h"
2188+#include "uclistitem_p.h"
2189+#include "quickutils.h"
2190+#include "i18n.h"
2191+#include "plugin.h"
2192+#include <QtQml/QQmlInfo>
2193+#include <QtQuick/private/qquickitem_p.h>
2194+#include <QtQml/private/qqmlabstractbinding_p.h>
2195+#define foreach Q_FOREACH //workaround to fix private includes
2196+#include <QtQml/private/qqmlbinding_p.h> // for QmlBinding
2197+#undef foreach
2198+
2199+
2200+UCListItemOptionsPrivate::UCListItemOptionsPrivate()
2201+ : QObjectPrivate()
2202+ , optionsFailure(false)
2203+ , panelColorChanged(false)
2204+ , iconColorChanged(false)
2205+ , status(UCListItemOptions::Disconnected)
2206+ , delegate(0)
2207+ , panelItem(0)
2208+ , panelColor(Qt::transparent)
2209+ , iconColor(Qt::transparent)
2210+ , optionSlotWidth(0.0)
2211+ , offsetDragged(0.0)
2212+ , optionsVisible(0)
2213+{
2214+}
2215+UCListItemOptionsPrivate::~UCListItemOptionsPrivate()
2216+{
2217+}
2218+
2219+void UCListItemOptionsPrivate::_q_handlePanelDrag()
2220+{
2221+ UCListItem *listItem = qobject_cast<UCListItem*>(panelItem->parentItem());
2222+ if (!listItem) {
2223+ return;
2224+ }
2225+
2226+ Q_Q(UCListItemOptions);
2227+ offsetDragged = (status == UCListItemOptions::LeadingOptions) ? panelItem->width() + panelItem->x() :
2228+ listItem->width() - panelItem->x();
2229+ if (offsetDragged < 0.0) {
2230+ offsetDragged = 0.0;
2231+ }
2232+ if (optionSlotWidth > 0.0) {
2233+ optionsVisible = (int)trunc(offsetDragged / optionSlotWidth);
2234+ }
2235+}
2236+
2237+void UCListItemOptionsPrivate::_q_handlePanelWidth()
2238+{
2239+ optionSlotWidth = panelItem->width() / options.count();
2240+ _q_handlePanelDrag();
2241+}
2242+
2243+void UCListItemOptionsPrivate::funcAppend(QQmlListProperty<QObject> *list, QObject *option)
2244+{
2245+ UCListItemOptions *_this = static_cast<UCListItemOptions*>(list->object);
2246+ UCListItemOptionsPrivate *plist = UCListItemOptionsPrivate::get(_this);
2247+ if (!QuickUtils::inherits(option, "Action")) {
2248+ qmlInfo(_this) << UbuntuI18n::instance().tr(QString("Option at index %1 is not an Action or a derivate of it.").arg(plist->options.size()));
2249+ plist->optionsFailure = true;
2250+ plist->options.clear();
2251+ return;
2252+ }
2253+ if (!plist->optionsFailure) {
2254+ plist->options.append(option);
2255+ }
2256+}
2257+int UCListItemOptionsPrivate::funcCount(QQmlListProperty<QObject> *list)
2258+{
2259+ UCListItemOptions *_this = static_cast<UCListItemOptions*>(list->object);
2260+ UCListItemOptionsPrivate *plist = UCListItemOptionsPrivate::get(_this);
2261+ return plist->options.size();
2262+}
2263+QObject *UCListItemOptionsPrivate::funcAt(QQmlListProperty<QObject> *list, int index)
2264+{
2265+ UCListItemOptions *_this = static_cast<UCListItemOptions*>(list->object);
2266+ UCListItemOptionsPrivate *plist = UCListItemOptionsPrivate::get(_this);
2267+ return plist->options.at(index);
2268+}
2269+void UCListItemOptionsPrivate::funcClear(QQmlListProperty<QObject> *list)
2270+{
2271+ UCListItemOptions *_this = static_cast<UCListItemOptions*>(list->object);
2272+ UCListItemOptionsPrivate *plist = UCListItemOptionsPrivate::get(_this);
2273+ plist->optionsFailure = false;
2274+ return plist->options.clear();
2275+}
2276+
2277+bool UCListItemOptionsPrivate::connectToListItem(UCListItemOptions *options, UCListItem *listItem, bool leading)
2278+{
2279+ UCListItemOptionsPrivate *_this = get(options);
2280+ if (!_this || !_this->createPanelItem() || isConnectedTo(options, listItem)) {
2281+ return isConnectedTo(options, listItem);
2282+ }
2283+ // check if the panel is still connected to a ListItem
2284+ // this may happen if there is a swipe over an other item while the previous
2285+ // one is rebounding
2286+ if (_this->panelItem->parentItem()) {
2287+ // set the requesting listItem as queuedItem
2288+ _this->queuedItem = listItem;
2289+ return false;
2290+ }
2291+ _this->panelItem->setProperty("listItemIndex", UCListItemPrivate::get(listItem)->index);
2292+ _this->panelItem->setProperty("leadingPanel", leading);
2293+ _this->panelItem->setParentItem(listItem);
2294+ _this->offsetDragged = 0.0;
2295+ QObject::connect(_this->panelItem, SIGNAL(selected()), _this->panelItem->parentItem(), SLOT(_q_rebound()));
2296+ _this->status = (leading) ? UCListItemOptions::LeadingOptions : UCListItemOptions::TrailingOptions;
2297+ Q_EMIT options->statusChanged();
2298+ Q_EMIT options->connectedItemChanged();
2299+ return true;
2300+}
2301+
2302+void UCListItemOptionsPrivate::disconnectFromListItem(UCListItemOptions *options)
2303+{
2304+ UCListItemOptionsPrivate *_this = get(options);
2305+ if (!_this || !_this->panelItem || !_this->panelItem->parentItem()) {
2306+ return;
2307+ }
2308+
2309+ QObject::disconnect(_this->panelItem, SIGNAL(selected()), _this->panelItem->parentItem(), SLOT(_q_rebound()));
2310+ _this->panelItem->setParentItem(0);
2311+ _this->status = UCListItemOptions::Disconnected;
2312+ Q_EMIT options->statusChanged();
2313+ Q_EMIT options->connectedItemChanged();
2314+ // if there was a queuedItem, make it grab the options list
2315+ if (_this->queuedItem) {
2316+ UCListItemPrivate::get(_this->queuedItem.data())->grabPanel(options, true);
2317+ // remove item from queue
2318+ _this->queuedItem.clear();
2319+ }
2320+}
2321+
2322+bool UCListItemOptionsPrivate::isConnectedTo(UCListItemOptions *options, UCListItem *listItem)
2323+{
2324+ UCListItemOptionsPrivate *_this = get(options);
2325+ return _this && _this->panelItem &&
2326+ (_this->status != UCListItemOptions::Disconnected) &&
2327+ (_this->panelItem->parentItem() == listItem);
2328+}
2329+
2330+qreal UCListItemOptionsPrivate::snap(UCListItemOptions *options)
2331+{
2332+ UCListItemOptionsPrivate *_this = get(options);
2333+ if (!_this || !_this->panelItem) {
2334+ return 0.0;
2335+ }
2336+ qreal ratio = _this->offsetDragged / _this->optionSlotWidth;
2337+ int visible = _this->optionsVisible;
2338+ if (ratio > 0.0 && (ratio - trunc(ratio)) > 0.5) {
2339+ visible++;
2340+ }
2341+ return visible * _this->optionSlotWidth * (_this->status == UCListItemOptions::LeadingOptions ? 1 : -1);
2342+}
2343+
2344+
2345+QQuickItem *UCListItemOptionsPrivate::createPanelItem()
2346+{
2347+ if (panelItem) {
2348+ return panelItem;
2349+ }
2350+ Q_Q(UCListItemOptions);
2351+ QUrl panelDocument = UbuntuComponentsPlugin::pluginUrl().
2352+ resolved(QUrl::fromLocalFile("ListItemPanel.qml"));
2353+ QQmlComponent component(qmlEngine(q), panelDocument);
2354+ if (!component.isError()) {
2355+ panelItem = qobject_cast<QQuickItem*>(component.beginCreate(qmlContext(q)));
2356+ if (panelItem) {
2357+ QQml_setParent_noEvent(panelItem, q);
2358+ if (delegate) {
2359+ panelItem->setProperty("delegate", QVariant::fromValue(delegate));
2360+ }
2361+ panelItem->setProperty("optionList", QVariant::fromValue(options));
2362+ component.completeCreate();
2363+ if (panelColorChanged) {
2364+ updateColor("panelColor", panelColor);
2365+ }
2366+ if (iconColorChanged) {
2367+ updateColor("iconColor", iconColor);
2368+ }
2369+ Q_EMIT q->panelItemChanged();
2370+
2371+ // calculate option's slot size
2372+ optionSlotWidth = panelItem->width() / options.count();
2373+ offsetDragged = 0.0;
2374+ optionsVisible = 0;
2375+ // connect to panel to catch dragging
2376+ QObject::connect(panelItem, SIGNAL(widthChanged()), q, SLOT(_q_handlePanelWidth()));
2377+ QObject::connect(panelItem, SIGNAL(xChanged()), q, SLOT(_q_handlePanelDrag()));
2378+ }
2379+ } else {
2380+ qmlInfo(q) << component.errorString();
2381+ }
2382+ return panelItem;
2383+}
2384+
2385+void UCListItemOptionsPrivate::updateColor(const char *property, const QColor &color)
2386+{
2387+ if (!panelItem) {
2388+ return;
2389+ }
2390+ Q_Q(UCListItemOptions);
2391+ // get this property binding
2392+ QQmlProperty myColor(q, property, qmlContext(q));
2393+ QQmlBinding *binding = static_cast<QQmlBinding*>(QQmlPropertyPrivate::binding(myColor));
2394+ if (!binding) {
2395+ // this is a simple value assignment, so only override panel's color
2396+ panelItem->setProperty(property, QVariant::fromValue(color));
2397+ } else {
2398+ // transfer the binding to panelItem
2399+ // get panel item's color binding
2400+ QQmlProperty colorProperty(panelItem, property, qmlContext(panelItem));
2401+ QQmlAbstractBinding *prevBinding = QQmlPropertyPrivate::binding(colorProperty);
2402+ if (prevBinding != binding) {
2403+ // clear the previous binding
2404+ prevBinding = QQmlPropertyPrivate::setBinding(colorProperty, 0);
2405+ if (prevBinding) {
2406+ prevBinding->destroy();
2407+ }
2408+ // do the "alias"
2409+ binding->retargetBinding(panelItem, colorProperty.index());
2410+ }
2411+ }
2412+}
2413+
2414+/*!
2415+ * \qmltype ListItemOptions
2416+ * \instantiates UCListItemOptions
2417+ * \inherits QtQObject
2418+ * \inqmlmodule Ubuntu.Components 1.1
2419+ * \ingroup ubuntu
2420+ * \brief Provides configuration for options to be added to a ListItem.
2421+ *
2422+ * ListItem accepts options that can be configured to appear when tugged to left
2423+ * or right. There is no limitation on how many options can be displayed on both
2424+ * sides, this depends on the space available. However design guides say that it
2425+ * can be a maximum of one option on the left (leadng) and a maximum of 3 options
2426+ * on the right (trailing) side of a ListItem.
2427+ *
2428+ * The options are Action instances or elements derived from Action. The default
2429+ * visualization of the options can be overridden using the \l delegate property,
2430+ * and the default implementation uses the \c name property of the Action.
2431+ *
2432+ * The leading and trailing options are placed on \l panelItem, which is created
2433+ * the first time the options are accessed. The colors of the panel is taken from
2434+ * the theme's palette.
2435+ *
2436+ * When tugged, panels reveal the options one by one. In case an option is revealed
2437+ * more than 50%, the option will be snapped and revealed completely. This is also
2438+ * valid for the case when the option is visible less than 50%, in which case the
2439+ * option is hidden. Options can be triggered by tapping.
2440+ *
2441+ * \note You cannot use the same ListItemOptions for leading and for trailing options
2442+ * the same time as when the item content is tugged, both options' panels will be
2443+ * bound to the list item, and teh same item cannot be bount to both edges. However
2444+ * the same set of actions can be used for both options either by using a shared
2445+ * set or by using the others' list. Example:
2446+ * \qml
2447+ * ListItem {
2448+ * leadingOptions: ListItemOptions {
2449+ * Action {
2450+ * iconName: "edit"
2451+ * }
2452+ * Action {
2453+ * iconName: "delete"
2454+ * }
2455+ * }
2456+ * trailingOptions: ListItemOptions {
2457+ * options: leadingOptions.options
2458+ * }
2459+ * }
2460+ * \endqml
2461+ *
2462+ * \section3 Notes on performance
2463+ * When used with views, or when the amount of items of same kind to be created
2464+ * is huge, it is recommended to use cached actions as well as cached ListItemOption
2465+ * instances. In this way we can reduce the creation time of the items:
2466+ * \qml
2467+ * import QtQuick 2.2
2468+ * import Ubuntu.Components 1.1
2469+ *
2470+ * MainView {
2471+ * width: units.gu(40)
2472+ * height: units.gu(71)
2473+ *
2474+ * UbuntuListView {
2475+ * anchors.fill: parent
2476+ * model: 10000
2477+ * ListItemOptions {
2478+ * id: commonOptions
2479+ * Action {
2480+ * iconName: "search"
2481+ * }
2482+ * Action {
2483+ * iconName: "edit"
2484+ * }
2485+ * Action {
2486+ * iconName: "copy"
2487+ * }
2488+ * }
2489+ * delegate: ListItem {
2490+ * trailingOptions: commonOptions
2491+ * }
2492+ * }
2493+ * }
2494+ * \endqml
2495+ */
2496+
2497+UCListItemOptions::UCListItemOptions(QObject *parent)
2498+ : QObject(*(new UCListItemOptionsPrivate), parent)
2499+{
2500+}
2501+UCListItemOptions::~UCListItemOptions()
2502+{
2503+}
2504+
2505+
2506+/*!
2507+ * \qmlproperty Component ListItemOptions::delegate
2508+ * Custom delegate which overrides the default one used by the ListItem. If the
2509+ * value is null, the default delegate will be used.
2510+ *
2511+ * ListItemOptions provides the \c option context property which contains the
2512+ * Action instance currently visualized. Using this property delegates can access
2513+ * the information to be visualized. The trigger is handled by the \l panelItem
2514+ * therefore only visualization is needed by the custom delegates. The other
2515+ * context property exposed to delegates is the \c index, which specifies the
2516+ * index of the option visualized.
2517+ *
2518+ * The delegate height is set automatically by the panelItem, and the width value
2519+ * is clamped between height and the maximum width of the list item divided by the
2520+ * number of options in the list.
2521+ * \qml
2522+ * import QtQuick 2.2
2523+ * import Ubuntu.Components 1.1
2524+ *
2525+ * MainView {
2526+ * width: units.gu(40)
2527+ * height: units.gu(71)
2528+ *
2529+ * UbuntuListView {
2530+ * anchors.fill: parent
2531+ * model: 50
2532+ * delegate: ListItem {
2533+ * trailingOptions: optionsList
2534+ * }
2535+ * ListItemOptions {
2536+ * id: optionsList
2537+ * delegate: Column {
2538+ * width: height + units.gu(2)
2539+ * Icon {
2540+ * name: option.iconName
2541+ * width: units.gu(3)
2542+ * height: width
2543+ * color: "blue"
2544+ * anchors.horizontalCenter: parent.horizontalCenter
2545+ * }
2546+ * Label {
2547+ * text: option.text + "#" + index
2548+ * width: parent.width
2549+ * horizontalAlignment: Text.AlignHCenter
2550+ * }
2551+ * }
2552+ * Action {
2553+ * iconName: "starred"
2554+ * text: "Star"
2555+ * }
2556+ * }
2557+ * }
2558+ * }
2559+ * \endqml
2560+ * \note Putting a Rectangle in the delegate can be used to override the color
2561+ * of the panel.
2562+ *
2563+ * Defaults to null.
2564+ */
2565+QQmlComponent *UCListItemOptions::delegate() const
2566+{
2567+ Q_D(const UCListItemOptions);
2568+ return d->delegate;
2569+}
2570+void UCListItemOptions::setDelegate(QQmlComponent *delegate)
2571+{
2572+ Q_D(UCListItemOptions);
2573+ if (d->delegate == delegate) {
2574+ return;
2575+ }
2576+ d->delegate = delegate;
2577+ if (d->panelItem) {
2578+ // update panel's delegate as well
2579+ d->panelItem->setProperty("delegate", QVariant::fromValue(delegate));
2580+ }
2581+ Q_EMIT delegateChanged();
2582+}
2583+
2584+/*!
2585+ * \qmlproperty list<Action> ListItemOptions::options
2586+ * \default
2587+ * The property holds the options to be displayed. It can hold instances cached or
2588+ * declared in place. An example of cached options:
2589+ * \qml
2590+ * ListItemOptions {
2591+ * id: cacedOptions
2592+ * options: [
2593+ * copyAction, searchAction, cutAction
2594+ * ]
2595+ * }
2596+ * \endqml
2597+ */
2598+QQmlListProperty<QObject> UCListItemOptions::options()
2599+{
2600+ Q_D(UCListItemOptions);
2601+ return QQmlListProperty<QObject>(this, &(d->options),
2602+ &UCListItemOptionsPrivate::funcAppend,
2603+ &UCListItemOptionsPrivate::funcCount,
2604+ &UCListItemOptionsPrivate::funcAt,
2605+ &UCListItemOptionsPrivate::funcClear);
2606+}
2607+
2608+/*!
2609+ * \qmlproperty Item ListItemOptions::panelItem
2610+ * The property presents the Item holding the visualized options. The panel is
2611+ * created when used the first time.
2612+ */
2613+QQuickItem *UCListItemOptions::panelItem() const
2614+{
2615+ Q_D(const UCListItemOptions);
2616+ return d->panelItem;
2617+}
2618+
2619+/*!
2620+ * \qmlproperty enum ListItemOptions::status
2621+ * \readonly
2622+ * The property holds the status of the ListItemOptions, whether is connected
2623+ * as leading or as trailing option list to a \l ListItem. Possible valueas are:
2624+ * \list A
2625+ * \li \b \c Disconnected - default, the options list is not connected to any \l ListItem
2626+ * \li \b \c LeadingOptions - the options list is connected as leading list
2627+ * \li \b \c TrailingOptions - the options list is connected as trailing list
2628+ * \endlist
2629+ * \sa connectedItem
2630+ */
2631+UCListItemOptions::Status UCListItemOptions::status() const
2632+{
2633+ Q_D(const UCListItemOptions);
2634+ return d->status;
2635+}
2636+
2637+/*!
2638+ * \qmlproperty ListItem ListItemOptions::connectedItem
2639+ * \readonly
2640+ * The property holds the \l ListItem the options list is connected to. It is
2641+ * null by default and when the status is \c Disconnected.
2642+ * \sa status
2643+ */
2644+UCListItem *UCListItemOptions::connectedItem() const
2645+{
2646+ Q_D(const UCListItemOptions);
2647+ return d->panelItem ? qobject_cast<UCListItem*>(d->panelItem->parentItem()) : 0;
2648+}
2649+
2650+/*!
2651+ * \qmlproperty color ListItemOptions::panelColor
2652+ * The property overrides the default colouring of the \l panelItem.
2653+ */
2654+QColor UCListItemOptions::panelColor() const
2655+{
2656+ Q_D(const UCListItemOptions);
2657+ return d->panelColor;
2658+}
2659+void UCListItemOptions::setPanelColor(const QColor &color)
2660+{
2661+ Q_D(UCListItemOptions);
2662+ if (d->panelColor == color) {
2663+ return;
2664+ }
2665+ d->panelColor = color;
2666+ d->panelColorChanged = true;
2667+ // update panelItem's color
2668+ d->updateColor("panelColor", d->panelColor);
2669+ Q_EMIT panelColorChanged();
2670+}
2671+
2672+/*!
2673+ * \qmlproperty color ListItemOptions::iconColor
2674+ * The property overrides the default colouring of the icons in the default
2675+ * options visualization.
2676+ */
2677+QColor UCListItemOptions::iconColor() const
2678+{
2679+ Q_D(const UCListItemOptions);
2680+ return d->iconColor;
2681+}
2682+void UCListItemOptions::setIconColor(const QColor &color)
2683+{
2684+ Q_D(UCListItemOptions);
2685+ if (d->iconColor == color) {
2686+ return;
2687+ }
2688+ d->iconColor = color;
2689+ d->iconColorChanged = true;
2690+ // update panelItem's color
2691+ d->updateColor("iconColor", d->iconColor);
2692+ Q_EMIT iconColorChanged();
2693+}
2694+
2695+#include "moc_uclistitemoptions.cpp"
2696
2697=== added file 'modules/Ubuntu/Components/plugin/uclistitemoptions.h'
2698--- modules/Ubuntu/Components/plugin/uclistitemoptions.h 1970-01-01 00:00:00 +0000
2699+++ modules/Ubuntu/Components/plugin/uclistitemoptions.h 2014-09-15 11:00:03 +0000
2700@@ -0,0 +1,75 @@
2701+/*
2702+ * Copyright 2014 Canonical Ltd.
2703+ *
2704+ * This program is free software; you can redistribute it and/or modify
2705+ * it under the terms of the GNU Lesser General Public License as published by
2706+ * the Free Software Foundation; version 3.
2707+ *
2708+ * This program is distributed in the hope that it will be useful,
2709+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2710+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2711+ * GNU Lesser General Public License for more details.
2712+ *
2713+ * You should have received a copy of the GNU Lesser General Public License
2714+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2715+ */
2716+
2717+#ifndef UCLISTITEMOPTIONS_H
2718+#define UCLISTITEMOPTIONS_H
2719+
2720+#include <QtCore/QObject>
2721+#include "uclistitem_p.h"
2722+
2723+class QQmlComponent;
2724+class UCListItemOptionsPrivate;
2725+class QQmlBinding;
2726+class UCListItemOptions : public QObject
2727+{
2728+ Q_OBJECT
2729+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
2730+ Q_PROPERTY(QQmlListProperty<QObject> options READ options CONSTANT)
2731+ Q_PROPERTY(QQuickItem *panelItem READ panelItem NOTIFY panelItemChanged)
2732+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
2733+ Q_PROPERTY(UCListItem *connectedItem READ connectedItem NOTIFY connectedItemChanged)
2734+ Q_PROPERTY(QColor panelColor READ panelColor WRITE setPanelColor NOTIFY panelColorChanged)
2735+ Q_PROPERTY(QColor iconColor READ iconColor WRITE setIconColor NOTIFY iconColorChanged)
2736+ Q_CLASSINFO("DefaultProperty", "options")
2737+ Q_ENUMS(Status)
2738+public:
2739+ enum Status {
2740+ Disconnected = 0,
2741+ LeadingOptions,
2742+ TrailingOptions
2743+ };
2744+
2745+ explicit UCListItemOptions(QObject *parent = 0);
2746+ ~UCListItemOptions();
2747+
2748+ QQmlComponent *delegate() const;
2749+ void setDelegate(QQmlComponent *delegate);
2750+ QQmlListProperty<QObject> options();
2751+ QQuickItem *panelItem() const;
2752+ Status status() const;
2753+ UCListItem *connectedItem() const;
2754+ QColor panelColor() const;
2755+ void setPanelColor(const QColor &color);
2756+ QColor iconColor() const;
2757+ void setIconColor(const QColor &color);
2758+
2759+Q_SIGNALS:
2760+ void delegateChanged();
2761+ void panelItemChanged();
2762+ void statusChanged();
2763+ void connectedItemChanged();
2764+ void panelColorChanged();
2765+ void iconColorChanged();
2766+
2767+public Q_SLOTS:
2768+
2769+private:
2770+ Q_DECLARE_PRIVATE(UCListItemOptions)
2771+ Q_PRIVATE_SLOT(d_func(), void _q_handlePanelDrag())
2772+ Q_PRIVATE_SLOT(d_func(), void _q_handlePanelWidth())
2773+};
2774+
2775+#endif // UCLISTITEMOPTIONS_H
2776
2777=== added file 'modules/Ubuntu/Components/plugin/uclistitemoptions_p.h'
2778--- modules/Ubuntu/Components/plugin/uclistitemoptions_p.h 1970-01-01 00:00:00 +0000
2779+++ modules/Ubuntu/Components/plugin/uclistitemoptions_p.h 2014-09-15 11:00:03 +0000
2780@@ -0,0 +1,66 @@
2781+/*
2782+ * Copyright 2014 Canonical Ltd.
2783+ *
2784+ * This program is free software; you can redistribute it and/or modify
2785+ * it under the terms of the GNU Lesser General Public License as published by
2786+ * the Free Software Foundation; version 3.
2787+ *
2788+ * This program is distributed in the hope that it will be useful,
2789+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2790+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2791+ * GNU Lesser General Public License for more details.
2792+ *
2793+ * You should have received a copy of the GNU Lesser General Public License
2794+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2795+ */
2796+
2797+#ifndef UCLISTITEMOPTIONS_P_H
2798+#define UCLISTITEMOPTIONS_P_H
2799+
2800+#include "uclistitemoptions.h"
2801+#include "QtCore/private/qobject_p.h"
2802+
2803+class UCListItem;
2804+class UCListItemOptionsPrivate : public QObjectPrivate {
2805+ Q_DECLARE_PUBLIC(UCListItemOptions)
2806+public:
2807+ UCListItemOptionsPrivate();
2808+ ~UCListItemOptionsPrivate();
2809+ static UCListItemOptionsPrivate* get(UCListItemOptions *options)
2810+ {
2811+ return options ? options->d_func() : 0;
2812+ }
2813+
2814+ bool optionsFailure:1;
2815+ bool panelColorChanged:1;
2816+ bool iconColorChanged:1;
2817+ UCListItemOptions::Status status;
2818+ QQmlComponent *delegate;
2819+ QQuickItem *panelItem;
2820+ QColor panelColor;
2821+ QColor iconColor;
2822+ QList<QObject*> options;
2823+ QPointer<UCListItem> queuedItem;
2824+ qreal optionSlotWidth;
2825+ qreal offsetDragged;
2826+ int optionsVisible;
2827+
2828+ void _q_handlePanelDrag();
2829+ void _q_handlePanelWidth();
2830+
2831+ // options list property functions
2832+ static void funcAppend(QQmlListProperty<QObject>*, QObject*);
2833+ static int funcCount(QQmlListProperty<QObject>*);
2834+ static QObject *funcAt(QQmlListProperty<QObject>*, int);
2835+ static void funcClear(QQmlListProperty<QObject>*);
2836+
2837+ static bool connectToListItem(UCListItemOptions *options, UCListItem *listItem, bool leading);
2838+ static void disconnectFromListItem(UCListItemOptions *options);
2839+ static bool isConnectedTo(UCListItemOptions *options, UCListItem *listItem);
2840+ static qreal snap(UCListItemOptions *options);
2841+
2842+ QQuickItem *createPanelItem();
2843+ void updateColor(const char *property, const QColor &color);
2844+};
2845+
2846+#endif // UCLISTITEMOPTIONS_P_H
2847
2848=== modified file 'modules/Ubuntu/Components/qmldir'
2849--- modules/Ubuntu/Components/qmldir 2014-09-03 08:17:24 +0000
2850+++ modules/Ubuntu/Components/qmldir 2014-09-15 11:00:03 +0000
2851@@ -106,3 +106,5 @@
2852 Icon 1.1 Icon11.qml
2853 StyledItem 1.1 StyledItem.qml
2854 singleton UbuntuColors 1.1 11/UbuntuColors.qml
2855+internal ListItemPanel ListItemPanel.qml
2856+internal ListItemSelectablePanel ListItemSelectablePanel.qml
2857
2858=== modified file 'tests/autopilot/ubuntuuitoolkit/environment.py'
2859--- tests/autopilot/ubuntuuitoolkit/environment.py 2014-03-10 18:53:37 +0000
2860+++ tests/autopilot/ubuntuuitoolkit/environment.py 2014-09-15 11:00:03 +0000
2861@@ -23,42 +23,46 @@
2862 logger = logging.getLogger(__name__)
2863
2864
2865-def is_initctl_env_var_set(variable):
2866+def is_initctl_env_var_set(variable, global_=False):
2867 """Check True if an initctl environment variable is set.
2868
2869 :param variable: The name of the variable to check.
2870+ :param global: if True, the method will operate on the global environment
2871+ table. Default is False.
2872 :return: True if the variable is set. False otherwise.
2873
2874 """
2875 try:
2876- get_initctl_env_var(variable)
2877+ get_initctl_env_var(variable, global_)
2878 return True
2879 except subprocess.CalledProcessError:
2880 return False
2881
2882
2883-def get_initctl_env_var(variable):
2884+def get_initctl_env_var(variable, global_=False):
2885 """Return the value of an initctl environment variable."""
2886+ command = ['/sbin/initctl', 'get-env', variable]
2887+ if global_:
2888+ command += ['--global']
2889 output = subprocess.check_output(
2890- ['/sbin/initctl', 'get-env', variable],
2891- stderr=subprocess.STDOUT,
2892- universal_newlines=True)
2893+ command, stderr=subprocess.STDOUT, universal_newlines=True)
2894 return output.rstrip()
2895
2896
2897 @autopilot_logging.log_action(logger.info)
2898-def set_initctl_env_var(variable, value):
2899+def set_initctl_env_var(variable, value, global_=False):
2900 """Set the value of an initctl environment variable."""
2901- subprocess.call(
2902- ['/sbin/initctl', 'set-env', '%s=%s' % (variable, value)],
2903- stderr=subprocess.STDOUT,
2904- universal_newlines=True)
2905+ command = ['/sbin/initctl', 'set-env', '%s=%s' % (variable, value)]
2906+ if global_:
2907+ command += ['--global']
2908+ subprocess.call(command, stderr=subprocess.STDOUT, universal_newlines=True)
2909
2910
2911 @autopilot_logging.log_action(logger.info)
2912-def unset_initctl_env_var(variable):
2913+def unset_initctl_env_var(variable, global_=False):
2914 """Remove an initctl environment variable."""
2915+ command = ['/sbin/initctl', 'unset-env', variable]
2916+ if global_:
2917+ command += ['--global']
2918 subprocess.call(
2919- ['/sbin/initctl', 'unset-env', variable],
2920- stderr=subprocess.STDOUT,
2921- universal_newlines=True)
2922+ command, stderr=subprocess.STDOUT, universal_newlines=True)
2923
2924=== modified file 'tests/autopilot/ubuntuuitoolkit/fixture_setup.py'
2925--- tests/autopilot/ubuntuuitoolkit/fixture_setup.py 2014-07-22 07:44:40 +0000
2926+++ tests/autopilot/ubuntuuitoolkit/fixture_setup.py 2014-09-15 11:00:03 +0000
2927@@ -115,24 +115,36 @@
2928 class InitctlEnvironmentVariable(fixtures.Fixture):
2929 """Set the value of initctl environment variables."""
2930
2931- def __init__(self, **kwargs):
2932+ def __init__(self, global_=False, **kwargs):
2933 super(InitctlEnvironmentVariable, self).__init__()
2934+ # Added one level of indirection to be able to spy the calls to
2935+ # environment during tests.
2936+ self.environment = environment
2937 self.variables = kwargs
2938+ self.global_ = global_
2939
2940 def setUp(self):
2941 super(InitctlEnvironmentVariable, self).setUp()
2942 for variable, value in self.variables.items():
2943 self._add_variable_cleanup(variable)
2944- environment.set_initctl_env_var(variable, value)
2945+ self.environment.set_initctl_env_var(
2946+ variable, value, global_=self.global_)
2947
2948 def _add_variable_cleanup(self, variable):
2949- if environment.is_initctl_env_var_set(variable):
2950- original_value = environment.get_initctl_env_var(variable)
2951+ if self.environment.is_initctl_env_var_set(
2952+ variable, global_=self.global_):
2953+ original_value = self.environment.get_initctl_env_var(
2954+ variable, global_=self.global_)
2955 self.addCleanup(
2956- environment.set_initctl_env_var, variable,
2957- original_value)
2958+ self.environment.set_initctl_env_var,
2959+ variable,
2960+ original_value,
2961+ global_=self.global_)
2962 else:
2963- self.addCleanup(environment.unset_initctl_env_var, variable)
2964+ self.addCleanup(
2965+ self.environment.unset_initctl_env_var,
2966+ variable,
2967+ global_=self.global_)
2968
2969
2970 class FakeHome(fixtures.Fixture):
2971
2972=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/__init__.py'
2973--- tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2014-07-24 21:28:05 +0000
2974+++ tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2014-09-15 11:00:03 +0000
2975@@ -87,6 +87,9 @@
2976 qml_file_contents=self.test_qml)
2977 self.useFixture(fake_application)
2978
2979+ local_modules_path = _get_module_include_path()
2980+ if os.path.exists(local_modules_path):
2981+ self.use_local_modules(local_modules_path)
2982 desktop_file_name = os.path.basename(
2983 fake_application.desktop_file_path)
2984 application_name, _ = os.path.splitext(desktop_file_name)
2985@@ -94,6 +97,17 @@
2986 application_name,
2987 emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
2988
2989+ def use_local_modules(self, local_modules_path):
2990+ env_vars = [
2991+ 'QML_IMPORT_PATH',
2992+ 'QML2_IMPORT_PATH',
2993+ 'UBUNTU_UI_TOOLKIT_THEMES_PATH'
2994+ ]
2995+ kwargs = {'global_': True}
2996+ for env in env_vars:
2997+ kwargs[env] = local_modules_path
2998+ self.useFixture(fixture_setup.InitctlEnvironmentVariable(**kwargs))
2999+
3000
3001 class QMLStringAppTestCase(UbuntuUIToolkitWithFakeAppRunningTestCase):
3002 """Base test case for self tests that define the QML on an string."""
3003
3004=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/test_environment.py'
3005--- tests/autopilot/ubuntuuitoolkit/tests/test_environment.py 2014-03-13 17:54:38 +0000
3006+++ tests/autopilot/ubuntuuitoolkit/tests/test_environment.py 2014-09-15 11:00:03 +0000
3007@@ -30,7 +30,7 @@
3008
3009 def test_is_environment_variable_set_with_set_variable(self):
3010 """Test that is_initctl_env_var_set returns True for existing vars."""
3011- variable = 'Testvariabletoset'
3012+ variable = 'Test variable to set {}'.format(uuid.uuid1())
3013 self.addCleanup(environment.unset_initctl_env_var, variable)
3014
3015 environment.set_initctl_env_var(variable, 'dummy')
3016@@ -39,7 +39,7 @@
3017
3018 def test_get_environment_variable(self):
3019 """Test that get_initctl_env_var returns the right value."""
3020- variable = 'Testvariabletoget'
3021+ variable = 'Test variable to get {}'.format(uuid.uuid1())
3022 self.addCleanup(environment.unset_initctl_env_var, variable)
3023 environment.set_initctl_env_var(variable, 'test value')
3024
3025@@ -48,7 +48,7 @@
3026
3027 def test_unset_environment_variable(self):
3028 """Test that unset_initctl_env_var removes the variable."""
3029- variable = 'Testvariabletoget'
3030+ variable = 'Test variable to unset {}'.format(uuid.uuid1())
3031 environment.set_initctl_env_var(variable, 'dummy')
3032
3033 environment.unset_initctl_env_var(variable)
3034@@ -57,8 +57,33 @@
3035
3036 def test_unset_environment_variable_with_unset_variable(self):
3037 """Test that unset_initctl_env_var does nothing with unset var."""
3038- variable = 'I do not exist'
3039+ variable = 'I do not exist {}'.format(uuid.uuid1())
3040
3041 environment.unset_initctl_env_var(variable)
3042
3043 self.assertFalse(environment.is_initctl_env_var_set(variable))
3044+
3045+ def test_is_global_environment_variable_set_with_unset_variable(self):
3046+ """Test is_initctl_env_var_set returns False for unset global vars."""
3047+ variable = 'I do not exist global {}'.format(uuid.uuid1())
3048+
3049+ self.assertFalse(environment.is_initctl_env_var_set(
3050+ variable, global_=True))
3051+
3052+ def test_get_global_environment_variable(self):
3053+ """Test that get_initctl_env_var returns the right global value."""
3054+ variable = 'Test variable to get {}'.format(uuid.uuid1())
3055+ self.addCleanup(
3056+ environment.unset_initctl_env_var, variable, global_=True)
3057+ environment.set_initctl_env_var(variable, 'test value', global_=True)
3058+
3059+ self.assertEqual(
3060+ 'test value',
3061+ environment.get_initctl_env_var(variable, global_=True))
3062+
3063+ def test_unset_global_environment_variable(self):
3064+ """Test that unset_initctl_env_var removes the global variable."""
3065+ variable = 'Test variable to unset {}'.format(uuid.uuid1())
3066+
3067+ environment.set_initctl_env_var(variable, 'dummy', global_=True)
3068+ environment.unset_initctl_env_var(variable, global_=True)
3069
3070=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py'
3071--- tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py 2014-06-26 04:28:38 +0000
3072+++ tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py 2014-09-15 11:00:03 +0000
3073@@ -23,6 +23,7 @@
3074 except ImportError:
3075 # Python 2 add-on: python-mock.
3076 import mock
3077+import testscenarios
3078 import testtools
3079 from autopilot import (
3080 display,
3081@@ -241,6 +242,95 @@
3082 environment.get_initctl_env_var('testenvvarforfixture'))
3083
3084
3085+class InitctlGlobalEnvironmentVariableTestCase(
3086+ testscenarios.TestWithScenarios):
3087+
3088+ scenarios = [
3089+ ('global unset variable', {
3090+ 'is_variable_set': False,
3091+ 'variable_value': 'dummy',
3092+ 'global_value': 'value',
3093+ 'expected_calls': [
3094+ mock.call.is_initctl_env_var_set(
3095+ 'testenvvarforfixture', global_='value'),
3096+ mock.call.set_initctl_env_var(
3097+ 'testenvvarforfixture', 'new test value', global_='value'),
3098+ mock.call.unset_initctl_env_var(
3099+ 'testenvvarforfixture', global_='value')
3100+ ]
3101+ }),
3102+ ('global set variable', {
3103+ 'is_variable_set': True,
3104+ 'variable_value': 'original_value',
3105+ 'global_value': 'value',
3106+ 'expected_calls': [
3107+ mock.call.is_initctl_env_var_set(
3108+ 'testenvvarforfixture', global_='value'),
3109+ mock.call.get_initctl_env_var(
3110+ 'testenvvarforfixture', global_='value'),
3111+ mock.call.set_initctl_env_var(
3112+ 'testenvvarforfixture', 'new test value', global_='value'),
3113+ mock.call.set_initctl_env_var(
3114+ 'testenvvarforfixture', 'original_value', global_='value')
3115+ ]
3116+ }),
3117+ ('default unset variable', {
3118+ 'is_variable_set': False,
3119+ 'variable_value': 'dummy',
3120+ 'global_value': 'default',
3121+ 'expected_calls': [
3122+ mock.call.is_initctl_env_var_set(
3123+ 'testenvvarforfixture', global_=False),
3124+ mock.call.set_initctl_env_var(
3125+ 'testenvvarforfixture', 'new test value', global_=False),
3126+ mock.call.unset_initctl_env_var(
3127+ 'testenvvarforfixture', global_=False)
3128+ ]
3129+ }),
3130+ ('global set variable', {
3131+ 'is_variable_set': True,
3132+ 'variable_value': 'original_value',
3133+ 'global_value': 'default',
3134+ 'expected_calls': [
3135+ mock.call.is_initctl_env_var_set(
3136+ 'testenvvarforfixture', global_=False),
3137+ mock.call.get_initctl_env_var(
3138+ 'testenvvarforfixture', global_=False),
3139+ mock.call.set_initctl_env_var(
3140+ 'testenvvarforfixture', 'new test value', global_=False),
3141+ mock.call.set_initctl_env_var(
3142+ 'testenvvarforfixture', 'original_value', global_=False)
3143+ ]
3144+ }),
3145+ ]
3146+
3147+ def test_use_initctl_environment_variable_with_global_unset_variable(self):
3148+ if self.global_value == 'default':
3149+ initctl_env_var = fixture_setup.InitctlEnvironmentVariable(
3150+ testenvvarforfixture='new test value')
3151+ else:
3152+ initctl_env_var = fixture_setup.InitctlEnvironmentVariable(
3153+ testenvvarforfixture='new test value',
3154+ global_=self.global_value)
3155+
3156+ mock_env = mock.Mock()
3157+ initctl_env_var.environment = mock_env
3158+ mock_env.is_initctl_env_var_set.return_value = self.is_variable_set
3159+ mock_env.get_initctl_env_var.return_value = self.variable_value
3160+
3161+ def inner_test():
3162+ class TestWithInitctlEnvVar(testtools.TestCase):
3163+ def test_it(self):
3164+ self.useFixture(initctl_env_var)
3165+
3166+ return TestWithInitctlEnvVar('test_it')
3167+
3168+ inner_test().run()
3169+
3170+ self.assertEquals(
3171+ self.expected_calls, mock_env.mock_calls)
3172+
3173+
3174 class FakeHomeTestCase(testtools.TestCase):
3175
3176 def test_fake_home_fixture_patches_initctl_env_var(self):
3177
3178=== added directory 'tests/resources/listitems'
3179=== added file 'tests/resources/listitems/InUListView.qml'
3180--- tests/resources/listitems/InUListView.qml 1970-01-01 00:00:00 +0000
3181+++ tests/resources/listitems/InUListView.qml 2014-09-15 11:00:03 +0000
3182@@ -0,0 +1,63 @@
3183+/*
3184+ * Copyright 2014 Canonical Ltd.
3185+ *
3186+ * This program is free software; you can redistribute it and/or modify
3187+ * it under the terms of the GNU Lesser General Public License as published by
3188+ * the Free Software Foundation; version 3.
3189+ *
3190+ * This program is distributed in the hope that it will be useful,
3191+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3192+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3193+ * GNU Lesser General Public License for more details.
3194+ *
3195+ * You should have received a copy of the GNU Lesser General Public License
3196+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3197+ */
3198+
3199+import QtQuick 2.2
3200+import Ubuntu.Components 1.1
3201+
3202+MainView {
3203+ id: main
3204+ width: units.gu(50)
3205+ height: units.gu(100)
3206+
3207+ Page {
3208+ title: "UbuntuListView"
3209+
3210+ UbuntuListView {
3211+ anchors.fill: parent
3212+
3213+ ListItemOptions {
3214+ id: stockOptions
3215+ Action {
3216+ iconName: "edit"
3217+ }
3218+ Action {
3219+ iconName: "share"
3220+ }
3221+ Action {
3222+ iconName: "attachment"
3223+ }
3224+ }
3225+
3226+ model: ListModel {
3227+ Component.onCompleted: {
3228+ for (var i = 0; i < 20; i++)
3229+ append({data: "List item mode #" + i})
3230+ }
3231+ }
3232+
3233+ delegate: ListItem {
3234+ trailingOptions: stockOptions
3235+
3236+ Label {
3237+ anchors.fill: parent
3238+ horizontalAlignment: Text.AlignHCenter
3239+ verticalAlignment: Text.AlignVCenter
3240+ text: modelData + " @index " + index
3241+ }
3242+ }
3243+ }
3244+ }
3245+}
3246
3247=== added file 'tests/resources/listitems/ListItemTest.qml'
3248--- tests/resources/listitems/ListItemTest.qml 1970-01-01 00:00:00 +0000
3249+++ tests/resources/listitems/ListItemTest.qml 2014-09-15 11:00:03 +0000
3250@@ -0,0 +1,186 @@
3251+/*
3252+ * Copyright 2014 Canonical Ltd.
3253+ *
3254+ * This program is free software; you can redistribute it and/or modify
3255+ * it under the terms of the GNU Lesser General Public License as published by
3256+ * the Free Software Foundation; version 3.
3257+ *
3258+ * This program is distributed in the hope that it will be useful,
3259+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3260+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3261+ * GNU Lesser General Public License for more details.
3262+ *
3263+ * You should have received a copy of the GNU Lesser General Public License
3264+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3265+ */
3266+
3267+import QtQuick 2.2
3268+import Ubuntu.Components 1.1
3269+
3270+MainView {
3271+ id: main
3272+ width: units.gu(50)
3273+ height: units.gu(100)
3274+
3275+ property bool override: false
3276+
3277+ Action {
3278+ objectName: "stock"
3279+ id: stock
3280+ iconName: "starred"
3281+ text: "Staaaar"
3282+ onTriggered: print(iconName, "triggered", value)
3283+ }
3284+
3285+ ListItemOptions {
3286+ id: leading
3287+ objectName: "StockLeading"
3288+ Action {
3289+ iconName: "delete"
3290+ onTriggered: { print(iconName, "triggered", value)
3291+ leading.panelColor = Qt.binding(function() {
3292+ return (leading.status == ListItemOptions.LeadingOptions) ? UbuntuColors.blue : UbuntuColors.lightGrey;
3293+ })
3294+ leading.iconColor = Qt.binding(function() {
3295+ return (leading.status == ListItemOptions.LeadingOptions) ? "white" : UbuntuColors.red;
3296+ })
3297+ }
3298+ }
3299+ Action {
3300+ iconName: "alarm-clock"
3301+ enabled: false
3302+ onTriggered: print(iconName, "triggered")
3303+ }
3304+ Action {
3305+ iconName: "camcorder"
3306+ onTriggered: print(iconName, "triggered", value)
3307+ }
3308+ Action {
3309+ iconName: "stock_website"
3310+ onTriggered: print(iconName, "triggered", value)
3311+ }
3312+ }
3313+
3314+ property bool selectable: false
3315+ Column {
3316+ anchors {
3317+ left: parent.left
3318+ right: parent.right
3319+ }
3320+
3321+ Button {
3322+ text: "Selectable " + (selectable ? "OFF" : "ON")
3323+ onClicked: selectable = !selectable
3324+ }
3325+
3326+ ListItem {
3327+ id: testItem
3328+ objectName: "single"
3329+ selectable: main.selectable
3330+ onClicked: {
3331+ print("click")
3332+ main.override = !main.override
3333+ }
3334+ onPressAndHold: print("pressAndHold", objectName)
3335+ Label {
3336+ anchors.fill: parent
3337+ text: units.gridUnit + "PX/unit"
3338+ }
3339+ leadingOptions: ListItemOptions {
3340+ objectName: "InlineLeading"
3341+ options: [stock]
3342+ delegate: Column {
3343+ width: height + units.gu(2)
3344+ Icon {
3345+ width: units.gu(3)
3346+ height: width
3347+ name: option.iconName
3348+ color: "blue"
3349+ anchors.horizontalCenter: parent.horizontalCenter
3350+ }
3351+ Label {
3352+ text: option.text + index
3353+ width: parent.width
3354+ horizontalAlignment: Text.AlignHCenter
3355+ }
3356+ }
3357+ }
3358+ trailingOptions: leading
3359+ }
3360+
3361+ ListView {
3362+ id: view
3363+ clip: true
3364+ width: parent.width
3365+ height: units.gu(36)
3366+ model: 10
3367+ pressDelay: 0
3368+ delegate: ListItem {
3369+ objectName: "ListItem" + index
3370+ id: listItem
3371+ selectable: main.selectable
3372+ selected: true
3373+ onClicked: print(" clicked")
3374+ leadingOptions: leading
3375+ Label {
3376+ text: modelData + " item"
3377+ }
3378+ states: State {
3379+ name: "override"
3380+ when: main.override
3381+ PropertyChanges {
3382+ target: listItem.contentItem
3383+ pressedColor: "brown"
3384+ }
3385+ }
3386+ }
3387+ }
3388+ Flickable {
3389+ id: flicker
3390+ width: parent.width
3391+ height: units.gu(36)
3392+ clip: true
3393+ contentHeight: column.childrenRect.height
3394+ Column {
3395+ id: column
3396+ width: view.width
3397+ property alias count: repeater.count
3398+ Repeater {
3399+ id: repeater
3400+ model: 10
3401+ ListItem {
3402+ objectName: "InFlickable"+index
3403+ selectable: main.selectable
3404+ leadingOptions: ListItemOptions {
3405+ id: optionData
3406+ panelColor: "pink"
3407+ Action {
3408+ iconName: "edit"
3409+ onTriggered: print(iconName, "triggered", value)
3410+ }
3411+ Action {
3412+ iconName: "delete"
3413+ onTriggered: print(iconName, "triggered", value)
3414+ }
3415+ }
3416+ trailingOptions: ListItemOptions {
3417+ panelColor: leadingOptions.panelColor
3418+ options: leadingOptions.options
3419+ }
3420+
3421+ contentItem {
3422+ color: UbuntuColors.red
3423+ pressedColor: "lime"
3424+ }
3425+ divider.colorFrom: UbuntuColors.green
3426+
3427+ Label {
3428+ text: modelData + " Flickable item"
3429+ }
3430+ onClicked: divider.visible = !divider.visible
3431+ }
3432+ }
3433+ }
3434+ }
3435+ }
3436+}
3437
3438=== added file 'tests/unit/tst_performance/ItemList.qml'
3439--- tests/unit/tst_performance/ItemList.qml 1970-01-01 00:00:00 +0000
3440+++ tests/unit/tst_performance/ItemList.qml 2014-09-15 11:00:03 +0000
3441@@ -0,0 +1,30 @@
3442+/*
3443+ * Copyright 2014 Canonical Ltd.
3444+ *
3445+ * This program is free software; you can redistribute it and/or modify
3446+ * it under the terms of the GNU Lesser General Public License as published by
3447+ * the Free Software Foundation; version 3.
3448+ *
3449+ * This program is distributed in the hope that it will be useful,
3450+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3451+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3452+ * GNU Lesser General Public License for more details.
3453+ *
3454+ * You should have received a copy of the GNU Lesser General Public License
3455+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3456+ */
3457+
3458+import QtQuick 2.0
3459+import Ubuntu.Components 1.1
3460+
3461+Column {
3462+ width: 800
3463+ height: 600
3464+ Repeater {
3465+ model: 5000
3466+ Item {
3467+ width: parent.width
3468+ height: units.gu(6)
3469+ }
3470+ }
3471+}
3472
3473=== added file 'tests/unit/tst_performance/ListItemList.qml'
3474--- tests/unit/tst_performance/ListItemList.qml 1970-01-01 00:00:00 +0000
3475+++ tests/unit/tst_performance/ListItemList.qml 2014-09-15 11:00:03 +0000
3476@@ -0,0 +1,30 @@
3477+/*
3478+ * Copyright 2014 Canonical Ltd.
3479+ *
3480+ * This program is free software; you can redistribute it and/or modify
3481+ * it under the terms of the GNU Lesser General Public License as published by
3482+ * the Free Software Foundation; version 3.
3483+ *
3484+ * This program is distributed in the hope that it will be useful,
3485+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3486+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3487+ * GNU Lesser General Public License for more details.
3488+ *
3489+ * You should have received a copy of the GNU Lesser General Public License
3490+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3491+ */
3492+
3493+import QtQuick 2.0
3494+import Ubuntu.Components 1.1
3495+
3496+Column {
3497+ width: 800
3498+ height: 600
3499+ property alias count: repeater.count
3500+ Repeater {
3501+ id: repeater
3502+ model: 5000
3503+ ListItem {
3504+ }
3505+ }
3506+}
3507
3508=== added file 'tests/unit/tst_performance/ListItemWithInlineOptionsList.qml'
3509--- tests/unit/tst_performance/ListItemWithInlineOptionsList.qml 1970-01-01 00:00:00 +0000
3510+++ tests/unit/tst_performance/ListItemWithInlineOptionsList.qml 2014-09-15 11:00:03 +0000
3511@@ -0,0 +1,39 @@
3512+/*
3513+ * Copyright 2014 Canonical Ltd.
3514+ *
3515+ * This program is free software; you can redistribute it and/or modify
3516+ * it under the terms of the GNU Lesser General Public License as published by
3517+ * the Free Software Foundation; version 3.
3518+ *
3519+ * This program is distributed in the hope that it will be useful,
3520+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3521+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3522+ * GNU Lesser General Public License for more details.
3523+ *
3524+ * You should have received a copy of the GNU Lesser General Public License
3525+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3526+ */
3527+
3528+import QtQuick 2.0
3529+import Ubuntu.Components 1.1
3530+
3531+Column {
3532+ width: 800
3533+ height: 600
3534+
3535+ Repeater {
3536+ model: 5000
3537+ ListItem {
3538+ trailingOptions: ListItemOptions {
3539+ Action {}
3540+ Action {}
3541+ Action {}
3542+ }
3543+ leadingOptions: ListItemOptions {
3544+ Action {}
3545+ Action {}
3546+ Action {}
3547+ }
3548+ }
3549+ }
3550+}
3551
3552=== added file 'tests/unit/tst_performance/ListItemWithLeadingOptionsList.qml'
3553--- tests/unit/tst_performance/ListItemWithLeadingOptionsList.qml 1970-01-01 00:00:00 +0000
3554+++ tests/unit/tst_performance/ListItemWithLeadingOptionsList.qml 2014-09-15 11:00:03 +0000
3555@@ -0,0 +1,41 @@
3556+/*
3557+ * Copyright 2014 Canonical Ltd.
3558+ *
3559+ * This program is free software; you can redistribute it and/or modify
3560+ * it under the terms of the GNU Lesser General Public License as published by
3561+ * the Free Software Foundation; version 3.
3562+ *
3563+ * This program is distributed in the hope that it will be useful,
3564+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3565+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3566+ * GNU Lesser General Public License for more details.
3567+ *
3568+ * You should have received a copy of the GNU Lesser General Public License
3569+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3570+ */
3571+
3572+import QtQuick 2.0
3573+import Ubuntu.Components 1.1
3574+
3575+Column {
3576+ width: 800
3577+ height: 600
3578+ property ListItemOptions leadingOptions: options1
3579+ ListItemOptions {
3580+ id: options1
3581+ Action {}
3582+ }
3583+ ListItemOptions {
3584+ id: options2
3585+ Action {}
3586+ Action {}
3587+ Action {}
3588+ }
3589+
3590+ Repeater {
3591+ model: 5000
3592+ ListItem {
3593+ trailingOptions: options1
3594+ }
3595+ }
3596+}
3597
3598=== added file 'tests/unit/tst_performance/ListItemWithOptionsList.qml'
3599--- tests/unit/tst_performance/ListItemWithOptionsList.qml 1970-01-01 00:00:00 +0000
3600+++ tests/unit/tst_performance/ListItemWithOptionsList.qml 2014-09-15 11:00:03 +0000
3601@@ -0,0 +1,41 @@
3602+/*
3603+ * Copyright 2014 Canonical Ltd.
3604+ *
3605+ * This program is free software; you can redistribute it and/or modify
3606+ * it under the terms of the GNU Lesser General Public License as published by
3607+ * the Free Software Foundation; version 3.
3608+ *
3609+ * This program is distributed in the hope that it will be useful,
3610+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3611+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3612+ * GNU Lesser General Public License for more details.
3613+ *
3614+ * You should have received a copy of the GNU Lesser General Public License
3615+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3616+ */
3617+
3618+import QtQuick 2.0
3619+import Ubuntu.Components 1.1
3620+
3621+Column {
3622+ width: 800
3623+ height: 600
3624+ ListItemOptions {
3625+ id: options1
3626+ Action {}
3627+ }
3628+ ListItemOptions {
3629+ id: options2
3630+ Action {}
3631+ Action {}
3632+ Action {}
3633+ }
3634+
3635+ Repeater {
3636+ model: 5000
3637+ ListItem {
3638+ trailingOptions: options1
3639+ leadingOptions: options2
3640+ }
3641+ }
3642+}
3643
3644=== added file 'tests/unit/tst_performance/ListItemsBaseList.qml'
3645--- tests/unit/tst_performance/ListItemsBaseList.qml 1970-01-01 00:00:00 +0000
3646+++ tests/unit/tst_performance/ListItemsBaseList.qml 2014-09-15 11:00:03 +0000
3647@@ -0,0 +1,29 @@
3648+/*
3649+ * Copyright 2014 Canonical Ltd.
3650+ *
3651+ * This program is free software; you can redistribute it and/or modify
3652+ * it under the terms of the GNU Lesser General Public License as published by
3653+ * the Free Software Foundation; version 3.
3654+ *
3655+ * This program is distributed in the hope that it will be useful,
3656+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3657+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3658+ * GNU Lesser General Public License for more details.
3659+ *
3660+ * You should have received a copy of the GNU Lesser General Public License
3661+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3662+ */
3663+
3664+import QtQuick 2.0
3665+import Ubuntu.Components 1.1
3666+import Ubuntu.Components.ListItems 1.0 as ListItems
3667+
3668+Column {
3669+ width: 800
3670+ height: 600
3671+ Repeater {
3672+ model: 5000
3673+ ListItems.Base {
3674+ }
3675+ }
3676+}
3677
3678=== added file 'tests/unit/tst_performance/ListItemsEmptyList.qml'
3679--- tests/unit/tst_performance/ListItemsEmptyList.qml 1970-01-01 00:00:00 +0000
3680+++ tests/unit/tst_performance/ListItemsEmptyList.qml 2014-09-15 11:00:03 +0000
3681@@ -0,0 +1,29 @@
3682+/*
3683+ * Copyright 2014 Canonical Ltd.
3684+ *
3685+ * This program is free software; you can redistribute it and/or modify
3686+ * it under the terms of the GNU Lesser General Public License as published by
3687+ * the Free Software Foundation; version 3.
3688+ *
3689+ * This program is distributed in the hope that it will be useful,
3690+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3691+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3692+ * GNU Lesser General Public License for more details.
3693+ *
3694+ * You should have received a copy of the GNU Lesser General Public License
3695+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3696+ */
3697+
3698+import QtQuick 2.0
3699+import Ubuntu.Components 1.1
3700+import Ubuntu.Components.ListItems 1.0 as ListItems
3701+
3702+Column {
3703+ width: 800
3704+ height: 600
3705+ Repeater {
3706+ model: 5000
3707+ ListItems.Empty {
3708+ }
3709+ }
3710+}
3711
3712=== modified file 'tests/unit/tst_performance/tst_performance.cpp'
3713--- tests/unit/tst_performance/tst_performance.cpp 2013-06-27 15:35:23 +0000
3714+++ tests/unit/tst_performance/tst_performance.cpp 2014-09-15 11:00:03 +0000
3715@@ -74,16 +74,16 @@
3716 QTest::newRow("grid with Label") << "LabelGrid.qml" << QUrl();
3717 QTest::newRow("grid with UbuntuShape") << "UbuntuShapeGrid.qml" << QUrl();
3718 QTest::newRow("grid with UbuntuShapePair") << "PairOfUbuntuShapeGrid.qml" << QUrl();
3719- QTest::newRow("grid with ButtonStyle") << "ButtonStyleGrid.qml" << QUrl();
3720 QTest::newRow("grid with Button") << "ButtonGrid.qml" << QUrl();
3721-// QTest::newRow("grid with CheckBoxStyle") << "CheckBoxStyleGrid.qml" << QUrl();
3722-// QTest::newRow("grid with CheckBox") << "CheckBoxGrid.qml" << QUrl();
3723-// QTest::newRow("grid with SwitchStyle") << "SwitchStyleGrid.qml" << QUrl();
3724-// QTest::newRow("grid with Switch") << "SwitchGrid.qml" << QUrl();
3725-// QTest::newRow("grid with SwitchStyle") << "SwitchStyleGrid.qml" << QUrl();
3726-// QTest::newRow("grid with Switch") << "SwitchGrid.qml" << QUrl();
3727- QTest::newRow("grid with SliderStyle") << "SliderStyleGrid.qml" << QUrl();
3728 QTest::newRow("grid with Slider") << "SliderGrid.qml" << QUrl();
3729+ QTest::newRow("list with QtQuick Item") << "ItemList.qml" << QUrl();
3730+ QTest::newRow("list with new ListItem") << "ListItemList.qml" << QUrl();
3731+ QTest::newRow("list with new ListItem with options") << "ListItemWithOptionsList.qml" << QUrl();
3732+ QTest::newRow("list with new ListItem using default leadingOptions") << "ListItemWithLeadingOptionsList.qml" << QUrl();
3733+ QTest::newRow("list with new ListItem with inline options") << "ListItemWithInlineOptionsList.qml" << QUrl();
3734+ QTest::newRow("list with ListItems.Empty (equivalent to the new ListItem") << "ListItemsEmptyList.qml" << QUrl();
3735+ // disable this test as it takes >20 seconds. Kept still for measurements to be done during development
3736+// QTest::newRow("list with ListItems.Base (one icon, one label and one chevron)") << "ListItemsBaseList.qml" << QUrl();
3737 }
3738
3739 void benchmark_GridOfComponents()
3740
3741=== modified file 'tests/unit/tst_performance/tst_performance.pro'
3742--- tests/unit/tst_performance/tst_performance.pro 2013-06-27 15:20:12 +0000
3743+++ tests/unit/tst_performance/tst_performance.pro 2014-09-15 11:00:03 +0000
3744@@ -19,4 +19,11 @@
3745 TextWithImport.qml \
3746 TextWithImportGrid.qml \
3747 TextWithImportPopupsGrid.qml \
3748- TextWithImportPopups.qml
3749+ TextWithImportPopups.qml \
3750+ ItemList.qml \
3751+ ListItemList.qml \
3752+ ListItemWithOptionsList.qml \
3753+ ListItemWithInlineOptionsList.qml \
3754+ ListItemsEmptyList.qml \
3755+ ListItemsBaseList.qml \
3756+ ListItemWithLeadingOptionsList.qml
3757
3758=== added file 'tests/unit_x11/tst_components/tst_listitem.qml'
3759--- tests/unit_x11/tst_components/tst_listitem.qml 1970-01-01 00:00:00 +0000
3760+++ tests/unit_x11/tst_components/tst_listitem.qml 2014-09-15 11:00:03 +0000
3761@@ -0,0 +1,569 @@
3762+/*
3763+ * Copyright 2014 Canonical Ltd.
3764+ *
3765+ * This program is free software; you can redistribute it and/or modify
3766+ * it under the terms of the GNU Lesser General Public License as published by
3767+ * the Free Software Foundation; version 3.
3768+ *
3769+ * This program is distributed in the hope that it will be useful,
3770+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3771+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3772+ * GNU Lesser General Public License for more details.
3773+ *
3774+ * You should have received a copy of the GNU Lesser General Public License
3775+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3776+ */
3777+
3778+import QtQuick 2.0
3779+import QtTest 1.0
3780+import Ubuntu.Test 1.0
3781+import Ubuntu.Components 1.1
3782+
3783+Item {
3784+ id: main
3785+ width: units.gu(40)
3786+ height: units.gu(71)
3787+
3788+ Action {
3789+ id: stockAction
3790+ iconName: "starred"
3791+ property var param
3792+ onTriggered: param = value
3793+ }
3794+ ListItemOptions {
3795+ id: leading
3796+ Action {
3797+ iconName: "delete"
3798+ property var param
3799+ onTriggered: param = value
3800+ }
3801+ Action {
3802+ iconName: "edit"
3803+ property var param
3804+ onTriggered: param = value
3805+ }
3806+ Action {
3807+ iconName: "camcorder"
3808+ property var param
3809+ onTriggered: param = value
3810+ }
3811+ }
3812+ ListItemOptions {
3813+ id: trailing
3814+ panelColor: leading.panelColor
3815+ iconColor: leading.iconColor
3816+ options: [
3817+ stockAction,
3818+ ]
3819+ delegate: Item {
3820+ objectName: "custom_delegate"
3821+ }
3822+ }
3823+ ListItemOptions {
3824+ id: wrongOption
3825+ Action {
3826+ id: goodAction
3827+ iconName: "starred"
3828+ }
3829+ QtObject {
3830+ id: badAction
3831+ }
3832+ Action {
3833+ iconName: "starred"
3834+ }
3835+ }
3836+ ListItemOptions {
3837+ id: optionsDefault
3838+ property int optionCount: options.length
3839+ }
3840+
3841+ Column {
3842+ width: parent.width
3843+ ListItem {
3844+ id: defaults
3845+ width: parent.width
3846+ }
3847+ ListItem {
3848+ id: testItem
3849+ width: parent.width
3850+ contentItem.color: "blue"
3851+ leadingOptions: leading
3852+ trailingOptions: ListItemOptions {
3853+ options: leading.options
3854+ }
3855+
3856+ Item {
3857+ id: bodyItem
3858+ anchors.fill: parent
3859+ }
3860+ }
3861+ ListView {
3862+ id: listView
3863+ width: parent.width
3864+ height: units.gu(24)
3865+ clip: true
3866+ model: 10
3867+ delegate: ListItem {
3868+ objectName: "listItem" + index
3869+ width: parent.width
3870+ leadingOptions: leading
3871+ trailingOptions: trailing
3872+ }
3873+ }
3874+ }
3875+
3876+ UbuntuTestCase {
3877+ name: "ListItemAPI"
3878+ when: windowShown
3879+
3880+ SignalSpy {
3881+ id: pressedSpy
3882+ signalName: "pressedChanged"
3883+ target: testItem
3884+ }
3885+
3886+ SignalSpy {
3887+ id: clickSpy
3888+ signalName: "clicked"
3889+ target: testItem;
3890+ }
3891+
3892+ SignalSpy {
3893+ id: actionSpy
3894+ signalName: "triggered"
3895+ }
3896+
3897+ SignalSpy {
3898+ id: xChangeSpy
3899+ signalName: "xChanged"
3900+ }
3901+
3902+ function waitReboundCompletion(item) {
3903+ var prevX;
3904+ tryCompareFunction(function() { var b = prevX == item.contentItem.x; prevX = item.contentItem.x; return b; }, true, 1000);
3905+ }
3906+
3907+ function initTestCase() {
3908+ TestExtras.registerTouchDevice();
3909+ waitForRendering(main);
3910+ }
3911+
3912+ function cleanup() {
3913+ testItem.selected = false;
3914+ testItem.selectable = false;
3915+ waitForRendering(testItem, 200);
3916+ pressedSpy.clear();
3917+ clickSpy.clear();
3918+ actionSpy.clear();
3919+ xChangeSpy.clear();
3920+ listView.interactive = true;
3921+ // tap on the first item to make sure we are rebounding all
3922+ mouseClick(defaults, 0, 0);
3923+ // make sure all events are processed
3924+ wait(200);
3925+ }
3926+
3927+ function test_0_defaults() {
3928+ verify(defaults.contentItem !== null, "Defaults is null");
3929+ compare(defaults.contentItem.color, "#000000", "Transparent by default");
3930+ compare(defaults.contentItem.pressedColor, Theme.palette.selected.background, "Theme.palette.selected.background color by default")
3931+ compare(defaults.pressed, false, "Not pressed buy default");
3932+ compare(defaults.divider.visible, true, "divider is visible by default");
3933+ compare(defaults.divider.leftMargin, units.gu(2), "divider's left margin is 2GU");
3934+ compare(defaults.divider.rightMargin, units.gu(2), "divider's right margin is 2GU");
3935+ compare(defaults.divider.colorFrom, "#000000", "colorFrom differs.");
3936+ fuzzyCompare(defaults.divider.colorFrom.a, 0.14, 0.01, "colorFrom alpha differs");
3937+ compare(defaults.divider.colorTo, "#ffffff", "colorTo differs.");
3938+ fuzzyCompare(defaults.divider.colorTo.a, 0.07, 0.01, "colorTo alpha differs");
3939+
3940+ compare(optionsDefault.delegate, null, "ListItemOptions has no delegate set by default.");
3941+ compare(optionsDefault.options.length, 0, "ListItemOptions has no options set.");
3942+ compare(optionsDefault.panelItem, null, "There is no panelItem created by default.");
3943+ compare(optionsDefault.status, ListItemOptions.Disconnected, "optiosn list is disconnected by default");
3944+ compare(optionsDefault.connectedItem, null, "No item is connected by default");
3945+ compare(optionsDefault.panelColor, "#000000", "default panelColor must be black");
3946+ }
3947+
3948+ function test_children_in_content_item() {
3949+ compare(bodyItem.parent, testItem.contentItem, "Content is not in the right holder!");
3950+ }
3951+
3952+ function test_pressedChanged_on_click() {
3953+ mousePress(testItem, testItem.width / 2, testItem.height / 2);
3954+ pressedSpy.wait();
3955+ mouseRelease(testItem, testItem.width / 2, testItem.height / 2);
3956+ }
3957+ function test_pressedChanged_on_tap() {
3958+ TestExtras.touchPress(0, testItem, centerOf(testItem));
3959+ pressedSpy.wait();
3960+ TestExtras.touchRelease(0, testItem, centerOf(testItem));
3961+ // local cleanup, wait few msecs to suppress double tap
3962+ wait(400);
3963+ }
3964+
3965+ function test_clicked_on_mouse() {
3966+ mouseClick(testItem, testItem.width / 2, testItem.height / 2);
3967+ clickSpy.wait();
3968+ }
3969+ function test_clicked_on_tap() {
3970+ TestExtras.touchClick(0, testItem, centerOf(testItem));
3971+ clickSpy.wait();
3972+ }
3973+
3974+ function test_mouse_click_on_listitem() {
3975+ var listItem = findChild(listView, "listItem0");
3976+ verify(listItem, "Cannot find listItem0");
3977+
3978+ mousePress(listItem, listItem.width / 2, 0);
3979+ compare(listItem.pressed, true, "Item is not pressed?");
3980+ // do 5 moves to be able to sense it
3981+ var dy = 0;
3982+ for (var i = 1; i <= 5; i++) {
3983+ dy += i * 10;
3984+ mouseMove(listItem, listItem.width / 2, dy);
3985+ }
3986+ compare(listItem.pressed, false, "Item is pressed still!");
3987+ mouseRelease(listItem, listItem.width / 2, dy);
3988+ }
3989+ function test_touch_click_on_listitem() {
3990+ var listItem = findChild(listView, "listItem0");
3991+ verify(listItem, "Cannot find listItem0");
3992+
3993+ TestExtras.touchPress(0, listItem, Qt.point(listItem.width / 2, 5));
3994+ compare(listItem.pressed, true, "Item is not pressed?");
3995+ // do 5 moves to be able to sense it
3996+ var dy = 0;
3997+ for (var i = 1; i <= 5; i++) {
3998+ dy += i * 10;
3999+ TestExtras.touchMove(0, listItem, Qt.point(listItem.width / 2, dy));
4000+ }
4001+ compare(listItem.pressed, false, "Item is pressed still!");
4002+ // cleanup, wait few milliseconds to avoid dbl-click collision
4003+ TestExtras.touchRelease(0, listItem, Qt.point(listItem.width / 2, dy));
4004+ }
4005+
4006+ function test_background_height_change_on_divider_visible() {
4007+ // make sure the testItem's divider is shown
4008+ testItem.divider.visible = true;
4009+ verify(testItem.contentItem.height < testItem.height, "ListItem's background height must be less than the item itself.");
4010+ testItem.divider.visible = false;
4011+ compare(testItem.contentItem.height, testItem.height, "ListItem's background height must be the same as the item itself.");
4012+ testItem.divider.visible = true;
4013+ }
4014+
4015+ // ListItemOptions tests
4016+ function test_valid_options_data() {
4017+ return [
4018+ {tag: "Inline Actions", object: leading, expected: 3, xfail: false},
4019+ {tag: "Stock Actions", object: trailing, expected: 1, xfail: false},
4020+ {tag: "Wrong Actions", object: wrongOption, expected: 3, xfail: true},
4021+ {tag: "Wrong Actions", object: wrongOption, expected: 0, xfail: false},
4022+ ];
4023+ }
4024+ function test_valid_options(data) {
4025+ if (data.xfail) {
4026+ expectFailContinue(data.tag, "expected to fail");
4027+ }
4028+ compare(data.object.options.length, data.expected, data.tag + ": expected options differ.");
4029+ }
4030+
4031+ function test_touch_tug_options_data() {
4032+ var item = findChild(listView, "listItem0");
4033+ return [
4034+ {tag: "Trailing, mouse", item: item, pos: centerOf(item), dx: -units.gu(20), positiveDirection: false, mouse: true},
4035+ {tag: "Leading, mouse", item: item, pos: centerOf(item), dx: units.gu(20), positiveDirection: true, mouse: true},
4036+ {tag: "Trailing, touch", item: item, pos: centerOf(item), dx: -units.gu(20), positiveDirection: false, mouse: false},
4037+ {tag: "Leading, touch", item: item, pos: centerOf(item), dx: units.gu(20), positiveDirection: true, mouse: false},
4038+ ];
4039+ }
4040+ function test_touch_tug_options(data) {
4041+ listView.positionViewAtBeginning();
4042+ if (data.mouse) {
4043+ flick(data.item, data.pos.x, data.pos.y, data.dx, 0);
4044+ } else {
4045+ TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0));
4046+ }
4047+ waitForRendering(data.item, 400);
4048+ if (data.positiveDirection) {
4049+ verify(data.item.contentItem.x > 0, data.tag + " options did not show up");
4050+ } else {
4051+ verify(data.item.contentItem.x < 0, data.tag + " options did not show up");
4052+ }
4053+
4054+ // dismiss
4055+ if (data.mouse) {
4056+ mouseClick(main, 1, 1);
4057+ } else {
4058+ TestExtras.touchClick(0, main, Qt.point(1, 1));
4059+ }
4060+ waitForRendering(data.item, 400);
4061+ }
4062+
4063+ function test_rebound_when_pressed_outside_or_clicked_data() {
4064+ var item0 = findChild(listView, "listItem0");
4065+ var item1 = findChild(listView, "listItem1");
4066+ return [
4067+ {tag: "Click on an other Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: true},
4068+ {tag: "Click on the same Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item0.contentItem, mouse: true},
4069+ {tag: "Tap on an other Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: false},
4070+ {tag: "Tap on the same Item", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item0.contentItem, mouse: false},
4071+ ];
4072+ }
4073+ function test_rebound_when_pressed_outside_or_clicked(data) {
4074+ listView.positionViewAtBeginning();
4075+ if (data.mouse) {
4076+ flick(data.item, data.pos.x, data.pos.y, data.dx, 0);
4077+ } else {
4078+ TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0));
4079+ }
4080+ waitForRendering(data.item, 400);
4081+ verify(data.item.contentItem.x != 0, "The component wasn't tugged!");
4082+ // dismiss
4083+ if (data.mouse) {
4084+ mouseClick(data.clickOn, centerOf(data.clickOn).x, centerOf(data.clickOn).y);
4085+ } else {
4086+ TestExtras.touchClick(0, data.clickOn, centerOf(data.clickOn));
4087+ }
4088+ waitForRendering(data.item, 400);
4089+ tryCompareFunction(function(){ return data.item.contentItem.x; }, 0, 1000);
4090+ }
4091+
4092+ function test_listview_not_interactive_while_tugged_data() {
4093+ var item0 = findChild(listView, "listItem0");
4094+ var item1 = findChild(listView, "listItem1");
4095+ return [
4096+ {tag: "Trailing", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: true},
4097+ {tag: "Leading", item: item0, pos: centerOf(item0), dx: units.gu(20), clickOn: item0.contentItem, mouse: true},
4098+ {tag: "Trailing", item: item0, pos: centerOf(item0), dx: -units.gu(20), clickOn: item1, mouse: false},
4099+ {tag: "Leading", item: item0, pos: centerOf(item0), dx: units.gu(20), clickOn: item0.contentItem, mouse: false},
4100+ ];
4101+ }
4102+ function test_listview_not_interactive_while_tugged(data) {
4103+ listView.positionViewAtBeginning();
4104+ if (data.mouse) {
4105+ flick(data.item, data.pos.x, data.pos.y, data.dx, 0);
4106+ } else {
4107+ TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0));
4108+ }
4109+ waitForRendering(data.item, 800);
4110+ compare(listView.interactive, false, "The ListView is still interactive!");
4111+ // dismiss
4112+ if (data.mouse) {
4113+ mouseClick(data.clickOn, centerOf(data.clickOn).x, centerOf(data.clickOn).y);
4114+ } else {
4115+ TestExtras.touchClick(0, data.clickOn, centerOf(data.clickOn));
4116+ }
4117+ waitForRendering(data.item, 400);
4118+ tryCompareFunction(function(){ return listView.interactive; }, true, 1000);
4119+ }
4120+
4121+ function test_selecting_option_rebounds_data() {
4122+ var item0 = findChild(listView, "listItem0");
4123+ return [
4124+ {tag: "With mouse", item: item0, pos: centerOf(item0), dx: units.gu(20), options: item0.leadingOptions, select: "list_option_0", mouse: true},
4125+ {tag: "With touch", item: item0, pos: centerOf(item0), dx: units.gu(20), options: item0.leadingOptions, select: "list_option_0", mouse: false},
4126+ ]
4127+ }
4128+ function test_selecting_option_rebounds(data) {
4129+ listView.positionViewAtBeginning();
4130+ if (data.mouse) {
4131+ flick(data.item, data.pos.x, data.pos.y, data.dx, 0);
4132+ } else {
4133+ TestExtras.touchDrag(0, data.item, data.pos, Qt.point(data.dx, 0));
4134+ }
4135+ waitForRendering(data.item, 800);
4136+ var selectedOption = findChild(data.options.panelItem, data.select);
4137+ verify(selectedOption, "Cannot select option " + data.select);
4138+ // dismiss
4139+ if (data.mouse) {
4140+ mouseClick(selectedOption, centerOf(selectedOption).x, centerOf(selectedOption).y);
4141+ } else {
4142+ TestExtras.touchClick(0, selectedOption, centerOf(selectedOption));
4143+ }
4144+ waitForRendering(data.item, 400);
4145+ tryCompareFunction(function(){ return data.item.contentItem.x; }, 0, 1000);
4146+ }
4147+
4148+ function test_custom_trailing_delegate() {
4149+ listView.positionViewAtBeginning();
4150+ var item = findChild(listView, "listItem0");
4151+ flick(item, centerOf(item).x, centerOf(item).y, -units.gu(20), 0);
4152+ verify(trailing.panelItem, "Panel is not visible");
4153+ var custom = findChild(trailing.panelItem, "custom_delegate");
4154+ verify(custom, "Custom delegate not in use");
4155+ // cleanup
4156+ mouseClick(main, 0, 0);
4157+ }
4158+
4159+ // execute as last so we make sure we have the panel created
4160+ function test_snap_data() {
4161+ verify(testItem.leadingOptions.panelItem, "Panel had not been created!");
4162+ var option = findChild(testItem.leadingOptions.panelItem, "list_option_0");
4163+ verify(option, "Options not accessible");
4164+ var optionSize = option.width;
4165+ return [
4166+ {tag: "Snap back leading, mouse", item: testItem.contentItem, dx: optionSize / 2 - 10, list: testItem.leadingOptions, snap: false, mouse: true},
4167+ {tag: "Snap back leading, touch", item: testItem.contentItem, dx: optionSize / 2 - 10, list: testItem.leadingOptions, snap: false, mouse: false},
4168+ {tag: "Snap in leading, mouse", item: testItem.contentItem, dx: optionSize / 2 + 10, list: testItem.leadingOptions, snap: true, mouse: true},
4169+ {tag: "Snap in leading, touch", item: testItem.contentItem, dx: optionSize / 2 + 10, list: testItem.leadingOptions, snap: true, mouse: false},
4170+
4171+ {tag: "Snap back trailing, mouse", item: testItem.contentItem, dx: -(optionSize / 2 - 10), list: testItem.trailingOptions, snap: false, mouse: true},
4172+ {tag: "Snap back trailing, touch", item: testItem.contentItem, dx: -(optionSize / 2 - 10), list: testItem.trailingOptions, snap: false, mouse: false},
4173+ {tag: "Snap in trailing, mouse", item: testItem.contentItem, dx: -(optionSize / 2 + 10), list: testItem.trailingOptions, snap: true, mouse: true},
4174+ {tag: "Snap in trailing, touch", item: testItem.contentItem, dx: -(optionSize / 2 + 10), list: testItem.trailingOptions, snap: true, mouse: false},
4175+ ];
4176+ }
4177+ function test_snap(data) {
4178+ if (data.mouse) {
4179+ flick(data.item, centerOf(data.item).x, centerOf(data.item).y, data.dx, 0);
4180+ } else {
4181+ TestExtras.touchDrag(0, data.item, centerOf(data.item), Qt.point(data.dx, 0));
4182+ }
4183+
4184+ waitForRendering(data.item, 800);
4185+ if (data.snap) {
4186+ verify(data.item.x != 0, "Not snapped to be visible");
4187+ } else {
4188+ verify(data.item.x == 0, "Not snapped back");
4189+ }
4190+
4191+ // cleanup
4192+ if (data.mouse) {
4193+ mouseClick(data.item, centerOf(data.item).x, centerOf(data.item).y);
4194+ } else {
4195+ TestExtras.touchClick(0, data.item, centerOf(data.item));
4196+ }
4197+ waitForRendering(data.item, 800);
4198+ }
4199+
4200+ function test_verify_action_value_data() {
4201+ return [
4202+ {tag: "Undefined", item: testItem, result: undefined},
4203+ {tag: "Index 0", item: findChild(listView, "listItem0"), result: 0},
4204+ {tag: "Index 1", item: findChild(listView, "listItem1"), result: 1},
4205+ {tag: "Index 2", item: findChild(listView, "listItem2"), result: 2},
4206+ {tag: "Index 3", item: findChild(listView, "listItem3"), result: 3},
4207+ ];
4208+ }
4209+ function test_verify_action_value(data) {
4210+ var option = findChild(data.item.leadingOptions.panelItem, "list_option_0");
4211+ verify(option, "Options panel cannot be reached");
4212+ // we test the last action, as we tug the first action on leading, which means teh alst will be accessible
4213+ var len = data.item.leadingOptions.options.length;
4214+ var action = data.item.leadingOptions.options[len - 1];
4215+ actionSpy.target = action;
4216+ actionSpy.clear();
4217+ // tug options in
4218+ flick(data.item.contentItem, centerOf(data.item.contentItem).x, centerOf(data.item.contentItem).y, option.width, 0);
4219+ waitForRendering(data.item.contentItem, 800);
4220+
4221+ // select the option
4222+ mouseClick(data.item, centerOf(option).x, centerOf(option).y);
4223+ waitForRendering(data.item.contentItem, 800);
4224+
4225+ // check the action param
4226+ actionSpy.wait();
4227+ compare(action.param, data.result, "Action parameter differs");
4228+ }
4229+
4230+ SignalSpy {
4231+ id: panelItemSpy
4232+ signalName: "onXChanged"
4233+ }
4234+
4235+ function test_disabled_item_locked_data() {
4236+ var item0 = findChild(listView, "listItem0");
4237+ return [
4238+ // drag same amount as height is
4239+ {tag: "Simple item, leading", item: testItem, enabled: false, dx: testItem.height},
4240+ {tag: "Simple item, trailing", item: testItem, enabled: false, dx: -testItem.height},
4241+ {tag: "ListView item, leading", item: item0, enabled: false, dx: item0.height},
4242+ {tag: "ListView item, trailing", item: item0, enabled: false, dx: -item0.height},
4243+ ];
4244+ }
4245+ function test_disabled_item_locked(data) {
4246+ var oldEnabled = data.item.enabled;
4247+ panelItemSpy.clear();
4248+ panelItemSpy.target = data.item;
4249+ data.item.enabled = false;
4250+ // tug
4251+ flick(data.item.contentItem, centerOf(data.item.contentItem).x, centerOf(data.item.contentItem).y, data.dx, 0);
4252+ compare(panelItemSpy.count, 0, "Item had been tugged despite being disabled!");
4253+ // check opacity
4254+ fuzzyCompare(data.item.opacity, 0.5, 0.1, "Disabled item must be 50% transparent");
4255+ //cleanup
4256+ data.item.enabled = oldEnabled;
4257+ }
4258+
4259+ function test_toggle_selectable_data() {
4260+ return [
4261+ {tag: "When not selected", selected: false},
4262+ {tag: "When selected", selected: true},
4263+ ]
4264+ }
4265+ function test_toggle_selectable(data) {
4266+ xChangeSpy.target = testItem.contentItem;
4267+ testItem.selectable = true;
4268+ waitForRendering(testItem.contentItem, 800);
4269+ testItem.selected = data.selected;
4270+ xChangeSpy.wait();
4271+ }
4272+
4273+ function test_no_tug_when_selectable() {
4274+ xChangeSpy.target = null;
4275+ testItem.selectable = true;
4276+ // wait till animation to selection mode ends
4277+ waitReboundCompletion(testItem);
4278+
4279+ // try to tug leading
4280+ xChangeSpy.target = testItem.contentItem;
4281+ xChangeSpy.clear();
4282+ compare(xChangeSpy.count, 0, "Wrong signal count!");
4283+ flick(testItem.contentItem, centerOf(testItem.contentItem).x, centerOf(testItem.contentItem).y, testItem.contentItem.width / 2, 0);
4284+ compare(xChangeSpy.count, 0, "No tug allowed when in selection mode");
4285+ }
4286+
4287+ SignalSpy {
4288+ id: pressAndHoldSpy
4289+ signalName: "pressAndHold"
4290+ }
4291+ function test_pressandhold_suppress_click() {
4292+ var center = centerOf(testItem);
4293+ pressAndHoldSpy.target = testItem;
4294+ clickSpy.target = testItem;
4295+ clickSpy.clear();
4296+ mouseLongPress(testItem, center.x, center.y);
4297+ mouseRelease(testItem, center.x, center.y);
4298+ pressAndHoldSpy.wait();
4299+ compare(clickSpy.count, 0, "Click must be suppressed when long pressed");
4300+ }
4301+
4302+ function test_listitemoptions_status_data() {
4303+ var drag = testItem.contentItem.width / 2;
4304+ return [
4305+ {tag:"Leading", item: testItem, dx: drag, list: testItem.leadingOptions, expectedStatus: ListItemOptions.LeadingOptions},
4306+ {tag:"Trailing", item: testItem, dx: -drag, list: testItem.trailingOptions, expectedStatus: ListItemOptions.TrailingOptions},
4307+ ];
4308+ }
4309+ function test_listitemoptions_status(data) {
4310+ var testItem = data.item.contentItem;
4311+ flick(testItem, centerOf(testItem).x, centerOf(testItem).y, data.dx, 0);
4312+ waitForRendering(testItem, 800);
4313+ compare(data.list.status, data.expectedStatus, "Status on the option list differs.");
4314+ compare(data.list.connectedItem, data.item, "connectedItem is not the tugged item.");
4315+ }
4316+
4317+ function test_panelColor_change() {
4318+ // change panel color for the leading and observe the trailing panelItem color change
4319+ leading.panelColor = UbuntuColors.blue;
4320+ compare(leading.panelItem.panelColor, UbuntuColors.blue, "leading panelItem color differs");
4321+ compare(trailing.panelItem.panelColor, UbuntuColors.blue, "trailing panelItem color has not been set");
4322+ }
4323+ function test_iconColor_change() {
4324+ // change panel color for the leading and observe the trailing panelItem color change
4325+ leading.iconColor = UbuntuColors.green;
4326+ compare(leading.panelItem.iconColor, UbuntuColors.green, "leading panelItem color differs");
4327+ compare(trailing.panelItem.iconColor, UbuntuColors.green, "trailing panelItem color has not been set");
4328+ }
4329+ }
4330+}
4331
4332=== added file 'tests/unit_x11/tst_components/tst_listitem_ubuntulistview.qml'
4333--- tests/unit_x11/tst_components/tst_listitem_ubuntulistview.qml 1970-01-01 00:00:00 +0000
4334+++ tests/unit_x11/tst_components/tst_listitem_ubuntulistview.qml 2014-09-15 11:00:03 +0000
4335@@ -0,0 +1,159 @@
4336+/*
4337+ * Copyright 2014 Canonical Ltd.
4338+ *
4339+ * This program is free software; you can redistribute it and/or modify
4340+ * it under the terms of the GNU Lesser General Public License as published by
4341+ * the Free Software Foundation; version 3.
4342+ *
4343+ * This program is distributed in the hope that it will be useful,
4344+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4345+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4346+ * GNU Lesser General Public License for more details.
4347+ *
4348+ * You should have received a copy of the GNU Lesser General Public License
4349+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4350+ */
4351+
4352+import QtQuick 2.0
4353+import QtTest 1.0
4354+import Ubuntu.Test 1.0
4355+import Ubuntu.Components 1.1
4356+
4357+Item {
4358+ id: main
4359+ width: units.gu(40)
4360+ height: units.gu(71)
4361+
4362+ Column {
4363+ anchors.fill: parent
4364+ UbuntuListView {
4365+ id: listView
4366+ clip: true
4367+ width: parent.width
4368+ height: parent.height / 3
4369+ delegate: ListItem {
4370+ objectName: "listItem" + index
4371+ }
4372+ }
4373+ // override default leading
4374+ UbuntuListView {
4375+ id: customListView1
4376+ clip: true
4377+ width: parent.width
4378+ height: parent.height / 3
4379+ model: numberModel
4380+ leadingOptions: ListItemOptions {
4381+ Action {
4382+ iconName: "edit"
4383+ }
4384+ }
4385+ delegate: ListItem {
4386+ objectName: "listItem" + index
4387+ }
4388+ }
4389+ // inline option list takes precedence (overrides UbuntuListView's default)
4390+ UbuntuListView {
4391+ id: customListView2
4392+ clip: true
4393+ width: parent.width
4394+ height: parent.height / 3
4395+ model: numberModel
4396+ delegate: ListItem {
4397+ objectName: "listItem" + index
4398+ leadingOptions: ListItemOptions {
4399+ Action {
4400+ iconName: "edit"
4401+ }
4402+ }
4403+ }
4404+ }
4405+ }
4406+
4407+ property int numberModel: 10
4408+ property var arrayModel: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
4409+ property ListModel objectModel: ListModel {
4410+ function fill() {
4411+ clear();
4412+ for (var i = 0; i < 10; i++) {
4413+ append({data: "ListElement #" + i})
4414+ }
4415+ }
4416+ }
4417+
4418+ UbuntuTestCase {
4419+ name: "ListItem with UbuntuListView"
4420+ when: windowShown
4421+
4422+ SignalSpy {
4423+ id: actionSpy
4424+ signalName: "triggered";
4425+ }
4426+
4427+ function init() {
4428+ numberModel = 10;
4429+ arrayModel = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
4430+ objectModel.fill();
4431+ }
4432+ function cleanup() {
4433+ listView.model = undefined;
4434+ actionSpy.target = null;
4435+ actionSpy.clear();
4436+ }
4437+
4438+ function test_leading_option_is_delete_alone() {
4439+ // set a model to the listView
4440+ listView.model = numberModel;
4441+ waitForRendering(listView, 800);
4442+ var listItem = findChild(listView, "listItem0");
4443+ verify(listItem, "Cannot get list item at index 0");
4444+ // tug the leadingOptions in
4445+ flick(listItem.contentItem, centerOf(listItem.contentItem).x, centerOf(listItem.contentItem).y, listItem.contentItem.width / 2, 0);
4446+ // leading options panel must be created
4447+ verify(listItem.leadingOptions.panelItem, "Panel of LeadingOptions cannot be reached!");
4448+ compare(listItem.leadingOptions.options.length, 1, "More options in the default leadingOptions than it should be!");
4449+ compare(listItem.leadingOptions.options[0].iconName, "delete", "The default leading option is not 'delete'");
4450+ }
4451+
4452+ function test_delete_item_data() {
4453+ return [
4454+ {tag: "Number model", model: numberModel, index: 3, resultModelSize: 9 },
4455+ {tag: "Array model", model: arrayModel, index: 4, resultModelSize: 9 },
4456+ {tag: "Object model", model: objectModel, index: 2, resultModelSize: 9 },
4457+ ];
4458+ }
4459+ function test_delete_item(data) {
4460+ listView.model = data.model;
4461+ waitForRendering(listView, 800);
4462+ // navigate to teh list item
4463+ listView.positionViewAtIndex(data.index, ListView.Contain);
4464+ // perform delete by tugging the leading panel in
4465+ var listItem = findChild(listView, "listItem" + data.index);
4466+ verify(listItem, "Cannot get list item at index " + data.index);
4467+ flick(listItem.contentItem, centerOf(listItem.contentItem).x, centerOf(listItem.contentItem).y, listItem.contentItem.width / 2, 0);
4468+
4469+ // watch action triggers
4470+ actionSpy.target = listItem.leadingOptions.options[0];
4471+ // select the option on the leadingOptions
4472+ var option = findChild(listItem.leadingOptions.panelItem, "list_option_0");
4473+ verify(option, "Option cannot be reached");
4474+ mouseClick(listItem, centerOf(option).x, centerOf(option).y);
4475+ actionSpy.wait();
4476+
4477+ // check model size by comparing the listview count
4478+ compare(listView.count, data.resultModelSize, "Item had not been deleted!");
4479+ }
4480+
4481+ function test_default_optionlist_override_data() {
4482+ return [
4483+ {tag: "Default leadingOption overridden", view: customListView1},
4484+ {tag: "Inline leadingOption has precedence over default", view: customListView2},
4485+ ];
4486+ }
4487+ function test_default_optionlist_override(data) {
4488+ var listItem = findChild(data.view, "listItem0");
4489+ verify(listItem, "Cannot get list item at index 0");
4490+ compare(listItem.leadingOptions.options.length, 1, "More than 1 option in the leadingOptions list");
4491+ compare(listItem.leadingOptions.options[0].iconName, "edit", "Default UbuntuListItem option not overridden!");
4492+ }
4493+ }
4494+}
4495
4496=== modified file 'tests/unit_x11/tst_components/tst_ubuntulistview11.qml'
4497--- tests/unit_x11/tst_components/tst_ubuntulistview11.qml 2014-06-26 15:59:10 +0000
4498+++ tests/unit_x11/tst_components/tst_ubuntulistview11.qml 2014-09-15 11:00:03 +0000
4499@@ -91,6 +91,7 @@
4500 function test_0_defaults() {
4501 verify(ubuntuListView.hasOwnProperty("pullToRefresh"), "PullToRefresh is missing");
4502 compare(ubuntuListView.pullToRefresh.enabled, false, "PullToRefresh functionality is disabled by default");
4503+ verify(ubuntuListView.leadingOptions, "LeadingOptions is not defined!");
4504 }
4505
4506 function test_pullToRefresh_manual_refresh() {

Subscribers

People subscribed via source and target branches

to status/vote changes: