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