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

Proposed by Zsombor Egri on 2015-01-12
Status: Superseded
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/80-selection-mode
Diff against target: 2811 lines (+1899/-175)
30 files modified
components.api (+19/-0)
debian/control (+1/-0)
examples/ubuntu-ui-toolkit-gallery/NewListItems.qml (+92/-30)
modules/Ubuntu/Components/MainView.qdoc (+1/-2)
modules/Ubuntu/Components/MainView12.qml (+0/-10)
modules/Ubuntu/Components/Page10.qml (+2/-1)
modules/Ubuntu/Components/PageStack.qml (+13/-3)
modules/Ubuntu/Components/Tab.qml (+1/-54)
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/Components/plugin/unitythemeiconprovider.cpp (+2/-5)
modules/Ubuntu/Test/UbuntuTestCase.qml (+7/-0)
tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py (+4/-6)
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)
tests/unit_x11/tst_iconprovider/tst_iconprovider.cpp (+3/-3)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/82-dragging-mode
Reviewer Review Type Date Requested Status
Christian Dywan 2015-01-12 Needs Fixing on 2015-02-26
PS Jenkins bot continuous-integration Needs Fixing on 2015-02-25
Review via email: mp+246128@code.launchpad.net

This proposal has been superseded by a proposal from 2015-02-27.

Commit Message

ListItem drag mode.

To post a comment you must log in.
1444. By Zsombor Egri on 2015-02-25

small cleanup

1445. By Zsombor Egri on 2015-02-25

revert latest staging merge

1446. By Zsombor Egri on 2015-02-25

adding dragMode to restrict swiping

1447. By Zsombor Egri on 2015-02-25

enable dragMode togehther with selectMode in gallery; moveDisplaced transition added to UbuntuListView

1448. By Zsombor Egri on 2015-02-25

documenting ListItemDrag.status uses

1449. By Zsombor Egri on 2015-02-25

drag sample with immutable merged

1450. By Zsombor Egri on 2015-02-25

build-dep to qtdeclarative5-models-plugin

1451. By Zsombor Egri on 2015-02-25

fixing correcvt dependency to QtQml.Models

Christian Dywan (kalikiana) wrote :

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
Zsombor Egri (zsombi) wrote :

> 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.

1452. By Zsombor Egri on 2015-02-27

review comments addressed

1453. By Zsombor Egri on 2015-02-27

staging merge

1454. By Zsombor Egri on 2015-02-27

once-reverted staging diff re-submit

Unmerged revisions

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

Subscribers

People subscribed via source and target branches