Merge lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode into lp:ubuntu-ui-toolkit/staging
- 82-dragging-mode
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Cris Dywan |
Approved revision: | 1454 |
Merged at revision: | 1423 |
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode |
Merge into: | lp:ubuntu-ui-toolkit/staging |
Diff against target: |
2582 lines (+1873/-91) 22 files modified
components.api (+19/-0) debian/control (+1/-0) examples/ubuntu-ui-toolkit-gallery/NewListItems.qml (+92/-30) modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml (+98/-13) modules/Ubuntu/Components/UbuntuListView.qml (+7/-0) modules/Ubuntu/Components/plugin/plugin.cpp (+1/-0) modules/Ubuntu/Components/plugin/plugin.pro (+6/-2) modules/Ubuntu/Components/plugin/privates/listitemdragarea.cpp (+313/-0) modules/Ubuntu/Components/plugin/privates/listitemdragarea.h (+55/-0) modules/Ubuntu/Components/plugin/privates/listitemdraghandler.cpp (+86/-0) modules/Ubuntu/Components/plugin/privates/listitemdraghandler.h (+47/-0) modules/Ubuntu/Components/plugin/uclistitem.cpp (+232/-18) modules/Ubuntu/Components/plugin/uclistitem.h (+58/-1) modules/Ubuntu/Components/plugin/uclistitem_p.h (+17/-4) modules/Ubuntu/Components/plugin/uclistitemstyle.cpp (+15/-0) modules/Ubuntu/Components/plugin/uclistitemstyle.h (+10/-2) modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp (+339/-11) modules/Ubuntu/Test/UbuntuTestCase.qml (+7/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml (+0/-3) tests/resources/listitems/ListItemDragging.qml (+174/-0) tests/resources/listitems/ListItemTest.qml (+0/-1) tests/unit_x11/tst_components/tst_listitem.qml (+296/-6) |
To merge this branch: | bzr merge lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode |
Related bugs: | |
Related blueprints: |
SDK: Design a new ListItem and layouts
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Cris Dywan | Approve | ||
Review via email: mp+251212@code.launchpad.net |
This proposal supersedes a proposal from 2015-01-12.
Commit message
Implement ListItem dragging support in ListView
Description of the change
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1436
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) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1438
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) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1443
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1446
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1449
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1451
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
Unfortunately the "not all features of dragging will be possible on this model." message isn't working well as it is now, because it's basically impossible to "fix" the code.
What we could have, though, is a message saying something like "Dragging is only supported when using a QAbstractItemModel, ListModel or list".
Other than that it's looking pretty good, I haven't spotted any more bugs.
Note: I see some weird bits in the diff that are changes from staging, not this branch, let's be careful not to merge anything unwanted here.
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
> Unfortunately the "not all features of dragging will be possible on this
> model." message isn't working well as it is now, because it's basically
> impossible to "fix" the code.
>
> What we could have, though, is a message saying something like "Dragging is
> only supported when using a QAbstractItemModel, ListModel or list".
I'll add a fix for that.
>
> Other than that it's looking pretty good, I haven't spotted any more bugs.
>
> Note: I see some weird bits in the diff that are changes from staging, not
> this branch, let's be careful not to merge anything unwanted here.
Yes, you asked me to revert the staging which had a regression due to the Page vs. Loader fix, I'll merge the staging again so we do have a clean sync.
Cris Dywan (kalikiana) wrote : | # |
Thanks for addressing the comments!
Looking good now!
PS Jenkins bot (ps-jenkins) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1454
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://
Preview Diff
1 | === modified file 'components.api' |
2 | --- components.api 2015-02-19 10:10:53 +0000 |
3 | +++ components.api 2015-02-27 07:56:03 +0000 |
4 | @@ -686,6 +686,7 @@ |
5 | function mouseLongPress(item, x, y, button, modifiers, delay) |
6 | function tryCompareFunction(func, expectedResult, timeout) |
7 | function typeString(string) |
8 | + function warningFormat(line, column, message) |
9 | plugins.qmltypes |
10 | name: "FilterBehavior" |
11 | prototype: "QObject" |
12 | @@ -921,6 +922,16 @@ |
13 | name: "quitWithError" |
14 | Parameter { name: "errorMessage"; type: "string" } |
15 | Method { name: "quitWithError" } |
16 | + name: "UCDragEvent" |
17 | + prototype: "QObject" |
18 | + exports: ["ListItemDrag 1.2"] |
19 | + name: "Status" |
20 | + Property { name: "status"; type: "Status"; isReadonly: true } |
21 | + Property { name: "from"; type: "int"; isReadonly: true } |
22 | + Property { name: "to"; type: "int"; isReadonly: true } |
23 | + Property { name: "minimumIndex"; type: "int" } |
24 | + Property { name: "maximumIndex"; type: "int" } |
25 | + Property { name: "accept"; type: "bool" } |
26 | name: "UCInverseMouse" |
27 | prototype: "UCMouse" |
28 | exports: ["InverseMouse 0.1", "InverseMouse 1.0"] |
29 | @@ -935,6 +946,8 @@ |
30 | Property { name: "contentMoving"; type: "bool"; isReadonly: true } |
31 | Property { name: "color"; type: "QColor" } |
32 | Property { name: "highlightColor"; type: "QColor" } |
33 | + Property { name: "dragging"; type: "bool"; isReadonly: true } |
34 | + Property { name: "dragMode"; type: "bool" } |
35 | Property { name: "selected"; type: "bool" } |
36 | Property { name: "selectMode"; type: "bool" } |
37 | Property { name: "action"; type: "UCAction"; isPointer: true } |
38 | @@ -960,7 +973,9 @@ |
39 | prototype: "QQuickItem" |
40 | exports: ["Ubuntu.Components.Styles/ListItemStyle 1.2"] |
41 | Property { name: "snapAnimation"; type: "QQuickAbstractAnimation"; isPointer: true } |
42 | + Property { name: "dropAnimation"; type: "QQuickPropertyAnimation"; isPointer: true } |
43 | Property { name: "animatePanels"; type: "bool"; isReadonly: true } |
44 | + Property { name: "dragPanel"; type: "QQuickItem"; isPointer: true } |
45 | Method { |
46 | name: "swipeEvent" |
47 | Parameter { name: "event"; type: "UCSwipeEvent"; isPointer: true } |
48 | @@ -1079,6 +1094,10 @@ |
49 | exports: ["ViewItems 1.2"] |
50 | Property { name: "selectMode"; type: "bool" } |
51 | Property { name: "selectedIndices"; type: "QList<int>" } |
52 | + Property { name: "dragMode"; type: "bool" } |
53 | + Signal { |
54 | + name: "dragUpdated" |
55 | + Parameter { name: "event"; type: "UCDragEvent"; isPointer: true } |
56 | name: "UbuntuI18n" |
57 | prototype: "QObject" |
58 | exports: ["i18n 0.1", "i18n 1.0"] |
59 | |
60 | === modified file 'debian/control' |
61 | --- debian/control 2015-02-10 16:47:59 +0000 |
62 | +++ debian/control 2015-02-27 07:56:03 +0000 |
63 | @@ -24,6 +24,7 @@ |
64 | qtdeclarative5-unity-action-plugin (>= 1.1.0), |
65 | qml-module-qtquick-localstorage | qtdeclarative5-localstorage-plugin, |
66 | qml-module-qt-labs-settings | qtdeclarative5-settings-plugin, |
67 | + qml-module-qtqml-models2, |
68 | qtdeclarative5-doc-html, |
69 | qtwebkit5-doc-html, |
70 | qtsvg5-doc-html, |
71 | |
72 | === modified file 'examples/ubuntu-ui-toolkit-gallery/NewListItems.qml' |
73 | --- examples/ubuntu-ui-toolkit-gallery/NewListItems.qml 2015-02-18 11:16:32 +0000 |
74 | +++ examples/ubuntu-ui-toolkit-gallery/NewListItems.qml 2015-02-27 07:56:03 +0000 |
75 | @@ -34,36 +34,6 @@ |
76 | // clip the action delegates while swiping left/right |
77 | clip: true |
78 | |
79 | - ListView { |
80 | - height: units.gu(20) |
81 | - width: parent.width |
82 | - |
83 | - model: [ i18n.tr("Basic"), i18n.tr("Colored divider"), i18n.tr("No divider") ] |
84 | - delegate: ListItemWithLabel { |
85 | - text: modelData |
86 | - divider { |
87 | - colorFrom: modelData == i18n.tr("Colored divider") ? UbuntuColors.red : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
88 | - colorTo: modelData == i18n.tr("Colored divider") ? UbuntuColors.green : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
89 | - visible: modelData != i18n.tr("No divider") |
90 | - } |
91 | - } |
92 | - } |
93 | - } |
94 | - |
95 | - TemplateSection { |
96 | - className: "ListItem" |
97 | - // no spacing between the list items in the Column |
98 | - spacing: 0 |
99 | - Item { |
100 | - // compensate for the spacing of 0 by adding this |
101 | - // Item inbetween the title and the list items. |
102 | - height: units.gu(3) |
103 | - width: parent.width |
104 | - } |
105 | - |
106 | - // clip the action delegates while swiping left/right |
107 | - clip: true |
108 | - |
109 | ListItemWithLabel { |
110 | color: UbuntuColors.blue |
111 | text: i18n.tr("Colored") |
112 | @@ -155,4 +125,96 @@ |
113 | } |
114 | } |
115 | } |
116 | + |
117 | + TemplateSection { |
118 | + className: "ListItem" |
119 | + title: "Select mode" |
120 | + |
121 | + ListView { |
122 | + height: units.gu(20) |
123 | + width: parent.width |
124 | + clip: true |
125 | + |
126 | + ViewItems.dragMode: ViewItems.selectMode |
127 | + ViewItems.onDragUpdated: { |
128 | + if (event.status == ListItemDrag.Started) { |
129 | + if (model[event.from] == "Immutable") |
130 | + event.accept = false; |
131 | + return; |
132 | + } |
133 | + if (model[event.to] == "Immutable") { |
134 | + event.accept = false; |
135 | + return; |
136 | + } |
137 | + // No instantaneous updates |
138 | + if (event.status == ListItemDrag.Moving) { |
139 | + event.accept = false; |
140 | + return; |
141 | + } |
142 | + if (event.status == ListItemDrag.Dropped) { |
143 | + var fromItem = model[event.from]; |
144 | + var list = model; |
145 | + list.splice(event.from, 1); |
146 | + list.splice(event.to, 0, fromItem); |
147 | + model = list; |
148 | + } |
149 | + } |
150 | + |
151 | + model: [ i18n.tr("Basic"), i18n.tr("Colored divider"), i18n.tr("Immutable"), i18n.tr("No divider") ] |
152 | + delegate: ListItemWithLabel { |
153 | + text: modelData |
154 | + color: dragging ? "lightblue" : "transparent" |
155 | + divider { |
156 | + colorFrom: modelData == i18n.tr("Colored divider") ? UbuntuColors.red : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
157 | + colorTo: modelData == i18n.tr("Colored divider") ? UbuntuColors.green : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
158 | + visible: modelData != i18n.tr("No divider") |
159 | + } |
160 | + } |
161 | + } |
162 | + } |
163 | + |
164 | + TemplateSection { |
165 | + className: "ListItem" |
166 | + title: "Drag mode" |
167 | + |
168 | + UbuntuListView { |
169 | + height: units.gu(20) |
170 | + width: parent.width |
171 | + clip: true |
172 | + ViewItems.dragMode: true |
173 | + ViewItems.onDragUpdated: { |
174 | + if (event.status == ListItemDrag.Started) { |
175 | + if (model.get(event.from).label == "Immutable") |
176 | + event.accept = false; |
177 | + return; |
178 | + } |
179 | + if (model.get(event.to).label == "Immutable") { |
180 | + event.accept = false; |
181 | + return; |
182 | + } |
183 | + // Live update as you drag |
184 | + if (event.status == ListItemDrag.Moving) { |
185 | + model.move(event.from, event.to, 1); |
186 | + } |
187 | + } |
188 | + |
189 | + |
190 | + model: ListModel { |
191 | + ListElement { label: "Basic" } |
192 | + ListElement { label: "Colored divider" } |
193 | + ListElement { label: "Immutable" } |
194 | + ListElement { label: "No divider" } |
195 | + } |
196 | + |
197 | + delegate: ListItemWithLabel { |
198 | + text: modelData |
199 | + color: dragMode ? "lightblue" : "lightgray" |
200 | + divider { |
201 | + colorFrom: modelData == i18n.tr("Colored divider") ? UbuntuColors.red : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
202 | + colorTo: modelData == i18n.tr("Colored divider") ? UbuntuColors.green : Qt.rgba(0.0, 0.0, 0.0, 0.0) |
203 | + visible: modelData != i18n.tr("No divider") |
204 | + } |
205 | + } |
206 | + } |
207 | + } |
208 | } |
209 | |
210 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml' |
211 | --- modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml 2015-02-18 10:57:06 +0000 |
212 | +++ modules/Ubuntu/Components/Themes/Ambiance/ListItemStyle.qml 2015-02-27 07:56:03 +0000 |
213 | @@ -162,7 +162,56 @@ |
214 | } |
215 | } |
216 | } |
217 | + // drag panel |
218 | + Component { |
219 | + id: dragDelegate |
220 | + Item { |
221 | + id: dragPanel |
222 | + objectName: "drag_panel" + index |
223 | + anchors.fill: parent ? parent : undefined |
224 | + Icon { |
225 | + objectName: "icon" |
226 | + id: dragIcon |
227 | + anchors.centerIn: parent |
228 | + width: units.gu(3) |
229 | + height: width |
230 | + name: "view-grid-symbolic" |
231 | + opacity: 0.0 |
232 | + scale: 0.5 |
233 | + } |
234 | + Binding { |
235 | + target: listItemStyle |
236 | + property: "dragPanel" |
237 | + value: dragPanel |
238 | + } |
239 | |
240 | + states: State { |
241 | + name: "enabled" |
242 | + when: loaded && styledItem.dragMode |
243 | + PropertyChanges { |
244 | + target: dragIcon |
245 | + opacity: 1.0 |
246 | + scale: 1.0 |
247 | + } |
248 | + } |
249 | + transitions: Transition { |
250 | + from: "" |
251 | + to: "*" |
252 | + reversible: true |
253 | + enabled: listItemStyle.animatePanels |
254 | + ParallelAnimation { |
255 | + OpacityAnimator { |
256 | + easing: UbuntuAnimation.StandardEasing |
257 | + duration: UbuntuAnimation.FastDuration |
258 | + } |
259 | + ScaleAnimator { |
260 | + easing: UbuntuAnimation.StandardEasing |
261 | + duration: UbuntuAnimation.FastDuration |
262 | + } |
263 | + } |
264 | + } |
265 | + } |
266 | + } |
267 | |
268 | // leading panel loader |
269 | Loader { |
270 | @@ -200,20 +249,18 @@ |
271 | } |
272 | } |
273 | ] |
274 | - transitions: [ |
275 | - Transition { |
276 | - from: "" |
277 | - to: "selectable" |
278 | - reversible: true |
279 | - enabled: listItemStyle.animatePanels |
280 | - PropertyAnimation { |
281 | - target: styledItem.contentItem |
282 | - properties: "anchors.leftMargin" |
283 | - easing: UbuntuAnimation.StandardEasing |
284 | - duration: UbuntuAnimation.FastDuration |
285 | - } |
286 | + transitions: Transition { |
287 | + from: "" |
288 | + to: "selectable" |
289 | + reversible: true |
290 | + enabled: listItemStyle.animatePanels |
291 | + PropertyAnimation { |
292 | + target: styledItem.contentItem |
293 | + properties: "anchors.leftMargin" |
294 | + easing: UbuntuAnimation.StandardEasing |
295 | + duration: UbuntuAnimation.FastDuration |
296 | } |
297 | - ] |
298 | + } |
299 | } |
300 | // trailing panel loader |
301 | Loader { |
302 | @@ -229,6 +276,38 @@ |
303 | panelComponent : null |
304 | // context properties used in delegates |
305 | readonly property bool leading: false |
306 | + readonly property bool loaded: status == Loader.Ready |
307 | + |
308 | + // panel states |
309 | + states: State { |
310 | + name: "draggable" |
311 | + when: styledItem.dragMode |
312 | + PropertyChanges { |
313 | + target: trailingLoader |
314 | + sourceComponent: dragDelegate |
315 | + width: units.gu(5) |
316 | + } |
317 | + PropertyChanges { |
318 | + target: listItemStyle |
319 | + anchors.rightMargin: 0 |
320 | + } |
321 | + PropertyChanges { |
322 | + target: styledItem.contentItem |
323 | + anchors.rightMargin: units.gu(5) |
324 | + } |
325 | + } |
326 | + transitions: Transition { |
327 | + from: "" |
328 | + to: "*" |
329 | + reversible: true |
330 | + enabled: listItemStyle.animatePanels |
331 | + PropertyAnimation { |
332 | + target: styledItem.contentItem |
333 | + properties: "anchors.rightMargin" |
334 | + easing: UbuntuAnimation.StandardEasing |
335 | + duration: UbuntuAnimation.FastDuration |
336 | + } |
337 | + } |
338 | } |
339 | |
340 | // internals |
341 | @@ -303,6 +382,12 @@ |
342 | } |
343 | } |
344 | |
345 | + // simple drop animation |
346 | + dropAnimation: SmoothedAnimation { |
347 | + properties: "y" |
348 | + velocity: units.gu(60) |
349 | + } |
350 | + |
351 | onXChanged: internals.updateSnapDirection() |
352 | // overriding default functions |
353 | function swipeEvent(event) { |
354 | |
355 | === modified file 'modules/Ubuntu/Components/UbuntuListView.qml' |
356 | --- modules/Ubuntu/Components/UbuntuListView.qml 2014-11-18 11:38:59 +0000 |
357 | +++ modules/Ubuntu/Components/UbuntuListView.qml 2015-02-27 07:56:03 +0000 |
358 | @@ -133,4 +133,11 @@ |
359 | enabled: root.expandedIndex != -1 |
360 | onClicked: root.expandedIndex = -1; |
361 | } |
362 | + |
363 | + // animate move displaced |
364 | + moveDisplaced: Transition { |
365 | + UbuntuNumberAnimation { |
366 | + properties: "x,y" |
367 | + } |
368 | + } |
369 | } |
370 | |
371 | === modified file 'modules/Ubuntu/Components/plugin/plugin.cpp' |
372 | --- modules/Ubuntu/Components/plugin/plugin.cpp 2015-02-11 20:17:06 +0000 |
373 | +++ modules/Ubuntu/Components/plugin/plugin.cpp 2015-02-27 07:56:03 +0000 |
374 | @@ -173,6 +173,7 @@ |
375 | qmlRegisterType<UCListItem>(uri, 1, 2, "ListItem"); |
376 | qmlRegisterType<UCListItemDivider>(); |
377 | qmlRegisterUncreatableType<UCSwipeEvent>(uri, 1, 2, "SwipeEvent", "This is an event object."); |
378 | + qmlRegisterUncreatableType<UCDragEvent>(uri, 1, 2, "ListItemDrag", "This is an event object"); |
379 | qmlRegisterType<UCListItemActions>(uri, 1, 2, "ListItemActions"); |
380 | qmlRegisterUncreatableType<UCViewItemsAttached>(uri, 1, 2, "ViewItems", "Not instantiable"); |
381 | } |
382 | |
383 | === modified file 'modules/Ubuntu/Components/plugin/plugin.pro' |
384 | --- modules/Ubuntu/Components/plugin/plugin.pro 2015-02-12 14:35:51 +0000 |
385 | +++ modules/Ubuntu/Components/plugin/plugin.pro 2015-02-27 07:56:03 +0000 |
386 | @@ -75,7 +75,9 @@ |
387 | propertychange_p.h \ |
388 | uclistitemstyle.h \ |
389 | ucserviceproperties.h \ |
390 | - ucserviceproperties_p.h |
391 | + ucserviceproperties_p.h \ |
392 | + privates/listitemdragarea.h \ |
393 | + privates/listitemdraghandler.h |
394 | |
395 | SOURCES += plugin.cpp \ |
396 | uctheme.cpp \ |
397 | @@ -118,7 +120,9 @@ |
398 | propertychange_p.cpp \ |
399 | uclistitemstyle.cpp \ |
400 | ucviewitemsattached.cpp \ |
401 | - ucserviceproperties.cpp |
402 | + ucserviceproperties.cpp \ |
403 | + privates/listitemdragarea.cpp \ |
404 | + privates/listitemdraghandler.cpp |
405 | |
406 | # adapters |
407 | SOURCES += adapters/alarmsadapter_organizer.cpp |
408 | |
409 | === added directory 'modules/Ubuntu/Components/plugin/privates' |
410 | === added file 'modules/Ubuntu/Components/plugin/privates/listitemdragarea.cpp' |
411 | --- modules/Ubuntu/Components/plugin/privates/listitemdragarea.cpp 1970-01-01 00:00:00 +0000 |
412 | +++ modules/Ubuntu/Components/plugin/privates/listitemdragarea.cpp 2015-02-27 07:56:03 +0000 |
413 | @@ -0,0 +1,313 @@ |
414 | +/* |
415 | + * Copyright 2015 Canonical Ltd. |
416 | + * |
417 | + * This program is free software; you can redistribute it and/or modify |
418 | + * it under the terms of the GNU Lesser General Public License as published by |
419 | + * the Free Software Foundation; version 3. |
420 | + * |
421 | + * This program is distributed in the hope that it will be useful, |
422 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
423 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
424 | + * GNU Lesser General Public License for more details. |
425 | + * |
426 | + * You should have received a copy of the GNU Lesser General Public License |
427 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
428 | + */ |
429 | + |
430 | +#include "listitemdragarea.h" |
431 | +#include "listitemdraghandler.h" |
432 | +#include "uclistitem.h" |
433 | +#include "uclistitem_p.h" |
434 | +#include "ucunits.h" |
435 | +#include "i18n.h" |
436 | +#include <QtQml/QQmlInfo> |
437 | +#include <QtQuick/private/qquickitem_p.h> |
438 | +#include <QtQuick/private/qquickflickable_p.h> |
439 | + |
440 | +#define IMPLICIT_DRAG_WIDTH_GU 5 |
441 | +#define DRAG_SCROLL_TIMEOUT 15 |
442 | + |
443 | +#define MIN(x, y) ((x) < (y) ? (x) : (y)) |
444 | +#define MAX(x, y) ((x) > (y) ? (x) : (y)) |
445 | +#define CLAMP(v, min, max) ((min) <= (max) ? MAX(min, MIN(v, max)) : MAX(max, MIN(v, min))) |
446 | + |
447 | +ListItemDragArea::ListItemDragArea(QQuickItem *parent) |
448 | + : QQuickItem(parent) |
449 | + , listView(static_cast<QQuickFlickable*>(parent)) |
450 | + , viewAttached(0) |
451 | + , scrollDirection(0) |
452 | + , fromIndex(-1) |
453 | + , toIndex(-1) |
454 | + , min(-1) |
455 | + , max(-1) |
456 | +{ |
457 | + setAcceptedMouseButtons(Qt::LeftButton); |
458 | + |
459 | + // for testing purposes |
460 | + setObjectName("drag_area"); |
461 | +} |
462 | + |
463 | +void ListItemDragArea::init() |
464 | +{ |
465 | + setParentItem(static_cast<QQuickItem*>(parent())); |
466 | + QQuickAnchors *anchors = QQuickItemPrivate::get(this)->anchors(); |
467 | + anchors->setFill(parentItem()); |
468 | + viewAttached = static_cast<UCViewItemsAttached*>( |
469 | + qmlAttachedPropertiesObject<UCViewItemsAttached>(listView)); |
470 | + reset(); |
471 | +} |
472 | + |
473 | +void ListItemDragArea::reset() |
474 | +{ |
475 | + fromIndex = toIndex = min = max = -1; |
476 | + item = 0; |
477 | + lastPos = QPointF(); |
478 | + setEnabled(true); |
479 | +} |
480 | + |
481 | +void ListItemDragArea::timerEvent(QTimerEvent *event) |
482 | +{ |
483 | + QQuickItem::timerEvent(event); |
484 | + if (event->timerId() == scrollTimer.timerId()) { |
485 | + qreal scrollAmount = UCUnits::instance().gu(0.5) * scrollDirection; |
486 | + qreal contentHeight = listView->contentHeight(); |
487 | + qreal height = listView->height(); |
488 | + if ((contentHeight - height) > 0) { |
489 | + // take topMargin into account when clamping |
490 | + qreal contentY = CLAMP(listView->contentY() + scrollAmount, |
491 | + -listView->topMargin(), |
492 | + contentHeight - height + listView->originY()); |
493 | + listView->setContentY(contentY); |
494 | + // update |
495 | + mouseMoveEvent(0); |
496 | + } |
497 | + } |
498 | +} |
499 | + |
500 | +// starts dragging operation; emits draggingStarted() and if the signal handler is implemented, |
501 | +// depending on the acceptance, will create a fake item and will start dragging. If the start is |
502 | +// cancelled, no dragging will happen |
503 | +void ListItemDragArea::mousePressEvent(QMouseEvent *event) |
504 | +{ |
505 | + mousePos = event->localPos(); |
506 | + QPointF pos = mapDragAreaPos(); |
507 | + UCListItem *listItem = itemAt(pos.x(), pos.y()); |
508 | + if (!listItem) { |
509 | + event->setAccepted(false); |
510 | + return; |
511 | + } |
512 | + // check if we tapped over the drag panel |
513 | + UCListItemPrivate *pListItem = UCListItemPrivate::get(listItem); |
514 | + if (pListItem->styleItem && pListItem->styleItem->m_dragPanel) { |
515 | + // convert mouse into local panel coordinates |
516 | + QPointF panelPos = pListItem->styleItem->m_dragPanel->mapFromItem(this, mousePos); |
517 | + if (!pListItem->styleItem->m_dragPanel->contains(panelPos)) { |
518 | + // not tapped over the drag panel, leave |
519 | + event->setAccepted(false); |
520 | + return; |
521 | + } |
522 | + } |
523 | + int index = indexAt(pos.x(), pos.y()); |
524 | + bool start = false; |
525 | + max = min = -1; |
526 | + // call start handler if implemented |
527 | + UCViewItemsAttachedPrivate *pViewAttached = UCViewItemsAttachedPrivate::get(viewAttached); |
528 | + if (pViewAttached->isDragUpdatedConnected()) { |
529 | + UCDragEvent drag(UCDragEvent::Started, index, -1, -1, -1); |
530 | + Q_EMIT viewAttached->dragUpdated(&drag); |
531 | + start = drag.m_accept; |
532 | + min = drag.m_minimum; |
533 | + max = drag.m_maximum; |
534 | + } else { |
535 | + qmlInfo(parentItem()) << UbuntuI18n::instance().tr( |
536 | + "ListView has no ViewItems.dragUpdated() signal handler implemented. "\ |
537 | + "No dragging will be possible."); |
538 | + } |
539 | + if (start) { |
540 | + pViewAttached->buildChangesList(false); |
541 | + fromIndex = toIndex = index; |
542 | + lastPos = pos; |
543 | + // create temp drag item |
544 | + createDraggedItem(listItem); |
545 | + } |
546 | + event->setAccepted(start); |
547 | +} |
548 | + |
549 | +// stops dragging, performs drop event (event.direction = ListItemDrag.Steady) |
550 | +// and clears temporary item |
551 | +void ListItemDragArea::mouseReleaseEvent(QMouseEvent *event) |
552 | +{ |
553 | + mousePos = event->localPos(); |
554 | + if (item.isNull()) { |
555 | + return; |
556 | + } |
557 | + // stop scroll timer |
558 | + scrollTimer.stop(); |
559 | + UCViewItemsAttachedPrivate *pViewAttached = UCViewItemsAttachedPrivate::get(viewAttached); |
560 | + if (pViewAttached->isDragUpdatedConnected() && (fromIndex != toIndex)) { |
561 | + UCDragEvent drag(UCDragEvent::Dropped, fromIndex, toIndex, min, max); |
562 | + Q_EMIT viewAttached->dragUpdated(&drag); |
563 | + updateDraggedItem(); |
564 | + if (drag.m_accept) { |
565 | + pViewAttached->updateSelectedIndices(fromIndex, toIndex); |
566 | + } |
567 | + } |
568 | + // unlock flickables |
569 | + pViewAttached->clearChangesList(); |
570 | + // perform drop |
571 | + UCListItemPrivate::get(item.data())->dragHandler->drop(); |
572 | + item = 0; |
573 | + fromIndex = toIndex = -1; |
574 | +} |
575 | + |
576 | +void ListItemDragArea::mouseMoveEvent(QMouseEvent *event) |
577 | +{ |
578 | + if (event) { |
579 | + mousePos = event->localPos(); |
580 | + } |
581 | + if (!item) { |
582 | + return; |
583 | + } |
584 | + QPointF pos = mapDragAreaPos(); |
585 | + qreal dy = -(lastPos.y() - pos.y()); |
586 | + qreal indexHotspot = item->y() + item->height() / 2; |
587 | + // update dragged item even if the dragging may be forbidden |
588 | + item->setY(item->y() + dy); |
589 | + lastPos = pos; |
590 | + |
591 | + // check what will be the index after the drag |
592 | + int index = indexAt(pos.x(), indexHotspot); |
593 | + if (index < 0) { |
594 | + return; |
595 | + } |
596 | + if ((min >= 0) && (min > index)) { |
597 | + // about to drag beyond the minimum, leave |
598 | + return; |
599 | + } |
600 | + if ((max >= 0) && (max < index)) { |
601 | + // about to drag beyond maximum, leave |
602 | + return; |
603 | + } |
604 | + |
605 | + // should we scroll the view? use a margin of 20% of the dragged item's height from top and bottom of the item |
606 | + qreal scrollMargin = item->height() * 0.2; |
607 | + qreal topHotspot = item->y() + scrollMargin - listView->contentY(); |
608 | + qreal bottomHotspot = item->y() + item->height() - scrollMargin - listView->contentY(); |
609 | + // use MouseArea's top/bottom as limits |
610 | + qreal topViewMargin = y() + listView->topMargin(); |
611 | + qreal bottomViewMargin = y() + height() - listView->bottomMargin(); |
612 | + scrollDirection = 0; |
613 | + if (topHotspot < topViewMargin) { |
614 | + // scroll upwards |
615 | + scrollDirection = -1; |
616 | + } else if (bottomHotspot > bottomViewMargin) { |
617 | + // scroll downwards |
618 | + scrollDirection = 1; |
619 | + } |
620 | + if (!scrollDirection) { |
621 | + scrollTimer.stop(); |
622 | + } else if (!scrollTimer.isActive()){ |
623 | + scrollTimer.start(DRAG_SCROLL_TIMEOUT, this); |
624 | + } |
625 | + |
626 | + // do we have index change? |
627 | + if (toIndex == index) { |
628 | + // no change, leave |
629 | + return; |
630 | + } |
631 | + |
632 | + toIndex = index; |
633 | + if (fromIndex != toIndex) { |
634 | + bool update = true; |
635 | + UCViewItemsAttachedPrivate *pViewAttached = UCViewItemsAttachedPrivate::get(viewAttached); |
636 | + if (pViewAttached->isDragUpdatedConnected()) { |
637 | + UCDragEvent drag(UCDragEvent::Moving, fromIndex, toIndex, min, max); |
638 | + Q_EMIT viewAttached->dragUpdated(&drag); |
639 | + update = drag.m_accept; |
640 | + if (update) { |
641 | + pViewAttached->updateSelectedIndices(fromIndex, toIndex); |
642 | + } |
643 | + } |
644 | + if (update) { |
645 | + // update item coordinates in the dragged item |
646 | + updateDraggedItem(); |
647 | + fromIndex = toIndex; |
648 | + } |
649 | + } |
650 | +} |
651 | + |
652 | +// returns the mapped mouse position of the dragged item's dragHandler to the ListView |
653 | +QPointF ListItemDragArea::mapDragAreaPos() |
654 | +{ |
655 | + QPointF pos(mousePos.x(), mousePos.y() + listView->contentY()); |
656 | + pos = listView->mapFromItem(this, pos); |
657 | + return pos; |
658 | +} |
659 | + |
660 | +// calls ListView.indexAt() invokable |
661 | +int ListItemDragArea::indexAt(qreal x, qreal y) |
662 | +{ |
663 | + if (!listView) { |
664 | + return -1; |
665 | + } |
666 | + int result = -1; |
667 | + QMetaObject::invokeMethod(listView, "indexAt", Qt::DirectConnection, |
668 | + Q_RETURN_ARG(int, result), |
669 | + Q_ARG(qreal, x), |
670 | + Q_ARG(qreal, y) |
671 | + ); |
672 | + return result; |
673 | +} |
674 | + |
675 | +// calls ListView.itemAt() invokable |
676 | +UCListItem *ListItemDragArea::itemAt(qreal x, qreal y) |
677 | +{ |
678 | + if (!listView) { |
679 | + return NULL; |
680 | + } |
681 | + QQuickItem *result = 0; |
682 | + QMetaObject::invokeMethod(listView, "itemAt", Qt::DirectConnection, |
683 | + Q_RETURN_ARG(QQuickItem*, result), |
684 | + Q_ARG(qreal, x), |
685 | + Q_ARG(qreal, y) |
686 | + ); |
687 | + return static_cast<UCListItem*>(result); |
688 | +} |
689 | + |
690 | +// creates a temporary list item available for the dragging time |
691 | +void ListItemDragArea::createDraggedItem(UCListItem *baseItem) |
692 | +{ |
693 | + if (item || !baseItem) { |
694 | + return; |
695 | + } |
696 | + QQmlComponent *delegate = listView->property("delegate").value<QQmlComponent*>(); |
697 | + if (!delegate) { |
698 | + return; |
699 | + } |
700 | + // use baseItem's context to get access to the ListView's model roles |
701 | + // use two-step component creation to have similar steps as when it is created in QML, |
702 | + // so itemChanged() is invoked prior to componentComplete() |
703 | + // use dragged item's context as parent context so we get all model roles and |
704 | + // context properties of that item |
705 | + QQmlContext *context = new QQmlContext(qmlContext(baseItem), baseItem); |
706 | + item = static_cast<UCListItem*>(delegate->beginCreate(context)); |
707 | + if (item) { |
708 | + QQml_setParent_noEvent(item, listView->contentItem()); |
709 | + // create drag handler instance |
710 | + UCListItemPrivate *pItem = UCListItemPrivate::get(item); |
711 | + pItem->dragHandler = new ListItemDragHandler(baseItem, item); |
712 | + pItem->dragHandler->init(); |
713 | + // invokes itemChanged() |
714 | + item->setParentItem(listView->contentItem()); |
715 | + // invoked componentComplete() |
716 | + delegate->completeCreate(); |
717 | + } |
718 | +} |
719 | + |
720 | +void ListItemDragArea::updateDraggedItem() |
721 | +{ |
722 | + if (abs(fromIndex - toIndex) > 0) { |
723 | + UCListItem *targetItem = itemAt(item->x(), item->y() + item->height() / 2); |
724 | + UCListItemPrivate::get(item)->dragHandler->update(targetItem); |
725 | + } |
726 | +} |
727 | |
728 | === added file 'modules/Ubuntu/Components/plugin/privates/listitemdragarea.h' |
729 | --- modules/Ubuntu/Components/plugin/privates/listitemdragarea.h 1970-01-01 00:00:00 +0000 |
730 | +++ modules/Ubuntu/Components/plugin/privates/listitemdragarea.h 2015-02-27 07:56:03 +0000 |
731 | @@ -0,0 +1,55 @@ |
732 | +/* |
733 | + * Copyright 2015 Canonical Ltd. |
734 | + * |
735 | + * This program is free software; you can redistribute it and/or modify |
736 | + * it under the terms of the GNU Lesser General Public License as published by |
737 | + * the Free Software Foundation; version 3. |
738 | + * |
739 | + * This program is distributed in the hope that it will be useful, |
740 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
741 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
742 | + * GNU Lesser General Public License for more details. |
743 | + * |
744 | + * You should have received a copy of the GNU Lesser General Public License |
745 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
746 | + */ |
747 | + |
748 | +#ifndef LISTITEMDRAGAREA_H |
749 | +#define LISTITEMDRAGAREA_H |
750 | + |
751 | +#include "uclistitem.h" |
752 | +#include <QtCore/QBasicTimer> |
753 | +#include <QtCore/QPointer> |
754 | + |
755 | +class QQuickFlickable; |
756 | +class ListItemDragArea : public QQuickItem |
757 | +{ |
758 | + Q_OBJECT |
759 | +public: |
760 | + explicit ListItemDragArea(QQuickItem *parent = 0); |
761 | + void init(); |
762 | + void reset(); |
763 | + |
764 | +protected: |
765 | + void timerEvent(QTimerEvent *event); |
766 | + void mousePressEvent(QMouseEvent *event); |
767 | + void mouseReleaseEvent(QMouseEvent *event); |
768 | + void mouseMoveEvent(QMouseEvent *event); |
769 | + |
770 | +private: |
771 | + QBasicTimer scrollTimer; |
772 | + QPointer<UCListItem> item; |
773 | + QQuickFlickable *listView; |
774 | + UCViewItemsAttached *viewAttached; |
775 | + QPointF lastPos, mousePos; |
776 | + int scrollDirection; |
777 | + int fromIndex, toIndex, min, max; |
778 | + |
779 | + QPointF mapDragAreaPos(); |
780 | + int indexAt(qreal x, qreal y); |
781 | + UCListItem *itemAt(qreal x, qreal y); |
782 | + void createDraggedItem(UCListItem *baseItem); |
783 | + void updateDraggedItem(); |
784 | +}; |
785 | + |
786 | +#endif // LISTITEMDRAGAREA_H |
787 | |
788 | === added file 'modules/Ubuntu/Components/plugin/privates/listitemdraghandler.cpp' |
789 | --- modules/Ubuntu/Components/plugin/privates/listitemdraghandler.cpp 1970-01-01 00:00:00 +0000 |
790 | +++ modules/Ubuntu/Components/plugin/privates/listitemdraghandler.cpp 2015-02-27 07:56:03 +0000 |
791 | @@ -0,0 +1,86 @@ |
792 | +/* |
793 | + * Copyright 2015 Canonical Ltd. |
794 | + * |
795 | + * This program is free software; you can redistribute it and/or modify |
796 | + * it under the terms of the GNU Lesser General Public License as published by |
797 | + * the Free Software Foundation; version 3. |
798 | + * |
799 | + * This program is distributed in the hope that it will be useful, |
800 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
801 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
802 | + * GNU Lesser General Public License for more details. |
803 | + * |
804 | + * You should have received a copy of the GNU Lesser General Public License |
805 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
806 | + */ |
807 | + |
808 | +#include "listitemdraghandler.h" |
809 | +#include "uclistitem.h" |
810 | +#include "uclistitem_p.h" |
811 | +#include "propertychange_p.h" |
812 | +#include <QtQuick/private/qquickanimation_p.h> |
813 | + |
814 | +ListItemDragHandler::ListItemDragHandler(UCListItem *baseItem, UCListItem *listItem) |
815 | + : QObject(listItem) |
816 | + , listItem(listItem) |
817 | + , baseItem(baseItem) |
818 | + , baseVisible(0) |
819 | +{ |
820 | + targetPos = baseItem->position(); |
821 | + baseVisible = new PropertyChange(baseItem, "visible"); |
822 | +} |
823 | + |
824 | +ListItemDragHandler::~ListItemDragHandler() |
825 | +{ |
826 | + // make sure the property change object is deleted |
827 | + delete baseVisible; |
828 | +} |
829 | + |
830 | +void ListItemDragHandler::init() |
831 | +{ |
832 | + PropertyChange::setValue(baseVisible, false); |
833 | + // position the item and show it |
834 | + listItem->setPosition(baseItem->position()); |
835 | + listItem->setZ(2); |
836 | + listItem->setVisible(true); |
837 | + // emit draggingChanged() signal |
838 | + Q_EMIT listItem->draggingChanged(); |
839 | +} |
840 | + |
841 | +// handles drop gesture animated if the style has animation defined for it |
842 | +void ListItemDragHandler::drop() |
843 | +{ |
844 | + QQuickPropertyAnimation *animation = UCListItemPrivate::get(listItem)->styleItem->m_dropAnimation; |
845 | + if (animation) { |
846 | + // complete any previous animation |
847 | + animation->complete(); |
848 | + |
849 | + connect(animation, &QQuickAbstractAnimation::stopped, |
850 | + this, &ListItemDragHandler::dropItem, Qt::DirectConnection); |
851 | + // force properties to contain only the 'y' coordinate |
852 | + animation->setProperties("y"); |
853 | + animation->setTargetObject(listItem); |
854 | + animation->setFrom(listItem->y()); |
855 | + animation->setTo(targetPos.y()); |
856 | + animation->start(); |
857 | + } else { |
858 | + dropItem(); |
859 | + } |
860 | +} |
861 | + |
862 | +// private slot connected to the reposition animation to drop item |
863 | +void ListItemDragHandler::dropItem() |
864 | +{ |
865 | + listItem->setVisible(false); |
866 | + listItem->deleteLater(); |
867 | + delete baseVisible; |
868 | + baseVisible = 0; |
869 | +} |
870 | + |
871 | +// update dragged item with the new target item the dragging is hovered over |
872 | +void ListItemDragHandler::update(UCListItem *hoveredItem) |
873 | +{ |
874 | + if (hoveredItem) { |
875 | + targetPos = hoveredItem->position(); |
876 | + } |
877 | +} |
878 | |
879 | === added file 'modules/Ubuntu/Components/plugin/privates/listitemdraghandler.h' |
880 | --- modules/Ubuntu/Components/plugin/privates/listitemdraghandler.h 1970-01-01 00:00:00 +0000 |
881 | +++ modules/Ubuntu/Components/plugin/privates/listitemdraghandler.h 2015-02-27 07:56:03 +0000 |
882 | @@ -0,0 +1,47 @@ |
883 | +/* |
884 | + * Copyright 2015 Canonical Ltd. |
885 | + * |
886 | + * This program is free software; you can redistribute it and/or modify |
887 | + * it under the terms of the GNU Lesser General Public License as published by |
888 | + * the Free Software Foundation; version 3. |
889 | + * |
890 | + * This program is distributed in the hope that it will be useful, |
891 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
892 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
893 | + * GNU Lesser General Public License for more details. |
894 | + * |
895 | + * You should have received a copy of the GNU Lesser General Public License |
896 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
897 | + */ |
898 | + |
899 | +#ifndef LISTITEMDRAGHANDLER_H |
900 | +#define LISTITEMDRAGHANDLER_H |
901 | + |
902 | +#include <QtCore/QObject> |
903 | +#include <QtCore/QPointer> |
904 | +#include <QtCore/QPointF> |
905 | + |
906 | +class UCListItem; |
907 | +class PropertyChange; |
908 | +class ListItemDragHandler : public QObject |
909 | +{ |
910 | + Q_OBJECT |
911 | +public: |
912 | + explicit ListItemDragHandler(UCListItem *baseItem, UCListItem *listItem = 0); |
913 | + ~ListItemDragHandler(); |
914 | + |
915 | + void init(); |
916 | + void drop(); |
917 | + void update(UCListItem *hoveredItem); |
918 | + |
919 | +private Q_SLOTS: |
920 | + void dropItem(); |
921 | + |
922 | +private: |
923 | + UCListItem *listItem; |
924 | + QPointer<UCListItem> baseItem; |
925 | + PropertyChange *baseVisible; |
926 | + QPointF targetPos; |
927 | +}; |
928 | + |
929 | +#endif // LISTITEMDRAGHANDLER_H |
930 | |
931 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem.cpp' |
932 | --- modules/Ubuntu/Components/plugin/uclistitem.cpp 2015-02-19 12:17:01 +0000 |
933 | +++ modules/Ubuntu/Components/plugin/uclistitem.cpp 2015-02-27 07:56:03 +0000 |
934 | @@ -240,7 +240,7 @@ |
935 | QObject::connect(&UCTheme::instance(), SIGNAL(nameChanged()), q, SLOT(_q_updateThemedData())); |
936 | _q_updateThemedData(); |
937 | |
938 | - // watch size change and set implicit size; |
939 | + // watch grid unit size change and set implicit size |
940 | QObject::connect(&UCUnits::instance(), SIGNAL(gridUnitChanged()), q, SLOT(_q_updateSize())); |
941 | _q_updateSize(); |
942 | } |
943 | @@ -314,6 +314,14 @@ |
944 | Q_EMIT q->selectModeChanged(); |
945 | } |
946 | |
947 | +// same for the dragMode |
948 | +void UCListItemPrivate::_q_syncDragMode() |
949 | +{ |
950 | + initStyleItem(); |
951 | + Q_Q(UCListItem); |
952 | + Q_EMIT q->dragModeChanged(); |
953 | +} |
954 | + |
955 | /*! |
956 | * \qmlproperty Component ListItem::style |
957 | * Holds the style of the component defining the components visualizing the leading/ |
958 | @@ -442,10 +450,12 @@ |
959 | void UCListItemPrivate::_q_updateSize() |
960 | { |
961 | Q_Q(UCListItem); |
962 | - |
963 | // update divider thickness |
964 | divider->setImplicitHeight(UCUnits::instance().dp(DIVIDER_THICKNESS_DP)); |
965 | - QQuickItem *owner = flickable ? flickable : parentItem; |
966 | + QQuickItem *owner = qobject_cast<QQuickItem*>(q->sender()); |
967 | + if (!owner && parentAttached) { |
968 | + owner = static_cast<QQuickItem*>(parentAttached->parent()); |
969 | + } |
970 | q->setImplicitWidth(owner ? owner->width() : UCUnits::instance().gu(IMPLICIT_LISTITEM_WIDTH_GU)); |
971 | q->setImplicitHeight(UCUnits::instance().gu(IMPLICIT_LISTITEM_HEIGHT_GU)); |
972 | } |
973 | @@ -626,7 +636,7 @@ |
974 | * be highlighted, and \l highlighted property will not be toggled either. Also, |
975 | * there will be no highlight happening if the click happens on the active component. |
976 | * \qml |
977 | - * import QtQuick 2.3 |
978 | + * import QtQuick 2.4 |
979 | * import Ubuntu.Components 1.2 |
980 | * |
981 | * MainView { |
982 | @@ -682,17 +692,17 @@ |
983 | * } |
984 | * \endqml |
985 | * |
986 | - * \c contentItem holds all components and resources declared as child to ListItem. |
987 | + * \l contentItem holds all components and resources declared as child to ListItem. |
988 | * Being an Item, all properties can be accessed or altered. However, make sure you |
989 | - * never change \c x, \c y, \c width, \c height or \c anchors properties as those are |
990 | + * never change \b x, \b y, \b width, \b height or \b anchors properties as those are |
991 | * controlled by the ListItem itself when leading or trailing actions are revealed |
992 | * or when selectable and draggable mode is turned on, and thus might cause the |
993 | * component to misbehave. Anchors margins are free to alter. |
994 | * |
995 | * Each ListItem has a thin divider shown on the bottom of the component. This |
996 | - * divider can be configured through the \c divider grouped property, which can |
997 | + * divider can be configured through the \l divider grouped property, which can |
998 | * configure its margins from the edges of the ListItem as well as its visibility. |
999 | - * When used in \c ListView or \l UbuntuListView, the last list item will not |
1000 | + * When used in ListView or \l UbuntuListView, the last list item will not |
1001 | * show the divider no matter of the visible property value set. |
1002 | * |
1003 | * ListItem can handle actions that can get swiped from front or back of the item. |
1004 | @@ -754,13 +764,13 @@ |
1005 | * |
1006 | * The component is styled using the \l ListItemStyle style interface. |
1007 | * |
1008 | - * \section3 Selection mode |
1009 | + * \section2 Selection mode |
1010 | * The selection mode of a ListItem is controlled by the \l ViewItems::selectMode |
1011 | * attached property. This property is attached to each parent item of the ListItem |
1012 | * exception being when used as delegate in ListView, where the property is attached |
1013 | * to the view itself. |
1014 | * \qml |
1015 | - * import QtQuick 2.3 |
1016 | + * import QtQuick 2.4 |
1017 | * import Ubuntu.Components 1.2 |
1018 | * |
1019 | * Flickable { |
1020 | @@ -795,6 +805,177 @@ |
1021 | * implies is that leading and trailing actions cannot be swiped in. \ selectable |
1022 | * property can be used to implement different behavior when \l clicked or \l |
1023 | * pressAndHold. |
1024 | + * |
1025 | + * \section2 Dragging mode |
1026 | + * The dragging mode is only supported on ListView, as it requires a model supported |
1027 | + * view to be used. The drag mode can be activated through the \l ViewItems::dragMode |
1028 | + * attached property, when attached to the ListView. The items will show a panel |
1029 | + * as defined in the style, and dragging will be possible when initiated over this |
1030 | + * panel. Pressing or clicking anywhere else on the ListItem will invoke the item's |
1031 | + * action assigned to the touched area. |
1032 | + * |
1033 | + * The dragging is realized through the \l ViewItems::dragUpdated signal, and |
1034 | + * a signal handler must be implemented in order to have the draging working. |
1035 | + * Implementations can drive the drag to be live (each time the dragged item is |
1036 | + * dragged over an other item will change the order of the items) or drag'n'drop |
1037 | + * way (the dragged item will be moved only when the user releases the item by |
1038 | + * dropping it to the desired position). The signal has a \l ListItemDrag \e event |
1039 | + * parameter, which gives detailed information about the drag event, like started, |
1040 | + * dragged up or downwards or dropped, allowing in this way various restrictions |
1041 | + * on the dragging. |
1042 | + * |
1043 | + * The dragging event provides three states reported in \l ListItemDrag::status |
1044 | + * field, \e Started, \e Moving and \e Dropped. The other event field values depend |
1045 | + * on the status, therefore the status must be taken into account when implementing |
1046 | + * the signal handler. In case live dragging is needed, \e Moving state must be |
1047 | + * checked, and for non-live drag (drag'n'drop) the \e Moving state must be blocked |
1048 | + * by setting \e {event.accept = false}, otherwise the dragging will not know |
1049 | + * whether the model has been updated or not. |
1050 | + * |
1051 | + * Example of live drag implementation: |
1052 | + * \qml |
1053 | + * import QtQuick 2.4 |
1054 | + * import Ubuntu.Components 1.2 |
1055 | + * |
1056 | + * ListView { |
1057 | + * model: ListModel { |
1058 | + * Component.onCompleted: { |
1059 | + * for (var i = 0; i < 100; i++) { |
1060 | + * append({tag: "List item #"+i}); |
1061 | + * } |
1062 | + * } |
1063 | + * } |
1064 | + * delegate: ListItem { |
1065 | + * Label { |
1066 | + * text: modelData |
1067 | + * } |
1068 | + * color: dragMode ? "lightblue" : "lightgray" |
1069 | + * onPressAndHold: ListView.view.ViewItems.dragMode = |
1070 | + * !ListView.view.ViewItems.dragMode |
1071 | + * } |
1072 | + * ViewItems.onDragUpdated: { |
1073 | + * if (event.status == ListItemDrag.Moving) { |
1074 | + * model.move(event.from, event.to, 1); |
1075 | + * } |
1076 | + * } |
1077 | + * moveDisplaced: Transition { |
1078 | + * UbuntuNumberAnimation { |
1079 | + * property: "y" |
1080 | + * } |
1081 | + * } |
1082 | + * } |
1083 | + * \endqml |
1084 | + * |
1085 | + * Example of drag'n'drop implementation: |
1086 | + * \qml |
1087 | + * import QtQuick 2.4 |
1088 | + * import Ubuntu.Components 1.2 |
1089 | + * |
1090 | + * ListView { |
1091 | + * model: ListModel { |
1092 | + * Component.onCompleted: { |
1093 | + * for (var i = 0; i < 100; i++) { |
1094 | + * append({tag: "List item #"+i}); |
1095 | + * } |
1096 | + * } |
1097 | + * } |
1098 | + * delegate: ListItem { |
1099 | + * Label { |
1100 | + * text: modelData |
1101 | + * } |
1102 | + * color: dragMode ? "lightblue" : "lightgray" |
1103 | + * onPressAndHold: ListView.view.ViewItems.dragMode = |
1104 | + * !ListView.view.ViewItems.dragMode |
1105 | + * } |
1106 | + * ViewItems.onDragUpdated: { |
1107 | + * if (event.status == ListItemDrag.Moving) { |
1108 | + * // inform dragging that move is not performed |
1109 | + * event.accept = false; |
1110 | + * } else if (event.status == ListItemDrag.Dropped) { |
1111 | + * model.move(event.from, event.to, 1); |
1112 | + * } |
1113 | + * } |
1114 | + * moveDisplaced: Transition { |
1115 | + * UbuntuNumberAnimation { |
1116 | + * property: "y" |
1117 | + * } |
1118 | + * } |
1119 | + * } |
1120 | + * \endqml |
1121 | + * |
1122 | + * ListItem does not provide animations when the ListView's model is updated. In order |
1123 | + * to have animation, use UbuntuListView or provide a transition animation to the |
1124 | + * moveDisplaced or displaced property of the ListView. |
1125 | + * |
1126 | + * \section3 Using non-QAbstractItemModel models |
1127 | + * Live dragging (moving content on the move) is only possible when the model is |
1128 | + * a derivate of the QAbstractItemModel. When a list model is used, the ListView |
1129 | + * will re-create all the items in the view, meaning that the dragged item will |
1130 | + * no longer be controlled by the dragging. However, non-live drag'n'drop operations |
1131 | + * can still be implemented with these kind of lists as well. |
1132 | + * \qml |
1133 | + * import QtQuick 2.4 |
1134 | + * import Ubuntu.Components 1.2 |
1135 | + * |
1136 | + * ListView { |
1137 | + * model: ["plum", "peach", "pomegrenade", "pear", "banana"] |
1138 | + * delegate: ListItem { |
1139 | + * Label { |
1140 | + * text: modelData |
1141 | + * } |
1142 | + * color: dragMode ? "lightblue" : "lightgray" |
1143 | + * onPressAndHold: ListView.view.ViewItems.dragMode = |
1144 | + * !ListView.view.ViewItems.dragMode |
1145 | + * } |
1146 | + * ViewItems.onDragUpdated: { |
1147 | + * if (event.status == ListItemDrag.Started) { |
1148 | + * return; |
1149 | + * } else if (event.status == ListItemDrag.Dropped) { |
1150 | + * var fromData = model[event.from]; |
1151 | + * // must use a temporary variable as list manipulation |
1152 | + * // is not working directly on model |
1153 | + * var list = model; |
1154 | + * list.splice(event.from, 1); |
1155 | + * list.splice(event.to, 0, fromData); |
1156 | + * model = list; |
1157 | + * } else { |
1158 | + * event.accept = false; |
1159 | + * } |
1160 | + * } |
1161 | + * } |
1162 | + * \endqml |
1163 | + * |
1164 | + * When using DelegateModel, it must be taken into account when implementing the |
1165 | + * \l ViewItems::dragUpdated signal handler. |
1166 | + * \qml |
1167 | + * import QtQuick 2.4 |
1168 | + * import Ubuntu.Components 1.2 |
1169 | + * |
1170 | + * ListView { |
1171 | + * model: DelegateModel { |
1172 | + * model: ["apple", "pear", "plum", "peach", "nuts", "dates"] |
1173 | + * delegate: ListItem { |
1174 | + * Label { |
1175 | + * text: modelData |
1176 | + * } |
1177 | + * onPressAndHold: dragMode = !dragMode; |
1178 | + * } |
1179 | + * } |
1180 | + * ViewItems.onDragUpdated: { |
1181 | + * if (event.status == ListItemDrag.Moving) { |
1182 | + * event.accept = false |
1183 | + * } else if (event.status == ListItemDrag.Dropped) { |
1184 | + * var fromData = model.model[event.from]; |
1185 | + * var list = model.model; |
1186 | + * list.splice(event.from, 1); |
1187 | + * list.splice(event.to, 0, fromData); |
1188 | + * model.model = list; |
1189 | + * } |
1190 | + * } |
1191 | + * } |
1192 | + * \endqml |
1193 | + * |
1194 | + * \sa ViewItems::dragMode, ViewItems::dragUpdated |
1195 | */ |
1196 | |
1197 | /*! |
1198 | @@ -860,11 +1041,18 @@ |
1199 | // sync selectModeChanged() |
1200 | connect(d->parentAttached, SIGNAL(selectModeChanged()), |
1201 | this, SLOT(_q_syncSelectMode())); |
1202 | + // also draggable |
1203 | + connect(d->parentAttached, SIGNAL(dragModeChanged()), |
1204 | + this, SLOT(_q_syncDragMode())); |
1205 | |
1206 | - // if selection mode is on, initialize style |
1207 | - if (d->parentAttached->selectMode()) { |
1208 | + // if selection or drag mode is on, initialize style, with animations turned off |
1209 | + if (d->parentAttached->selectMode() || d->parentAttached->dragMode()) { |
1210 | d->initStyleItem(false); |
1211 | } |
1212 | + // set the object name for testing purposes |
1213 | + if (d->dragging()) { |
1214 | + setObjectName("DraggedListItem"); |
1215 | + } |
1216 | } |
1217 | } |
1218 | |
1219 | @@ -1006,7 +1194,7 @@ |
1220 | Q_D(UCListItem); |
1221 | UCStyledItemBase::mouseMoveEvent(event); |
1222 | |
1223 | - if (d->selectMode()) { |
1224 | + if (d->selectMode() || d->dragMode()) { |
1225 | // no move is allowed while selectable mode is on |
1226 | return; |
1227 | } |
1228 | @@ -1183,12 +1371,12 @@ |
1229 | * |
1230 | * This grouped property configures the thin divider shown in the bottom of the |
1231 | * component. The divider is not moved together with the content when swiped left |
1232 | - * or right to reveal the actions. \c colorFrom and \c colorTo configure |
1233 | + * or right to reveal the actions. \b colorFrom and \b colorTo configure |
1234 | * the starting and ending colors of the divider. Beside these properties all Item |
1235 | * specific properties can be accessed. |
1236 | * |
1237 | - * When \c visible is true, the ListItem's content size gets thinner with the |
1238 | - * divider's \c thickness. By default the divider is anchored to the bottom, left |
1239 | + * When \b visible is true, the ListItem's content size gets thinner with the |
1240 | + * divider's \b thickness. By default the divider is anchored to the bottom, left |
1241 | * right of the ListItem, and has a 2dp height. |
1242 | */ |
1243 | UCListItemDivider* UCListItem::divider() const |
1244 | @@ -1214,7 +1402,7 @@ |
1245 | * \li * it has an \l action attached |
1246 | * \li * if the ListItem has an active child component, such as a \l Button, a |
1247 | * \l Switch, etc. |
1248 | - * \li * in general, if an active (enabled and visible) \c MouseArea is added |
1249 | + * \li * in general, if an active (enabled and visible) \b MouseArea is added |
1250 | * as a child component |
1251 | * \li * \l clicked signal handler is implemented or there is a slot or function |
1252 | * connected to it |
1253 | @@ -1334,6 +1522,32 @@ |
1254 | } |
1255 | |
1256 | /*! |
1257 | + * \qmlproperty bool ListItem::dragging |
1258 | + * The property informs about an ongoing dragging on a ListItem. |
1259 | + */ |
1260 | +bool UCListItemPrivate::dragging() |
1261 | +{ |
1262 | + return !dragHandler.isNull(); |
1263 | +} |
1264 | + |
1265 | +/*! |
1266 | + * \qmlproperty bool ListItem::dragMode |
1267 | + * The property reports whether a ListItem is draggable or not. While in drag mode, |
1268 | + * the list item content cannot be swiped. The default value is false. |
1269 | + */ |
1270 | +bool UCListItemPrivate::dragMode() |
1271 | +{ |
1272 | + UCViewItemsAttachedPrivate *attached = UCViewItemsAttachedPrivate::get(parentAttached); |
1273 | + return attached ? attached->draggable : false; |
1274 | +} |
1275 | +void UCListItemPrivate::setDragMode(bool draggable) |
1276 | +{ |
1277 | + if (parentAttached) { |
1278 | + parentAttached->setDragMode(draggable); |
1279 | + } |
1280 | +} |
1281 | + |
1282 | +/*! |
1283 | * |
1284 | * \qmlproperty bool ListItem::selected |
1285 | * The property drives whether a list item is selected or not. Defaults to false. |
1286 | @@ -1382,7 +1596,7 @@ |
1287 | * of the components placed inside the list item. However, when set, the ListItem |
1288 | * will be highlighted on press. |
1289 | * |
1290 | - * If the action set has no value type set, ListItem will set its type to \c |
1291 | + * If the action set has no value type set, ListItem will set its type to \b |
1292 | * Action.Integer and the \l {Action::triggered}{triggered} signal will be getting |
1293 | * the ListItem index as \e value parameter. |
1294 | * |
1295 | |
1296 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem.h' |
1297 | --- modules/Ubuntu/Components/plugin/uclistitem.h 2015-02-18 10:57:06 +0000 |
1298 | +++ modules/Ubuntu/Components/plugin/uclistitem.h 2015-02-27 07:56:03 +0000 |
1299 | @@ -36,6 +36,8 @@ |
1300 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool contentMoving READ contentMoving NOTIFY contentMovingChanged) |
1301 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) |
1302 | Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setHighlightColor RESET resetHighlightColor NOTIFY highlightColorChanged) |
1303 | + Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool dragging READ dragging NOTIFY draggingChanged) |
1304 | + Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool dragMode READ dragMode WRITE setDragMode NOTIFY dragModeChanged) |
1305 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool selected READ isSelected WRITE setSelected NOTIFY selectedChanged) |
1306 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), bool selectMode READ selectMode WRITE setSelectMode NOTIFY selectModeChanged) |
1307 | Q_PRIVATE_PROPERTY(UCListItem::d_func(), UCAction *action READ action WRITE setAction NOTIFY actionChanged DESIGNABLE false) |
1308 | @@ -80,6 +82,8 @@ |
1309 | void contentMovingChanged(); |
1310 | void colorChanged(); |
1311 | void highlightColorChanged(); |
1312 | + void draggingChanged(); |
1313 | + void dragModeChanged(); |
1314 | void selectedChanged(); |
1315 | void selectModeChanged(); |
1316 | void actionChanged(); |
1317 | @@ -105,6 +109,7 @@ |
1318 | Q_PRIVATE_SLOT(d_func(), void _q_updateIndex()) |
1319 | Q_PRIVATE_SLOT(d_func(), void _q_contentMoving()) |
1320 | Q_PRIVATE_SLOT(d_func(), void _q_syncSelectMode()) |
1321 | + Q_PRIVATE_SLOT(d_func(), void _q_syncDragMode()) |
1322 | }; |
1323 | |
1324 | class UCListItemDividerPrivate; |
1325 | @@ -135,12 +140,14 @@ |
1326 | Q_DECLARE_PRIVATE(UCListItemDivider) |
1327 | }; |
1328 | |
1329 | +class UCDragEvent; |
1330 | class UCViewItemsAttachedPrivate; |
1331 | class UCViewItemsAttached : public QObject |
1332 | { |
1333 | Q_OBJECT |
1334 | Q_PROPERTY(bool selectMode READ selectMode WRITE setSelectMode NOTIFY selectModeChanged) |
1335 | Q_PROPERTY(QList<int> selectedIndices READ selectedIndices WRITE setSelectedIndices NOTIFY selectedIndicesChanged) |
1336 | + Q_PROPERTY(bool dragMode READ dragMode WRITE setDragMode NOTIFY dragModeChanged) |
1337 | public: |
1338 | explicit UCViewItemsAttached(QObject *owner); |
1339 | ~UCViewItemsAttached(); |
1340 | @@ -157,17 +164,67 @@ |
1341 | void setSelectMode(bool value); |
1342 | QList<int> selectedIndices() const; |
1343 | void setSelectedIndices(const QList<int> &list); |
1344 | + bool dragMode() const; |
1345 | + void setDragMode(bool value); |
1346 | + |
1347 | private Q_SLOTS: |
1348 | void unbindItem(); |
1349 | + void completed(); |
1350 | |
1351 | Q_SIGNALS: |
1352 | void selectModeChanged(); |
1353 | void selectedIndicesChanged(); |
1354 | + void dragModeChanged(); |
1355 | + |
1356 | + void dragUpdated(UCDragEvent *event); |
1357 | |
1358 | private: |
1359 | Q_DECLARE_PRIVATE(UCViewItemsAttached) |
1360 | - QScopedPointer<UCViewItemsAttachedPrivate> d_ptr; |
1361 | }; |
1362 | QML_DECLARE_TYPEINFO(UCViewItemsAttached, QML_HAS_ATTACHED_PROPERTIES) |
1363 | |
1364 | +class UCDragEvent : public QObject |
1365 | +{ |
1366 | + Q_OBJECT |
1367 | + Q_PROPERTY(Status status READ status) |
1368 | + Q_PROPERTY(int from READ from) |
1369 | + Q_PROPERTY(int to READ to) |
1370 | + Q_PROPERTY(int minimumIndex MEMBER m_minimum) |
1371 | + Q_PROPERTY(int maximumIndex MEMBER m_maximum) |
1372 | + Q_PROPERTY(bool accept MEMBER m_accept) |
1373 | + Q_ENUMS(Status) |
1374 | +public: |
1375 | + enum Status { |
1376 | + Started, |
1377 | + Moving, |
1378 | + Dropped |
1379 | + }; |
1380 | + |
1381 | + explicit UCDragEvent(Status status, int from, int to, int min, int max) |
1382 | + : QObject(0), m_status(status), m_from(from), m_to(to), m_minimum(min), m_maximum(max), m_accept(true) |
1383 | + {} |
1384 | + int from() const |
1385 | + { |
1386 | + return m_from; |
1387 | + } |
1388 | + int to() const |
1389 | + { |
1390 | + return m_to; |
1391 | + } |
1392 | + Status status() const |
1393 | + { |
1394 | + return m_status; |
1395 | + } |
1396 | + |
1397 | +private: |
1398 | + Status m_status; |
1399 | + int m_from; |
1400 | + int m_to; |
1401 | + int m_minimum; |
1402 | + int m_maximum; |
1403 | + bool m_accept; |
1404 | + |
1405 | + friend class ListItemDragArea; |
1406 | +}; |
1407 | + |
1408 | #endif // UCLISTITEM_H |
1409 | |
1410 | === modified file 'modules/Ubuntu/Components/plugin/uclistitem_p.h' |
1411 | --- modules/Ubuntu/Components/plugin/uclistitem_p.h 2015-02-18 10:57:06 +0000 |
1412 | +++ modules/Ubuntu/Components/plugin/uclistitem_p.h 2015-02-27 07:56:03 +0000 |
1413 | @@ -33,7 +33,7 @@ |
1414 | class UCListItemDivider; |
1415 | class UCListItemActions; |
1416 | class UCListItemStyle; |
1417 | -class UCActionPanel; |
1418 | +class ListItemDragHandler; |
1419 | class UCListItemPrivate : public UCStyledItemBasePrivate |
1420 | { |
1421 | Q_DECLARE_PUBLIC(UCListItem) |
1422 | @@ -58,6 +58,7 @@ |
1423 | void _q_updateIndex(); |
1424 | void _q_contentMoving(); |
1425 | void _q_syncSelectMode(); |
1426 | + void _q_syncDragMode(); |
1427 | int index(); |
1428 | bool canHighlight(QMouseEvent *event); |
1429 | void setHighlighted(bool pressed); |
1430 | @@ -83,6 +84,7 @@ |
1431 | QPointer<QQuickItem> countOwner; |
1432 | QPointer<QQuickFlickable> flickable; |
1433 | QPointer<UCViewItemsAttached> parentAttached; |
1434 | + QPointer<ListItemDragHandler> dragHandler; |
1435 | QQuickItem *contentItem; |
1436 | UCListItemDivider *divider; |
1437 | UCListItemActions *leadingActions; |
1438 | @@ -104,6 +106,9 @@ |
1439 | void resetStyle(); |
1440 | void initStyleItem(bool withAnimatedPanels = true); |
1441 | QQuickItem *styleInstance() const; |
1442 | + bool dragging(); |
1443 | + bool dragMode(); |
1444 | + void setDragMode(bool draggable); |
1445 | bool isSelected(); |
1446 | void setSelected(bool value); |
1447 | bool selectMode(); |
1448 | @@ -113,11 +118,12 @@ |
1449 | }; |
1450 | |
1451 | class PropertyChange; |
1452 | -class UCViewItemsAttachedPrivate |
1453 | +class ListItemDragArea; |
1454 | +class UCViewItemsAttachedPrivate : public QObjectPrivate |
1455 | { |
1456 | Q_DECLARE_PUBLIC(UCViewItemsAttached) |
1457 | public: |
1458 | - UCViewItemsAttachedPrivate(UCViewItemsAttached *qq); |
1459 | + UCViewItemsAttachedPrivate(); |
1460 | ~UCViewItemsAttachedPrivate(); |
1461 | |
1462 | static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item) |
1463 | @@ -132,10 +138,17 @@ |
1464 | bool addSelectedItem(UCListItem *item); |
1465 | bool removeSelectedItem(UCListItem *item); |
1466 | bool isItemSelected(UCListItem *item); |
1467 | + void enterDragMode(); |
1468 | + void leaveDragMode(); |
1469 | + bool isDragUpdatedConnected(); |
1470 | + void updateSelectedIndices(int fromIndex, int toIndex); |
1471 | |
1472 | - UCViewItemsAttached *q_ptr; |
1473 | + QQuickFlickable *listView; |
1474 | + ListItemDragArea *dragArea; |
1475 | bool globalDisabled:1; |
1476 | bool selectable:1; |
1477 | + bool draggable:1; |
1478 | + bool ready:1; |
1479 | QSet<int> selectedList; |
1480 | QList< QPointer<QQuickFlickable> > flickables; |
1481 | QList< PropertyChange* > changes; |
1482 | |
1483 | === modified file 'modules/Ubuntu/Components/plugin/uclistitemstyle.cpp' |
1484 | --- modules/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-02-13 11:51:50 +0000 |
1485 | +++ modules/Ubuntu/Components/plugin/uclistitemstyle.cpp 2015-02-27 07:56:03 +0000 |
1486 | @@ -38,6 +38,8 @@ |
1487 | UCListItemStyle::UCListItemStyle(QQuickItem *parent) |
1488 | : QQuickItem(parent) |
1489 | , m_snapAnimation(0) |
1490 | + , m_dropAnimation(0) |
1491 | + , m_dragPanel(0) |
1492 | , m_animatePanels(true) |
1493 | { |
1494 | } |
1495 | @@ -140,3 +142,16 @@ |
1496 | m_animatePanels = animate; |
1497 | Q_EMIT animatePanelsChanged(); |
1498 | } |
1499 | + |
1500 | +/*! |
1501 | + * \qmlproperty PropertyAnimation ListItemStyle::dropAnimation |
1502 | + * The property holds the animation executed on ListItem dropping. |
1503 | + */ |
1504 | + |
1505 | +/*! |
1506 | + * \qmlproperty Item ListItemStyle::dragPanel |
1507 | + * The property holds the item visualizing the drag handler. ListItem's dragging |
1508 | + * mechanism uses this property to detect the area the dragging can be initiated |
1509 | + * from. If not set, the ListItem will assume the dragging can be initiated from |
1510 | + * the entire area of the ListItem. |
1511 | + */ |
1512 | |
1513 | === modified file 'modules/Ubuntu/Components/plugin/uclistitemstyle.h' |
1514 | --- modules/Ubuntu/Components/plugin/uclistitemstyle.h 2015-02-13 12:20:27 +0000 |
1515 | +++ modules/Ubuntu/Components/plugin/uclistitemstyle.h 2015-02-27 07:56:03 +0000 |
1516 | @@ -59,12 +59,15 @@ |
1517 | |
1518 | class QQmlComponent; |
1519 | class QQuickAbstractAnimation; |
1520 | +class QQuickPropertyAnimation; |
1521 | class QQuickBehavior; |
1522 | class UCListItemStyle : public QQuickItem |
1523 | { |
1524 | Q_OBJECT |
1525 | Q_PROPERTY(QQuickAbstractAnimation *snapAnimation MEMBER m_snapAnimation NOTIFY snapAnimationChanged) |
1526 | + Q_PROPERTY(QQuickPropertyAnimation *dropAnimation MEMBER m_dropAnimation NOTIFY dropAnimationChanged) |
1527 | Q_PROPERTY(bool animatePanels READ animatePanels NOTIFY animatePanelsChanged) |
1528 | + Q_PROPERTY(QQuickItem *dragPanel MEMBER m_dragPanel NOTIFY dragPanelChanged) |
1529 | public: |
1530 | explicit UCListItemStyle(QQuickItem *parent = 0); |
1531 | |
1532 | @@ -75,7 +78,9 @@ |
1533 | |
1534 | Q_SIGNALS: |
1535 | void snapAnimationChanged(); |
1536 | + void dropAnimationChanged(); |
1537 | void animatePanelsChanged(); |
1538 | + void dragPanelChanged(); |
1539 | |
1540 | public Q_SLOTS: |
1541 | void swipeEvent(UCSwipeEvent *event); |
1542 | @@ -89,11 +94,14 @@ |
1543 | QMetaMethod m_swipeEvent; |
1544 | QMetaMethod m_rebound; |
1545 | QQuickAbstractAnimation *m_snapAnimation; |
1546 | + QQuickPropertyAnimation *m_dropAnimation; |
1547 | + QQuickItem *m_dragPanel; |
1548 | bool m_animatePanels:1; |
1549 | |
1550 | friend class UCListItemPrivate; |
1551 | - friend class UCActionPanel; |
1552 | - friend class ListItemAnimator; |
1553 | + friend class ListItemDragArea; |
1554 | + friend class ListItemDragHandler; |
1555 | + friend class UCViewItemsAttachedPrivate; |
1556 | }; |
1557 | |
1558 | #endif // UCLISTITEMSTYLE_H |
1559 | |
1560 | === modified file 'modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp' |
1561 | --- modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-02-13 12:20:27 +0000 |
1562 | +++ modules/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-02-27 07:56:03 +0000 |
1563 | @@ -19,8 +19,79 @@ |
1564 | #include "uclistitem.h" |
1565 | #include "uclistitem_p.h" |
1566 | #include "propertychange_p.h" |
1567 | +#include "quickutils.h" |
1568 | +#include "i18n.h" |
1569 | #include "uclistitemstyle.h" |
1570 | +#include "privates/listitemdragarea.h" |
1571 | #include <QtQuick/private/qquickflickable_p.h> |
1572 | +#include <QtQml/private/qqmlcomponentattached_p.h> |
1573 | +#include <QtQml/QQmlInfo> |
1574 | +#include <QtCore/QAbstractItemModel> |
1575 | +#include <QtQml/private/qqmlobjectmodel_p.h> |
1576 | +#include <QtQml/private/qqmldelegatemodel_p.h> |
1577 | + |
1578 | +/*! |
1579 | + * \qmltype ListItemDrag |
1580 | + * \inqmlmodule Ubuntu.Components 1.2 |
1581 | + * \ingroup unstable-ubuntu-listitems |
1582 | + * \since Ubuntu.Components 1.2 |
1583 | + * \brief Provides information about a ListItem drag event. |
1584 | + * |
1585 | + * The object cannot be instantiated and it is passed as parameter to \l ViewItems::dragUpdated |
1586 | + * attached signal. Developer can decide whether to accept or restrict the dragging |
1587 | + * event based on the input provided by this event. |
1588 | + * |
1589 | + * The direction of the drag can be found via the \l status property and the |
1590 | + * source and destination the drag can be applied via \l from and \l to properties. |
1591 | + * The allowed directions can be configured through \l minimumIndex and \l maximumIndex |
1592 | + * properties, and the event acceptance through \l accept property. If the event is not |
1593 | + * accepted, the drag action will be considered as cancelled. |
1594 | + */ |
1595 | + |
1596 | +/*! |
1597 | + * \qmlproperty enum ListItemDrag::status |
1598 | + * \readonly |
1599 | + * The property provides information about the status of the drag. Its value can |
1600 | + * be one of the following: |
1601 | + * \list |
1602 | + * \li \b ListItemDrag.Started - indicates that the dragging is about to start, |
1603 | + * giving opportunities to define restrictions on the dragging indexes, |
1604 | + * like \l minimumIndex, \l maximumIndex |
1605 | + * \li \b ListItemDrag.Moving - the dragged item is moved upwards or downwards |
1606 | + * in the ListItem |
1607 | + * \li \b ListItemDrag.Dropped - indicates that the drag event is finished, and |
1608 | + * the dragged item is about to be dropped to the given position. |
1609 | + * \endlist |
1610 | + */ |
1611 | + |
1612 | +/*! |
1613 | + * \qmlproperty int ListItemDrag::from |
1614 | + * \readonly |
1615 | + * Specifies the source index the ListItem is dragged from. |
1616 | + */ |
1617 | +/*! |
1618 | + * \qmlproperty int ListItemDrag::to |
1619 | + * \readonly |
1620 | + * |
1621 | + * Specifies the index the ListItem is dragged to or dropped. |
1622 | + */ |
1623 | + |
1624 | +/*! |
1625 | + * \qmlproperty int ListItemDrag::maximumIndex |
1626 | + */ |
1627 | +/*! |
1628 | + * \qmlproperty int ListItemDrag::minimumIndex |
1629 | + * These properties configure the minimum and maximum indexes the item can be |
1630 | + * dragged. The properties can be set in \l ViewItems::dragUpdated signal. |
1631 | + * A negative value means no restriction defined on the dragging interval side. |
1632 | + */ |
1633 | + |
1634 | +/*! |
1635 | + * \qmlproperty bool ListItemDrag::accept |
1636 | + * The property can be used to dismiss the event. By default its value is true, |
1637 | + * meaning the drag event is accepted. The value of false blocks the drag event |
1638 | + * to be handled by the ListItem's dragging mechanism. |
1639 | + */ |
1640 | |
1641 | /* |
1642 | * The properties are attached to the ListItem's parent item or to its closest |
1643 | @@ -29,10 +100,14 @@ |
1644 | * in this way the controlling of the interactive flag of the Flickable and all |
1645 | * its ascendant Flickables. |
1646 | */ |
1647 | -UCViewItemsAttachedPrivate::UCViewItemsAttachedPrivate(UCViewItemsAttached *qq) |
1648 | - : q_ptr(qq) |
1649 | +UCViewItemsAttachedPrivate::UCViewItemsAttachedPrivate() |
1650 | + : QObjectPrivate() |
1651 | + , listView(0) |
1652 | + , dragArea(0) |
1653 | , globalDisabled(false) |
1654 | , selectable(false) |
1655 | + , draggable(false) |
1656 | + , ready(false) |
1657 | { |
1658 | } |
1659 | |
1660 | @@ -47,9 +122,12 @@ |
1661 | { |
1662 | Q_Q(UCViewItemsAttached); |
1663 | Q_FOREACH(const QPointer<QQuickFlickable> &flickable, flickables) { |
1664 | - if (flickable.data()) |
1665 | - QObject::disconnect(flickable.data(), &QQuickFlickable::movementStarted, |
1666 | - q, &UCViewItemsAttached::unbindItem); |
1667 | + if (flickable.data()) { |
1668 | + QObject::disconnect(flickable.data(), &QQuickFlickable::movementStarted, |
1669 | + q, &UCViewItemsAttached::unbindItem); |
1670 | + QObject::disconnect(flickable.data(), &QQuickFlickable::flickStarted, |
1671 | + q, &UCViewItemsAttached::unbindItem); |
1672 | + } |
1673 | } |
1674 | flickables.clear(); |
1675 | } |
1676 | @@ -68,6 +146,8 @@ |
1677 | if (flickable) { |
1678 | QObject::connect(flickable, &QQuickFlickable::movementStarted, |
1679 | q, &UCViewItemsAttached::unbindItem); |
1680 | + QObject::connect(flickable, &QQuickFlickable::flickStarted, |
1681 | + q, &UCViewItemsAttached::unbindItem); |
1682 | flickables << flickable; |
1683 | } |
1684 | item = item->parentItem(); |
1685 | @@ -109,13 +189,18 @@ |
1686 | * \since Ubuntu.Components 1.2 |
1687 | * \brief A set of properties attached to the ListItem's parent item or ListView. |
1688 | * |
1689 | - * These properties are attached to the parent item of the ListItem, or to |
1690 | - * ListView, when the component is used as delegate. |
1691 | + * These properties are automatically attached to the parent item of the ListItem, |
1692 | + * or to ListView, when the component is used as delegate. |
1693 | */ |
1694 | UCViewItemsAttached::UCViewItemsAttached(QObject *owner) |
1695 | - : QObject(owner) |
1696 | - , d_ptr(new UCViewItemsAttachedPrivate(this)) |
1697 | + : QObject(*(new UCViewItemsAttachedPrivate()), owner) |
1698 | { |
1699 | + if (owner->inherits("QQuickListView")) { |
1700 | + d_func()->listView = static_cast<QQuickFlickable*>(owner); |
1701 | + } |
1702 | + // listen readyness |
1703 | + QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(owner); |
1704 | + connect(attached, &QQmlComponentAttached::completed, this, &UCViewItemsAttached::completed); |
1705 | } |
1706 | |
1707 | UCViewItemsAttached::~UCViewItemsAttached() |
1708 | @@ -214,6 +299,18 @@ |
1709 | d->clearFlickablesList(); |
1710 | } |
1711 | |
1712 | +// reports completion, and in case the dragMode is turned on, enters drag mode |
1713 | +void UCViewItemsAttached::completed() |
1714 | +{ |
1715 | + Q_D(UCViewItemsAttached); |
1716 | + d->ready = true; |
1717 | + if (d->draggable) { |
1718 | + d->enterDragMode(); |
1719 | + } else { |
1720 | + d->leaveDragMode(); |
1721 | + } |
1722 | +} |
1723 | + |
1724 | /*! |
1725 | * \qmlattachedproperty bool ViewItems::selectMode |
1726 | * The property drives whether list items are selectable or not. |
1727 | @@ -265,7 +362,7 @@ |
1728 | int index = UCListItemPrivate::get(item)->index(); |
1729 | if (!selectedList.contains(index)) { |
1730 | selectedList.insert(index); |
1731 | - Q_EMIT q_ptr->selectedIndicesChanged(); |
1732 | + Q_EMIT q_func()->selectedIndicesChanged(); |
1733 | return true; |
1734 | } |
1735 | return false; |
1736 | @@ -273,7 +370,7 @@ |
1737 | bool UCViewItemsAttachedPrivate::removeSelectedItem(UCListItem *item) |
1738 | { |
1739 | if (selectedList.remove(UCListItemPrivate::get(item)->index()) > 0) { |
1740 | - Q_EMIT q_ptr->selectedIndicesChanged(); |
1741 | + Q_EMIT q_func()->selectedIndicesChanged(); |
1742 | return true; |
1743 | } |
1744 | return false; |
1745 | @@ -283,3 +380,234 @@ |
1746 | { |
1747 | return selectedList.contains(UCListItemPrivate::get(item)->index()); |
1748 | } |
1749 | + |
1750 | +/*! |
1751 | + * \qmlattachedproperty bool ViewItems::dragMode |
1752 | + * The property drives the dragging mode of the ListItems within a ListView. It |
1753 | + * has no effect on any other parent of the ListItem. |
1754 | + * |
1755 | + * When set, ListItem content will be disabled and a panel will be shown enabling |
1756 | + * the dragging mode. The items can be dragged by dragging this handler only. |
1757 | + * The feature can be activated same time with \l ListItem::selectMode. |
1758 | + * |
1759 | + * The panel is configured by the style. |
1760 | + * |
1761 | + * \sa ListItemStyle, dragUpdated |
1762 | + */ |
1763 | + |
1764 | +/*! |
1765 | + * \qmlattachedsignal ViewItems::dragUpdated(ListItemDrag event) |
1766 | + * The signal is emitted whenever a dragging related event occurrs. The \b event.status |
1767 | + * specifies the dragging event type. Depending on the type, the ListItemDrag event |
1768 | + * properties will have the following meaning: |
1769 | + * \table |
1770 | + * \header |
1771 | + * \li status |
1772 | + * \li from |
1773 | + * \li to |
1774 | + * \li minimumIndex |
1775 | + * \li maximumIndex |
1776 | + * \row |
1777 | + * \li Started |
1778 | + * \li the index of the item to be dragged |
1779 | + * \li -1 |
1780 | + * \li default (-1), can be changed to restrict moves |
1781 | + * \li default (-1), can be changed to restrict moves |
1782 | + * \row |
1783 | + * \li Moving |
1784 | + * \li source index from where the item dragged from |
1785 | + * \li destination index where the item can be dragged to |
1786 | + * \li the same value set at \e Started, can be changed |
1787 | + * \li the same value set at \e Started, can be changed |
1788 | + * \row |
1789 | + * \li Dropped |
1790 | + * \li source index from where the item dragged from |
1791 | + * \li destination index where the item can be dragged to |
1792 | + * \li the value set at \e Started/Moving, changes are omitted |
1793 | + * \li the value set at \e Started/Moving, changes are omitted |
1794 | + * \endtable |
1795 | + * Implementations \b {must move the model data} in order to re-order the ListView |
1796 | + * content. If the move is not acceptable, it must be cancelled by setting |
1797 | + * \b event.accept to \e false, in which case the dragged index (\b from) will |
1798 | + * not be updated and next time the signal is emitted will be the same. |
1799 | + * |
1800 | + * An example implementation of a live dragging with restrictions: |
1801 | + * \qml |
1802 | + * import QtQuick 2.4 |
1803 | + * import Ubuntu.Components 1.2 |
1804 | + * |
1805 | + * ListView { |
1806 | + * width: units.gu(40) |
1807 | + * height: units.gu(40) |
1808 | + * model: ListModel { |
1809 | + * // initiate with random data |
1810 | + * } |
1811 | + * delegate: ListItem { |
1812 | + * // content |
1813 | + * } |
1814 | + * |
1815 | + * ViewItems.dragMode: true |
1816 | + * ViewItems.onDragUpdated: { |
1817 | + * if (event.status == ListViewDrag.Started) { |
1818 | + * if (event.from < 5) { |
1819 | + * // deny dragging on the first 5 element |
1820 | + * event.accept = false; |
1821 | + * } else if (event.from >= 5 && event.from <= 10 && |
1822 | + * event.to >= 5 && event.to <= 10) { |
1823 | + * // specify the interval |
1824 | + * event.minimumIndex = 5; |
1825 | + * event.maximumIndex = 10; |
1826 | + * } else if (event.from > 10) { |
1827 | + * // prevent dragging to the first 11 items area |
1828 | + * event.minimumIndex = 11; |
1829 | + * } |
1830 | + * } else { |
1831 | + * model.move(event.from, event.to, 1); |
1832 | + * } |
1833 | + * } |
1834 | + * } |
1835 | + * \endqml |
1836 | + * |
1837 | + * A drag'n'drop implementation might be required when model changes are too |
1838 | + * expensive, and continuously updating while dragging would cause lot of traffic. |
1839 | + * The following example illustrates how to implement such a scenario: |
1840 | + * \qml |
1841 | + * import QtQuick 2.4 |
1842 | + * import Ubuntu.Components 1.2 |
1843 | + * |
1844 | + * ListView { |
1845 | + * width: units.gu(40) |
1846 | + * height: units.gu(40) |
1847 | + * model: ListModel { |
1848 | + * // initiate with random data |
1849 | + * } |
1850 | + * delegate: ListItem { |
1851 | + * // content |
1852 | + * } |
1853 | + * |
1854 | + * ViewItems.dragMode: true |
1855 | + * ViewItems.onDragUpdated: { |
1856 | + * if (event.direction == ListItemDrag.Dropped) { |
1857 | + * // this is the last event, so drop the item |
1858 | + * model.move(event.from, event.to, 1); |
1859 | + * } else if (event.direction != ListItemDrag.Started) { |
1860 | + * // do not accept the moving events, so drag.from will |
1861 | + * // always contain the original drag index |
1862 | + * event.accept = false; |
1863 | + * } |
1864 | + * } |
1865 | + * } |
1866 | + * \endqml |
1867 | + * |
1868 | + * \note Do not forget to set \b{event.accept} to false in \b dragUpdated in |
1869 | + * case the drag event handling is not accepted, otherwise the system will not |
1870 | + * know whether the move has been performed or not, and selected indexes will |
1871 | + * not be synchronized properly. |
1872 | + */ |
1873 | +bool UCViewItemsAttached::dragMode() const |
1874 | +{ |
1875 | + Q_D(const UCViewItemsAttached); |
1876 | + return d->draggable; |
1877 | +} |
1878 | +void UCViewItemsAttached::setDragMode(bool value) |
1879 | +{ |
1880 | + Q_D(UCViewItemsAttached); |
1881 | + if (d->draggable == value) { |
1882 | + return; |
1883 | + } |
1884 | + if (value) { |
1885 | + /* |
1886 | + * The dragging works only if the ListItem is used inside a ListView, and the |
1887 | + * model used is a list, a ListModel or a derivate of QAbstractItemModel. Do |
1888 | + * not enable dragging if these conditions are not fulfilled. |
1889 | + */ |
1890 | + if (!d->listView) { |
1891 | + qmlInfo(parent()) << UbuntuI18n::instance().tr("Dragging mode requires ListView"); |
1892 | + return; |
1893 | + } |
1894 | + QVariant model = d->listView->property("model"); |
1895 | + // warn if the model is anything else but Instance model (ObjectModel or DelegateModel) |
1896 | + // or a derivate of QAbstractItemModel |
1897 | + QString warning = UbuntuI18n::instance().tr("Dragging is only supported when using a QAbstractItemModel, ListModel or list."); |
1898 | + if (model.isValid() && !model.value<QQmlInstanceModel*>() && !model.value<QAbstractItemModel*>() && !(model.type() == QVariant::List)) { |
1899 | + qmlInfo(parent()) << warning; |
1900 | + } |
1901 | + // if we have a QQmlDelegateModel we must also check the model property of it |
1902 | + QQmlDelegateModel *delegateModel = model.value<QQmlDelegateModel*>(); |
1903 | + if (delegateModel && delegateModel->model().isValid() && |
1904 | + !delegateModel->model().value<QAbstractItemModel*>() && |
1905 | + !(delegateModel->model().type() == QVariant::List)) { |
1906 | + qmlInfo(parent()) << warning; |
1907 | + } |
1908 | + } |
1909 | + d->draggable = value; |
1910 | + if (d->draggable) { |
1911 | + d->enterDragMode(); |
1912 | + } else { |
1913 | + d->leaveDragMode(); |
1914 | + } |
1915 | + Q_EMIT dragModeChanged(); |
1916 | +} |
1917 | + |
1918 | +void UCViewItemsAttachedPrivate::enterDragMode() |
1919 | +{ |
1920 | + if (dragArea) { |
1921 | + dragArea->reset(); |
1922 | + return; |
1923 | + } |
1924 | + dragArea = new ListItemDragArea(listView); |
1925 | + dragArea->init(); |
1926 | +} |
1927 | + |
1928 | +void UCViewItemsAttachedPrivate::leaveDragMode() |
1929 | +{ |
1930 | + if (dragArea) { |
1931 | + dragArea->setEnabled(false); |
1932 | + } |
1933 | +} |
1934 | + |
1935 | +// returns true when the dragUpdated signal handler is implemented or a function is connected to it |
1936 | +bool UCViewItemsAttachedPrivate::isDragUpdatedConnected() |
1937 | +{ |
1938 | + Q_Q(UCViewItemsAttached); |
1939 | + static QMetaMethod method = QMetaMethod::fromSignal(&UCViewItemsAttached::dragUpdated); |
1940 | + static int signalIdx = QMetaObjectPrivate::signalIndex(method); |
1941 | + return QObjectPrivate::get(q)->isSignalConnected(signalIdx); |
1942 | +} |
1943 | + |
1944 | +// updates the selected indices list in ViewAttached which is changed due to dragging |
1945 | +void UCViewItemsAttachedPrivate::updateSelectedIndices(int fromIndex, int toIndex) |
1946 | +{ |
1947 | + if (selectedList.count() == listView->property("count").toInt()) { |
1948 | + // all indices selected, no need to reorder |
1949 | + return; |
1950 | + } |
1951 | + |
1952 | + Q_Q(UCViewItemsAttached); |
1953 | + bool isFromSelected = selectedList.contains(fromIndex); |
1954 | + if (isFromSelected) { |
1955 | + selectedList.remove(fromIndex); |
1956 | + Q_EMIT q->selectedIndicesChanged(); |
1957 | + } |
1958 | + // direction is -1 (forwards) or 1 (backwards) |
1959 | + int direction = (fromIndex < toIndex) ? -1 : 1; |
1960 | + int i = (direction < 0) ? fromIndex + 1 : fromIndex - 1; |
1961 | + // loop thru the selectedIndices and fix all indices |
1962 | + while (1) { |
1963 | + if (((direction < 0) && (i > toIndex)) || |
1964 | + ((direction > 0) && (i < toIndex))) { |
1965 | + break; |
1966 | + } |
1967 | + |
1968 | + if (selectedList.contains(i)) { |
1969 | + selectedList.remove(i); |
1970 | + selectedList.insert(i + direction); |
1971 | + Q_EMIT q->selectedIndicesChanged(); |
1972 | + } |
1973 | + i -= direction; |
1974 | + } |
1975 | + if (isFromSelected) { |
1976 | + selectedList.insert(toIndex); |
1977 | + Q_EMIT q->selectedIndicesChanged(); |
1978 | + } |
1979 | +} |
1980 | |
1981 | === modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml' |
1982 | --- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-10-29 09:25:55 +0000 |
1983 | +++ modules/Ubuntu/Test/UbuntuTestCase.qml 2015-02-27 07:56:03 +0000 |
1984 | @@ -230,4 +230,11 @@ |
1985 | keyClick(string[i]); |
1986 | } |
1987 | } |
1988 | + |
1989 | + /*! |
1990 | + Warning message formatter, uses file name, line and column numbers to build up the message. |
1991 | + */ |
1992 | + function warningFormat(line, column, message) { |
1993 | + return util.callerFile() + ":" + line + ":" + column + ": " + message; |
1994 | + } |
1995 | } |
1996 | |
1997 | === modified file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml' |
1998 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml 2015-02-12 07:31:08 +0000 |
1999 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitem.ListItemTestCase.qml 2015-02-27 07:56:03 +0000 |
2000 | @@ -22,9 +22,6 @@ |
2001 | height: units.gu(60) |
2002 | objectName: "mainView" |
2003 | |
2004 | - // make sure we're not messed up by the deprecated toolbar |
2005 | - useDeprecatedToolbar: false |
2006 | - |
2007 | Page { |
2008 | id: testPage |
2009 | objectName: "test_page" |
2010 | |
2011 | === added file 'tests/resources/listitems/ListItemDragging.qml' |
2012 | --- tests/resources/listitems/ListItemDragging.qml 1970-01-01 00:00:00 +0000 |
2013 | +++ tests/resources/listitems/ListItemDragging.qml 2015-02-27 07:56:03 +0000 |
2014 | @@ -0,0 +1,174 @@ |
2015 | +/* |
2016 | + * Copyright 2015 Canonical Ltd. |
2017 | + * |
2018 | + * This program is free software; you can redistribute it and/or modify |
2019 | + * it under the terms of the GNU Lesser General Public License as published by |
2020 | + * the Free Software Foundation; version 3. |
2021 | + * |
2022 | + * This program is distributed in the hope that it will be useful, |
2023 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2024 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2025 | + * GNU Lesser General Public License for more details. |
2026 | + * |
2027 | + * You should have received a copy of the GNU Lesser General Public License |
2028 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2029 | + */ |
2030 | + |
2031 | +import QtQuick 2.4 |
2032 | +import Ubuntu.Components 1.2 |
2033 | +import Ubuntu.Components.ListItems 1.0 |
2034 | +import QtQml.Models 2.1 |
2035 | + |
2036 | +MainView { |
2037 | + id: main |
2038 | + width: units.gu(40) |
2039 | + height: units.gu(71) |
2040 | + |
2041 | + property bool restrictDragging: true |
2042 | + |
2043 | + Action { |
2044 | + id: deleteAction |
2045 | + iconName: "delete" |
2046 | + } |
2047 | + property list<Action> contextualActions: [ |
2048 | + Action { |
2049 | + iconName: "edit" |
2050 | + }, |
2051 | + Action { |
2052 | + iconName: "share" |
2053 | + }, |
2054 | + Action { |
2055 | + iconName: "stock_website" |
2056 | + } |
2057 | + ] |
2058 | + Tabs { |
2059 | + Tab { |
2060 | + title: "Dragging with ListModel" |
2061 | + page: Page { |
2062 | + ListView { |
2063 | + onEnabledChanged: print("enabled", enabled) |
2064 | + anchors.fill: parent |
2065 | + ViewItems.selectMode: ViewItems.dragMode |
2066 | + ViewItems.onSelectedIndicesChanged: print(ViewItems.selectedIndices) |
2067 | + |
2068 | + function handleDragStarted(event) { |
2069 | + if (!restrictDragging) { |
2070 | + return; |
2071 | + } |
2072 | + |
2073 | + if (event.from < 3) { |
2074 | + // the first 3 items are not draggable |
2075 | + event.accept = false; |
2076 | + } else if (event.from >=3 && event.from <= 10) { |
2077 | + // dragging is limited between index 3 and 10 |
2078 | + event.minimumIndex = 3; |
2079 | + event.maximumIndex = 10; |
2080 | + } else { |
2081 | + // prevent dragging above index 10 |
2082 | + event.minimumIndex = 11; |
2083 | + } |
2084 | + } |
2085 | + function handleDragUpdates(event) { |
2086 | + if (restrictDragging) { |
2087 | + // deal only with indexes >= 3 |
2088 | + if (event.to >= 3 && event.to <= 10 && event.from >= 3 && event.from <= 10) { |
2089 | + // live update |
2090 | + model.move(event.from, event.to, 1); |
2091 | + } else if (event.status == ListItemDrag.Dropped) { |
2092 | + model.move(event.from, event.to, 1); |
2093 | + } else { |
2094 | + event.accept = false; |
2095 | + } |
2096 | + } else { |
2097 | + // no restrictions, perform live update |
2098 | + model.move(event.from, event.to, 1); |
2099 | + } |
2100 | + } |
2101 | + |
2102 | + ViewItems.onDragUpdated: { |
2103 | + if (event.status == ListItemDrag.Started) { |
2104 | + handleDragStarted(event); |
2105 | + } else { |
2106 | + handleDragUpdates(event); |
2107 | + } |
2108 | + } |
2109 | + |
2110 | + model: ListModel { |
2111 | + Component.onCompleted: { |
2112 | + for (var i = 0; i < 3; i++) { |
2113 | + append({label: "List item #"+i, sectionData: "Locked"}); |
2114 | + } |
2115 | + for (i = 3; i < 11; i++) { |
2116 | + append({label: "List item #"+i, sectionData: "Limited, live move"}); |
2117 | + } |
2118 | + for (i = 11; i < 25; i++) { |
2119 | + append({label: "List item #"+i, sectionData: "Unlimited, drag'n'drop"}); |
2120 | + } |
2121 | + } |
2122 | + } |
2123 | + |
2124 | + section { |
2125 | + property: "sectionData" |
2126 | + criteria: ViewSection.FullString |
2127 | + delegate: Header { |
2128 | + text: section |
2129 | + } |
2130 | + } |
2131 | + |
2132 | + moveDisplaced: Transition { |
2133 | + UbuntuNumberAnimation { |
2134 | + properties: "x,y" |
2135 | + } |
2136 | + } |
2137 | + |
2138 | + delegate: ListItem { |
2139 | + id: item |
2140 | + objectName: "ListItem-" + index |
2141 | + leadingActions: ListItemActions { |
2142 | + actions: deleteAction |
2143 | + } |
2144 | + trailingActions: ListItemActions { |
2145 | + actions: contextualActions |
2146 | + } |
2147 | + |
2148 | + Rectangle { |
2149 | + anchors.fill: parent |
2150 | + color: item.dragging ? UbuntuColors.blue : "#69aa69" |
2151 | + } |
2152 | + Label { |
2153 | + text: label + " from index #" + index |
2154 | + } |
2155 | + |
2156 | + onPressAndHold: { |
2157 | + print("entering/leaving draggable mode") |
2158 | + ListView.view.ViewItems.dragMode = !ListView.view.ViewItems.dragMode; |
2159 | + } |
2160 | + } |
2161 | + } |
2162 | + } |
2163 | + } |
2164 | + Tab { |
2165 | + title: "Using DelegateModel" |
2166 | + page: Page { |
2167 | + ListView { |
2168 | + anchors.fill: parent |
2169 | + model: DelegateModel { |
2170 | + model: ["apple", "pear", "plum", "peach", "nuts", "dates"] |
2171 | + delegate: ListItem { Label { text: modelData } onPressAndHold: dragMode = !dragMode; } |
2172 | + } |
2173 | + ViewItems.onDragUpdated: { |
2174 | + if (event.status == ListItemDrag.Moving) { |
2175 | + event.accept = false |
2176 | + } else if (event.status == ListItemDrag.Dropped) { |
2177 | + var fromData = model.model[event.from]; |
2178 | + var list = model.model; |
2179 | + list.splice(event.from, 1); |
2180 | + list.splice(event.to, 0, fromData); |
2181 | + model.model = list; |
2182 | + } |
2183 | + } |
2184 | + } |
2185 | + } |
2186 | + } |
2187 | + } |
2188 | +} |
2189 | |
2190 | === modified file 'tests/resources/listitems/ListItemTest.qml' |
2191 | --- tests/resources/listitems/ListItemTest.qml 2015-02-12 14:35:51 +0000 |
2192 | +++ tests/resources/listitems/ListItemTest.qml 2015-02-27 07:56:03 +0000 |
2193 | @@ -22,7 +22,6 @@ |
2194 | id: main |
2195 | width: units.gu(50) |
2196 | height: units.gu(105) |
2197 | - useDeprecatedToolbar: false |
2198 | |
2199 | property bool override: false |
2200 | |
2201 | |
2202 | === modified file 'tests/unit_x11/tst_components/tst_listitem.qml' |
2203 | --- tests/unit_x11/tst_components/tst_listitem.qml 2015-02-19 10:10:53 +0000 |
2204 | +++ tests/unit_x11/tst_components/tst_listitem.qml 2015-02-27 07:56:03 +0000 |
2205 | @@ -1,5 +1,5 @@ |
2206 | /* |
2207 | - * Copyright 2014 Canonical Ltd. |
2208 | + * Copyright 2014-2015 Canonical Ltd. |
2209 | * |
2210 | * This program is free software; you can redistribute it and/or modify |
2211 | * it under the terms of the GNU Lesser General Public License as published by |
2212 | @@ -14,11 +14,12 @@ |
2213 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2214 | */ |
2215 | |
2216 | -import QtQuick 2.0 |
2217 | +import QtQuick 2.4 |
2218 | import QtTest 1.0 |
2219 | import Ubuntu.Test 1.0 |
2220 | import Ubuntu.Components 1.2 |
2221 | import Ubuntu.Components.Styles 1.2 |
2222 | +import QtQml.Models 2.1 |
2223 | |
2224 | Item { |
2225 | id: main |
2226 | @@ -56,6 +57,16 @@ |
2227 | ListItemActions { |
2228 | id: actionsDefault |
2229 | } |
2230 | + ListModel { |
2231 | + id: objectModel |
2232 | + function reset() { |
2233 | + clear(); |
2234 | + for (var i = 0; i < 25; i++) { |
2235 | + append({data: i}); |
2236 | + } |
2237 | + } |
2238 | + Component.onCompleted: reset() |
2239 | + } |
2240 | |
2241 | Component { |
2242 | id: customDelegate |
2243 | @@ -107,23 +118,22 @@ |
2244 | width: parent.width |
2245 | height: units.gu(28) |
2246 | clip: true |
2247 | - model: 10 |
2248 | + model: objectModel |
2249 | ViewItems.selectMode: false |
2250 | delegate: ListItem { |
2251 | objectName: "listItem" + index |
2252 | color: "lightgray" |
2253 | - width: parent.width |
2254 | leadingActions: leading |
2255 | trailingActions: trailing |
2256 | Label { |
2257 | - text: "Data " + index |
2258 | + text: "Data: " + modelData + " @" + index |
2259 | } |
2260 | } |
2261 | } |
2262 | Flickable { |
2263 | id: testFlickable |
2264 | width: parent.width |
2265 | - height: units.gu(28) |
2266 | + height: units.gu(21) |
2267 | ListView { |
2268 | id: nestedListView |
2269 | width: parent.width |
2270 | @@ -209,12 +219,60 @@ |
2271 | flick(item, x, y, dx, dy, 0, 0, undefined, undefined, 100); |
2272 | } |
2273 | |
2274 | + SignalSpy { |
2275 | + id: dropSpy |
2276 | + signalName: "stopped" |
2277 | + } |
2278 | + |
2279 | + function toggleDragMode(view, enabled) { |
2280 | + // use the topmost listItem to wait for rendering completion |
2281 | + view.positionViewAtBeginning(); |
2282 | + var listItem = findChild(view, "listItem0"); |
2283 | + verify(listItem); |
2284 | + view.ViewItems.dragMode = enabled; |
2285 | + // waitForRendering aint seems to be reliable here, so we wait ~400 msecs |
2286 | + wait(400); |
2287 | + } |
2288 | + |
2289 | + function drag(view, from, to) { |
2290 | + var dragArea = findChild(view, "drag_area"); |
2291 | + verify(dragArea, "Cannot locate drag area"); |
2292 | + |
2293 | + // grab the source item |
2294 | + view.positionViewAtBeginning(from,ListView.Beginning); |
2295 | + var panel = findChild(view, "drag_panel" + from); |
2296 | + verify(panel, "Cannot locate source panel"); |
2297 | + var dragPos = dragArea.mapFromItem(panel, centerOf(panel).x, centerOf(panel).y); |
2298 | + // move the mouse |
2299 | + var dy = Math.abs(to - from) * panel.height + units.gu(1); |
2300 | + dy *= (to > from) ? 1 : -1; |
2301 | + mousePress(dragArea, dragPos.x, dragPos.y); |
2302 | + wait(100); |
2303 | + var draggedItem = findChild(view.contentItem, "DraggedListItem"); |
2304 | + if (draggedItem) { |
2305 | + dropSpy.target = draggedItem.__styleInstance.dropAnimation; |
2306 | + } |
2307 | + // use 10 steps to be sure the move is properly detected by the drag area |
2308 | + mouseMoveSlowly(dragArea, dragPos.x, dragPos.y, 0, dy, 10, 100); |
2309 | + // drop it, needs two mouse releases, this generates the Drop event also |
2310 | + mouseRelease(dragArea, dragPos.x, dragPos.y + dy); |
2311 | + // needs one more mouse release |
2312 | + mouseRelease(dragArea, dragPos.x, dragPos.y + dy); |
2313 | + if (dropSpy.target) { |
2314 | + dropSpy.wait(); |
2315 | + } else { |
2316 | + // draggedItem cannot be found, we might be trying to drag a restricted item |
2317 | + wait(200); |
2318 | + } |
2319 | + } |
2320 | + |
2321 | function initTestCase() { |
2322 | TestExtras.registerTouchDevice(); |
2323 | waitForRendering(main); |
2324 | } |
2325 | |
2326 | function cleanup() { |
2327 | + listView.model = objectModel; |
2328 | testItem.action = null; |
2329 | testItem.contentItem.anchors.margins = 0; |
2330 | testItem.selected = false; |
2331 | @@ -231,6 +289,7 @@ |
2332 | interactiveSpy.clear(); |
2333 | listView.interactive = true; |
2334 | listView.ViewItems.selectMode = false; |
2335 | + listView.ViewItems.dragMode = false; |
2336 | // make sure we collapse |
2337 | mouseClick(defaults, 0, 0) |
2338 | movingSpy.target = null; |
2339 | @@ -265,6 +324,7 @@ |
2340 | compare(defaults.selectMode, false, "Not selectable by default"); |
2341 | compare(testColumn.ViewItems.selectMode, false, "The parent attached property is not selectable by default"); |
2342 | compare(testColumn.ViewItems.selectedIndices.length, 0, "No item is selected by default"); |
2343 | + compare(listView.ViewItems.dragMode, false, "Drag mode is off on ListView"); |
2344 | |
2345 | compare(actionsDefault.delegate, null, "ListItemActions has no delegate set by default."); |
2346 | compare(actionsDefault.actions.length, 0, "ListItemActions has no actions set."); |
2347 | @@ -914,5 +974,235 @@ |
2348 | wait(400); |
2349 | verify(panel, "Selection panel not found, wrong attached property target?"); |
2350 | } |
2351 | + |
2352 | + function test_dragmode_availability_data() { |
2353 | + return [ |
2354 | + {tag: "Attached to Column", item: testColumn, lookupOn: testItem, xfail: true}, |
2355 | + {tag: "Attached to ListView", item: listView, lookupOn: findChild(listView, "listItem0"), xfail: false}, |
2356 | + ]; |
2357 | + } |
2358 | + function test_dragmode_availability(data) { |
2359 | + if (data.xfail) { |
2360 | + ignoreWarning(warningFormat(80, 5, "QML Column: Dragging mode requires ListView")); |
2361 | + } |
2362 | + data.item.ViewItems.dragMode = true; |
2363 | + wait(400); |
2364 | + var panel = findChild(data.lookupOn, "drag_panel0"); |
2365 | + if (data.xfail) { |
2366 | + expectFailContinue(data.tag, "There should be no drag handler shown!") |
2367 | + } |
2368 | + verify(panel, "No drag handler found!"); |
2369 | + } |
2370 | + |
2371 | + function test_drag_data() { |
2372 | + return [ |
2373 | + {tag: "Live 0->1 OK", live: true, from: 0, to: 1, count: 1, accept: true, indices:[1,0,2,3,4]}, |
2374 | + {tag: "Live 0->2 OK", live: true, from: 0, to: 2, count: 2, accept: true, indices:[1,2,0,3,4]}, |
2375 | + {tag: "Live 0->3 OK", live: true, from: 0, to: 3, count: 3, accept: true, indices:[1,2,3,0,4]}, |
2376 | + {tag: "Live 3->0 OK", live: true, from: 3, to: 0, count: 3, accept: true, indices:[3,0,1,2,4]}, |
2377 | + // do not accept moves |
2378 | + {tag: "Live 0->1 NOK", live: true, from: 0, to: 1, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2379 | + {tag: "Live 0->2 NOK", live: true, from: 0, to: 2, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2380 | + {tag: "Live 0->3 NOK", live: true, from: 0, to: 3, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2381 | + {tag: "Live 3->0 NOK", live: true, from: 3, to: 0, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2382 | + |
2383 | + // non-live updates |
2384 | + {tag: "Drop 0->1 OK", live: false, from: 0, to: 1, count: 1, accept: true, indices:[1,0,2,3,4]}, |
2385 | + {tag: "Drop 0->2 OK", live: false, from: 0, to: 2, count: 1, accept: true, indices:[1,2,0,3,4]}, |
2386 | + {tag: "Drop 0->3 OK", live: false, from: 0, to: 3, count: 1, accept: true, indices:[1,2,3,0,4]}, |
2387 | + {tag: "Drop 3->0 OK", live: false, from: 3, to: 0, count: 1, accept: true, indices:[3,0,1,2,4]}, |
2388 | + // do not accept moves |
2389 | + {tag: "Drop 0->1 NOK", live: false, from: 0, to: 1, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2390 | + {tag: "Drop 0->2 NOK", live: false, from: 0, to: 2, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2391 | + {tag: "Drop 0->3 NOK", live: false, from: 0, to: 3, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2392 | + {tag: "Drop 3->0 NOK", live: false, from: 3, to: 0, count: 0, accept: false, indices:[0,1,2,3,4]}, |
2393 | + ]; |
2394 | + } |
2395 | + |
2396 | + function test_drag(data) { |
2397 | + var moveCount = 0; |
2398 | + function liveUpdate(event) { |
2399 | + if (event.status == ListItemDrag.Started) { |
2400 | + return; |
2401 | + } |
2402 | + if (data.accept) { |
2403 | + moveCount++; |
2404 | + listView.model.move(event.from, event.to, 1); |
2405 | + } |
2406 | + event.accept = data.accept; |
2407 | + } |
2408 | + function singleDrop(event) { |
2409 | + if (event.status == ListItemDrag.Dropped) { |
2410 | + if (data.accept) { |
2411 | + moveCount++; |
2412 | + listView.model.move(event.from, event.to, 1); |
2413 | + } |
2414 | + event.accept = data.accept; |
2415 | + } else if (event.status == ListItemDrag.Moving) { |
2416 | + event.accept = false; |
2417 | + } |
2418 | + } |
2419 | + |
2420 | + objectModel.reset(); |
2421 | + waitForRendering(listView); |
2422 | + listView.positionViewAtBeginning(); |
2423 | + var func = data.live ? liveUpdate : singleDrop; |
2424 | + listView.ViewItems.dragUpdated.connect(func); |
2425 | + |
2426 | + // enter drag mode |
2427 | + toggleDragMode(listView, true); |
2428 | + drag(listView, data.from, data.to); |
2429 | + compare(moveCount, data.count, "Move did not happen or more than one item was moved"); |
2430 | + // compare array indices |
2431 | + for (var i in data.indices) { |
2432 | + compare(listView.model.get(i).data, data.indices[i], "data at index " + i + " is not the expected one"); |
2433 | + } |
2434 | + |
2435 | + // cleanup |
2436 | + listView.ViewItems.dragUpdated.disconnect(func); |
2437 | + toggleDragMode(listView, false); |
2438 | + } |
2439 | + |
2440 | + // preconditions: |
2441 | + // the first 2 items cannot be dragged anywhere, nothing can be dropped in this area |
2442 | + // the 3-> items can be interchanged in between, cannot be dragged outside |
2443 | + function test_drag_restricted_data() { |
2444 | + return [ |
2445 | + {tag: "[0,1] locked, drag 0->1 NOK", from: 0, to: 1, count: 0, indices: [0,1,2,3,4]}, |
2446 | + {tag: "[0,1] locked, drag 1->2 NOK", from: 1, to: 2, count: 0, indices: [0,1,2,3,4]}, |
2447 | + {tag: "[0,1] locked, drag 2->1 NOK", from: 2, to: 1, count: 0, indices: [0,1,2,3,4]}, |
2448 | + {tag: "[0,1] locked, drag 2->0 NOK", from: 2, to: 0, count: 0, indices: [0,1,2,3,4]}, |
2449 | + // drag |
2450 | + {tag: "[0,1] locked, drag 2->3 OK", from: 2, to: 3, count: 1, indices: [0,1,3,2,4]}, |
2451 | + ]; |
2452 | + } |
2453 | + function test_drag_restricted(data) { |
2454 | + var moveCount = 0; |
2455 | + function updateHandler(event) { |
2456 | + if (event.status == ListItemDrag.Started) { |
2457 | + if (event.from < 2) { |
2458 | + event.accept = false; |
2459 | + } else { |
2460 | + event.minimumIndex = 2; |
2461 | + } |
2462 | + } else if (event.status == ListItemDrag.Moving) { |
2463 | + listView.model.move(event.from, event.to, 1); |
2464 | + moveCount++; |
2465 | + } |
2466 | + } |
2467 | + |
2468 | + objectModel.reset(); |
2469 | + waitForRendering(listView); |
2470 | + listView.positionViewAtBeginning(); |
2471 | + listView.ViewItems.dragUpdated.connect(updateHandler); |
2472 | + |
2473 | + // enter drag mode |
2474 | + toggleDragMode(listView, true); |
2475 | + drag(listView, data.from, data.to); |
2476 | + compare(moveCount, data.count, "Move did not happen or more than one item was moved"); |
2477 | + // compare array indices |
2478 | + for (var i in data.indices) { |
2479 | + compare(listView.model.get(i).data, data.indices[i], "data at index " + i + " is not the expected one"); |
2480 | + } |
2481 | + |
2482 | + // cleanup |
2483 | + listView.ViewItems.dragUpdated.disconnect(updateHandler); |
2484 | + toggleDragMode(listView, false); |
2485 | + } |
2486 | + |
2487 | + function test_drag_keeps_selected_indexes_data() { |
2488 | + return [ |
2489 | + {tag: "[0,1,2] selected, move 0->3, live", selected: [0,1,2], from: 0, to: 3, expected: [0,1,3], live: true}, |
2490 | + {tag: "[1,2] selected, move 3->2, live", selected: [1,2], from: 3, to: 2, expected: [1,3], live: true}, |
2491 | + {tag: "[1,2] selected, move 0->3, live", selected: [1,2], from: 0, to: 3, expected: [0,1], live: true}, |
2492 | + {tag: "[1,2] selected, move 3->0, live", selected: [1,2], from: 3, to: 0, expected: [2,3], live: true}, |
2493 | + // non-live updates |
2494 | + {tag: "[0,1,2] selected, move 0->3, non-live", selected: [0,1,2], from: 0, to: 3, expected: [0,1,3], live: false}, |
2495 | + {tag: "[1,2] selected, move 3->2, non-live", selected: [1,2], from: 3, to: 2, expected: [1,3], live: false}, |
2496 | + {tag: "[1,2] selected, move 0->3, non-live", selected: [1,2], from: 0, to: 3, expected: [0,1], live: false}, |
2497 | + {tag: "[1,2] selected, move 3->0, non-live", selected: [1,2], from: 3, to: 0, expected: [2,3], live: false}, |
2498 | + ]; |
2499 | + } |
2500 | + function test_drag_keeps_selected_indexes(data) { |
2501 | + function updateHandler(event) { |
2502 | + if (event.status == ListItemDrag.Started) { |
2503 | + return; |
2504 | + } |
2505 | + if (data.live || event.status == ListItemDrag.Dropped) { |
2506 | + listView.model.move(event.from, event.to, 1); |
2507 | + } else { |
2508 | + event.accept = false; |
2509 | + } |
2510 | + } |
2511 | + objectModel.reset(); |
2512 | + waitForRendering(listView); |
2513 | + listView.ViewItems.selectedIndices = data.selected; |
2514 | + listView.ViewItems.dragUpdated.connect(updateHandler); |
2515 | + toggleDragMode(listView, true); |
2516 | + drag(listView, data.from, data.to); |
2517 | + listView.ViewItems.dragUpdated.disconnect(updateHandler); |
2518 | + toggleDragMode(listView, false); |
2519 | + |
2520 | + // NOTE: the selected indexes order is arbitrar and cannot be predicted by the test |
2521 | + // therefore we check the selected indexes presence in the expected list. |
2522 | + compare(listView.ViewItems.selectedIndices.length, data.expected.length, "The selected indexes and expected list size differs"); |
2523 | + for (var i = 0; i < listView.ViewItems.selectedIndices.length; i++) { |
2524 | + var index = data.expected.indexOf(listView.ViewItems.selectedIndices[i]); |
2525 | + verify(index >= 0, "Index " + listView.ViewItems.selectedIndices[i] + " is not expected to be selected!"); |
2526 | + } |
2527 | + } |
2528 | + |
2529 | + // must run this immediately after the defaults are checked otherwise drag handler connected check will fail |
2530 | + function test_1_warn_missing_dragUpdated_signal_handler() { |
2531 | + ignoreWarning(warningFormat(116, 9, "QML ListView: ListView has no ViewItems.dragUpdated() signal handler implemented. No dragging will be possible.")); |
2532 | + toggleDragMode(listView, true); |
2533 | + drag(listView, 0, 1); |
2534 | + toggleDragMode(listView, true); |
2535 | + } |
2536 | + |
2537 | + DelegateModel { |
2538 | + id: delegateModel |
2539 | + delegate: ListItem { |
2540 | + objectName: "listItem" + index |
2541 | + Label { text: modelData } |
2542 | + } |
2543 | + } |
2544 | + ObjectModel { |
2545 | + id: objectModel2 |
2546 | + Repeater { |
2547 | + model: 3 |
2548 | + ListItem { |
2549 | + objectName: "listItem" + index |
2550 | + Label { text: modelData } |
2551 | + } |
2552 | + } |
2553 | + } |
2554 | + function test_warn_model_data() { |
2555 | + var list = [1,2,3,4,5,6,7,8,9,10]; |
2556 | + return [ |
2557 | + {tag: "number", model: 20, warning: "Dragging is only supported when using a QAbstractItemModel, ListModel or list."}, |
2558 | + {tag: "list", model: list, warning: ""}, |
2559 | + {tag: "ListModel", model: objectModel, warning: ""}, |
2560 | + {tag: "DelegateModel with number", model: delegateModel, modelModel: 20, warning: "Dragging is only supported when using a QAbstractItemModel, ListModel or list."}, |
2561 | + {tag: "DelegateModel with list", model: delegateModel, modelModel: list, warning: ""}, |
2562 | + {tag: "DelegateModel with ListModel", model: delegateModel, modelModel: objectModel, warning: ""}, |
2563 | + {tag: "ObjectModel", model: objectModel2, warning: ""}, |
2564 | + ]; |
2565 | + } |
2566 | + function test_warn_model(data) { |
2567 | + function dummyFunc() {} |
2568 | + if (data.warning !== "") { |
2569 | + ignoreWarning(warningFormat(116, 9, "QML ListView: " + data.warning)); |
2570 | + } |
2571 | + listView.model = data.model; |
2572 | + if (typeof data.modelModel !== "undefined") { |
2573 | + listView.model.model = data.modelModel; |
2574 | + } |
2575 | + waitForRendering(listView, 500); |
2576 | + listView.ViewItems.dragUpdated.connect(dummyFunc); |
2577 | + toggleDragMode(listView, true); |
2578 | + toggleDragMode(listView, false); |
2579 | + listView.ViewItems.dragUpdated.disconnect(dummyFunc); |
2580 | + } |
2581 | } |
2582 | } |
FAILED: Continuous integration, rev:1436 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1461/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1450 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/188 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/191 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/191/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/188 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 1284 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1448 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1448/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 18208
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: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1461/ rebuild
http://