Merge lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
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
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

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
Cris Dywan (kalikiana) wrote :

Thanks for addressing the comments!

Looking good now!

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 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 }

Subscribers

People subscribed via source and target branches