on xenial help sign in
Merge trunk
03be7aeMichał Sawicz
.bazaar/plugins/makecheck_unity_phablet.py+15−6
@@ -23,12 +23,21 @@ def execute_makecheck(local_branch, master_branch, old_revision_number, old_revi
23
23
24 os.chdir(local_path_from_url(master_branch.base))
24 os.chdir(local_path_from_url(master_branch.base))
25
25
26− print "Executing 'make check'.."
26+ print "Executing 'make -C builddir test'.."
27+ os.environ['CTEST_OUTPUT_ON_FAILURE'] = "1"
27− if (subprocess.call("make check", shell=True) != 0):
28+ if (subprocess.call("make -C builddir test", shell=True) != 0):
28− raise errors.BzrError("Unit tests failed, fix them before committing!")
29
29
30− print "Executing 'make qmluitests'.."
30+ print("\n\n*** Warning ***\n\nBasic tests failed. This commit will not pass continuous integration.")
31− if (subprocess.call("make qmluitests", shell=True) != 0):
31+
32− raise errors.BzrError("QML UI tests failed, fix them before committing!")
32+ branch = local_branch or master_branch
33+ revision = branch.repository.get_revision(future_revision_id)
34+ msg_file = open('message.txt', 'w')
35+ msg_file.write(revision.message)
36+ msg_file.close()
37+
38+ print("\n\nSaved commit message to $SRC_DIR/message.txt.")
39+ print("You can uncommit this revision, fix the tests and reuse your message running:\n\nbzr commit -F message.txt\n\n")
40+ elif os.path.isfile("message.txt"):
41+ os.remove("message.txt")
33
42
34branch.Branch.hooks.install_named_hook('pre_commit', execute_makecheck, 'make check pre-commit')
43branch.Branch.hooks.install_named_hook('pre_commit', execute_makecheck, 'make check pre-commit')
.bzrignore+1
@@ -7,3 +7,4 @@
7/.cproject
7/.cproject
8/.settings
8/.settings
9/.pydevproject
9/.pydevproject
10+message.txt
Bottombar/Bottombar.qml+1−8
@@ -64,7 +64,7 @@ Item {
64 onShownChanged: bottomBarVisibilityCommunicatorShell.forceHidden = theHud.shown
64 onShownChanged: bottomBarVisibilityCommunicatorShell.forceHidden = theHud.shown
65 }
65 }
66
66
67− DirectionalDragArea {
67+ EdgeDragArea {
68 id: dragArea
68 id: dragArea
69 width: parent.width
69 width: parent.width
70 height: distanceThreshold
70 height: distanceThreshold
@@ -73,13 +73,6 @@ Item {
73 enabled: !theHud.shown && bottombar.enabled && applicationIsOnForeground
73 enabled: !theHud.shown && bottombar.enabled && applicationIsOnForeground
74 direction: Direction.Upwards
74 direction: Direction.Upwards
75
75
76− // values to be tweaked and later removed from here and set in stone as defaults
77− // once we are confident it's all good.
78− maxDeviation: units.gu(1)
79− wideningAngle: 30
80− distanceThreshold: units.gu(3)
81− minSpeed: units.gu(5)
82−
83 property int previousStatus: -1
76 property int previousStatus: -1
84 property real touchStartX: -1
77 property real touchStartX: -1
85
78
CODING+75−29
@@ -1,43 +1,64 @@
1Building Unity 8
1Building Unity 8
2================
2================
3
3
4+Only Ubuntu 13.10 (saucy) is supported as a developement environment.
5+
4download source
6download source
5---------------
7---------------
6−- the build requires some additional dependencies, so create the parent folder to not pollute your setup
8+- create a parent folder to not pollute your setup. so that you can have different unity
9+ branches there (trunk, WIP etc)
10+
7− $ mkdir ~/unity
11+ $ mkdir ~/unity8
8
12
9- fetch Unity 8 source
13- fetch Unity 8 source
10 $ sudo apt-get install bzr
14 $ sudo apt-get install bzr
11− $ bzr branch lp:unity/8.0 ~/unity/unity8
15+ $ bzr branch lp:unity8 ~/unity8/trunk
12− $ cd ~/unity/unity8
16+ $ cd ~/unity8/trunk
17+
18+install dependencies
19+--------------------
20+If its your first time, install build dependencies
21+
22+$ cd ~/unity8/trunk
23+$ ./build --setup
13
24
14−build dependencies
15−------------------
16−If its your first time, download & build deps
17−$ ./build -s
18 this will:
25 this will:
26+ - add the following PPA's
27+ - ppa:phablet-team/desktop-deps
19− - add ppa:phablet-team/desktop-deps, ppa:canonical-qt5-edgers/qt5-proper, ppa:ubuntu-sdk-team
28+ - ppa:canonical-qt5-edgers/qt5-proper
20− - build and install lp:libunity/phablet, lp:unity/phablet-mods, lp:hud/phablet locally
29+ - ppa:ubuntu-sdk-team
21− - install build dependencies
30+ - install the required dependencies to build, as well as run on the desktop
31+ - it should also install unity-scope-home and unity-lens-application which
32+ are technically "recommends", not strict dependencies
33+
34+Note, once the PPA's are added, the script runs apt-get update, if you have
35+any failures associated with it, the build script will bail out of the setup
36+and fail to install all the required dependencies.
37+
22
38
23build Unity 8
39build Unity 8
24-------------
40-------------
25−$ ./build
26
41
27−subsequent updates of the dependencies
42+$ cd ~/unity8/trunk
28−--------------------------------------
43+$ ./build
29−$ ./build_unity -u
30−$ ./build_unity
31
44
45+You may also rebuild with the -c for clean, which just deletes the build
46+directory.
32
47
33Running Unity 8 on your desktop
48Running Unity 8 on your desktop
34===============================
49===============================
35
50
36run the Unity 8 shell
51run the Unity 8 shell
37---------------------
52---------------------
38−$ cd ~/unity/unity8
53+$ cd ~/unity8/trunk
39$ ./run
54$ ./run
40
55
56+Options can be passed to test the lock screen
57+$ ./run -p OR -k
58+
59+-p is for pin lock, for which the default prompt answer is "1234"
60+-k is for keylock passphrase, for which the default prompt answer is "password"
61+
41Notes
62Notes
42-----
63-----
43- to navigate, utilize the mouse left button as you would your finger
64- to navigate, utilize the mouse left button as you would your finger
@@ -45,17 +66,29 @@ Notes
45 put the *.mo files into correct structure. We'll look for a better solution
66 put the *.mo files into correct structure. We'll look for a better solution
46 later.
67 later.
47 $ cd builddir; make install
68 $ cd builddir; make install
69+- if you wish to populate contacts
70+ $ sudo apt-get install gir1.2-ebook-1.2
71+ $ manage-address-book.py create
72+ this will:
73+ - Create an EDS address book
74+ - Populate the address book with sample data
48
75
49Running Unity 8 on devices
76Running Unity 8 on devices
50==========================
77==========================
51- Ubuntu Touch builds are currently only available in 13.10 flavour
78- Ubuntu Touch builds are currently only available in 13.10 flavour
52−- supported hardware should match one those listed here https://wiki.ubuntu.com/Touch/Devices#Running_fine
79+- supported hardware should match one those listed here
80+ https://wiki.ubuntu.com/Touch/Devices#Running_fine
53−- follow the Ubuntu Touch install instructions for your chosen device https://wiki.ubuntu.com/Touch/Install
81+- follow the Ubuntu Touch install instructions for your chosen device
82+ https://wiki.ubuntu.com/Touch/Install
54- make sure your device is connected to a WiFi network
83- make sure your device is connected to a WiFi network
84+- If you haven’t already or if you’ve just freshly flashed your device
85+ make sure you run phablet-network-setup which will allow package updates to
86+ the device
55−- also make sure you have your SSH key in ~/.ssh/id_rsa.pub (or install one on the device yourself)
87+- also make sure you have your SSH key in ~/.ssh/id_rsa.pub (or install one
56−
88+ on the device yourself)
89+
57−$ cd ~/unity/unity8
90+$ cd ~/unity/trunk
58−$ ./run_on_device -s
91+$ ./run_on_device --setup
59 this will:
92 this will:
60 - install your public SSH key on the device
93 - install your public SSH key on the device
61 - add ppa:canonical-qt5-edgers/qt5-proper and ppa:phablet-team
94 - add ppa:canonical-qt5-edgers/qt5-proper and ppa:phablet-team
@@ -67,19 +100,27 @@ $ ./run_on_device
67 - build on the device
100 - build on the device
68 - replace the running shell
101 - replace the running shell
69
102
103+Options can be passed to test the lock screen
104+$ ./run_on_device -p OR -k
105+
106+-p is for pin lock, for which the default prompt answer is "1234"
107+-k is for keylock passphrase, for which the default prompt answer is "password"
108+
70Notes
109Notes
71-----
110-----
72- using adb over TCP has some advantages, Google will be of help here
111- using adb over TCP has some advantages, Google will be of help here
73 - TL;DR: `adb tcpip 5555; adb connect $your_device_ip`
112 - TL;DR: `adb tcpip 5555; adb connect $your_device_ip`
74 - you can persist the above via `adb shell setprop persist.adb.tcp.port 5555`
113 - you can persist the above via `adb shell setprop persist.adb.tcp.port 5555`
75 - it's unsafe - anyone can connect to your device
114 - it's unsafe - anyone can connect to your device
76− - with TCP the Nexus 10 doesn't drop the adb connection when locking the screen
115+ - with TCP the Nexus 10 doesn't drop the adb connection when locking
116+ the screen
77−- exporting ANDROID_SERIAL=$your_device_serial or ANDROID_SERIAL=$your_device_ip:5555
117+- exporting ANDROID_SERIAL=$your_device_serial or
78− will make adb not complain about multiple devices
118+ ANDROID_SERIAL=$your_device_ip:5555 will make adb not complain about
119+ multiple devices
79- you can export TARGET_IP=$your_device_ip TARGET_SSH_PORT=22 for run_on_device
120- you can export TARGET_IP=$your_device_ip TARGET_SSH_PORT=22 for run_on_device
80 to not use adb forwarding - useful if you use multiple devices
121 to not use adb forwarding - useful if you use multiple devices
81−- using Ninja (package ninja-build - run_on_device -s installs this now) will improve
122+- using Ninja (package ninja-build - run_on_device -s installs this now)
82− build times (especially in the nothing-to-build case)
123+ will improve build times (especially in the nothing-to-build case)
83
124
84
125
85Coding guidelines
126Coding guidelines
@@ -90,4 +131,9 @@ https://launchpad.net/canonical-client-development-guidelines
90
131
91Development Environment
132Development Environment
92=======================
133=======================
93−QtCreator (available via the Software Center) is the best IDE to hack on Unity 8.
134+QtCreator (available via the Software Center) is the best IDE to hack on
135+Unity 8.
136+
137+If using VIM, make sure you install some syntax highlighting for QML, like
138+https://github.com/peterhoeg/vim-qml
139+
Components/Carousel.qml+2
@@ -24,6 +24,8 @@ import "carousel.js" as CarouselJS
24Item {
24Item {
25 id: carousel
25 id: carousel
26
26
27+ clip: true // Don't leak horizontally to other dashes
28+
27 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
29 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
28 property Component itemComponent
30 property Component itemComponent
29 /// Model for the Carousel, which has to be a model usable by a ListView
31 /// Model for the Carousel, which has to be a model usable by a ListView
Components/DragHandle.qml+128−64
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Ubuntu.Gestures 0.1
19import Ubuntu.Gestures 0.1
20+import "Math.js" as MathLocal
20
21
21/*
22/*
22 Put a DragHandle inside a Showable to enable the user to drag it from that handle.
23 Put a DragHandle inside a Showable to enable the user to drag it from that handle.
@@ -43,28 +44,128 @@ import Ubuntu.Gestures 0.1
43 }
44 }
44
45
45 */
46 */
46−DirectionalDragArea {
47+EdgeDragArea {
47 id: dragArea
48 id: dragArea
49+ objectName: "dragHandle"
48
50
49− // Once we are satisfied with those values, make them the default
51+ // Disable gesture detection by default when hinting is used.
50− // and remove them from here for the sake of consistency
52+ // It doesn't make sense to have both.
51− maxDeviation: units.gu(1)
52− wideningAngle: 20
53− distanceThreshold: units.gu(3)
53+ distanceThreshold: hintDisplacement > 0 ? 0 : units.gu(1.5)
54− minSpeed: units.gu(5)
54+
55+ property bool stretch: false
56+
57+ property alias autoCompleteDragThreshold: dragEvaluator.dragThreshold
55
58
56 // How far you can drag
59 // How far you can drag
60+ property real maxTotalDragDistance: {
61+ if (stretch) {
62+ 0; // not enough context information to set a sensible default
63+ } else {
57− property real maxTotalDragDistance: Direction.isHorizontal(direction) ? parent.width : parent.height
64+ Direction.isHorizontal(direction) ? parent.width : parent.height;
65+ }
66+ }
67+
68+ property real hintDisplacement: 0
69+ SmoothedAnimation {
70+ id: hintingAnimation
71+ target: hintingAnimation
72+ property: "targetValue"
73+ duration: 150
74+ velocity: -1
75+ to: Direction.isPositive(direction) ? d.startValue + hintDisplacement
76+ : d.startValue - hintDisplacement
77+ property real targetValue
78+ onTargetValueChanged: {
79+ if (!running) {
80+ return;
81+ }
82+
83+ if (Direction.isPositive(direction)) {
84+ if (parent[d.targetProp] < targetValue) {
85+ parent[d.targetProp] = targetValue;
86+ }
87+ } else {
88+ if (parent[d.targetProp] > targetValue) {
89+ parent[d.targetProp] = targetValue;
90+ }
91+ }
92+ }
93+ }
94+
95+ // Private stuff
96+ QtObject {
97+ id: d
98+ property var previousStatus: undefined
99+ property real startValue
100+ property real minValue: Direction.isPositive(direction) ? startValue
101+ : startValue - maxTotalDragDistance
102+ property real maxValue: Direction.isPositive(direction) ? startValue + maxTotalDragDistance
103+ : startValue
104+
105+ property var dragParent: dragArea.parent
106+
107+ // The property of DragHandle's parent that will be modified
108+ property string targetProp: {
109+ if (stretch) {
110+ Direction.isHorizontal(direction) ? "width" : "height";
111+ } else {
112+ Direction.isHorizontal(direction) ? "x" : "y";
113+ }
114+ }
115+
116+ function limitMovement(step) {
117+ var targetValue = MathLocal.clamp(dragParent[targetProp] + step, minValue, maxValue);
118+ var step = targetValue - dragParent[targetProp];
119+
120+ if (hintDisplacement == 0) {
121+ return step;
122+ }
123+
124+ // we should not go behind hintingAnimation's current value
125+ if (Direction.isPositive(direction)) {
126+ if (dragParent[targetProp] + step < hintingAnimation.targetValue) {
127+ step = hintingAnimation.targetValue - dragParent[targetProp];
128+ }
129+ } else {
130+ if (dragParent[targetProp] + step > hintingAnimation.targetValue) {
131+ step = hintingAnimation.targetValue - dragParent[targetProp];
132+ }
133+ }
134+
135+ return step;
136+ }
137+
138+ function onFinishedRecognizedGesture() {
139+ if (dragEvaluator.shouldAutoComplete()) {
140+ completeDrag();
141+ } else {
142+ rollbackDrag();
143+ }
144+ }
58
145
59− property var __previousStatus
146+ function completeDrag() {
60− property real __totalDragDistance: 0
147+ if (dragParent.shown) {
148+ dragParent.hide();
149+ } else {
150+ dragParent.show();
151+ }
152+ }
153+
154+ function rollbackDrag() {
155+ if (dragParent.shown) {
156+ dragParent.show();
157+ } else {
158+ dragParent.hide();
159+ }
160+ }
161+ }
61
162
62 property alias edgeDragEvaluator: dragEvaluator
163 property alias edgeDragEvaluator: dragEvaluator
63
164
64 EdgeDragEvaluator {
165 EdgeDragEvaluator {
65 objectName: "edgeDragEvaluator"
166 objectName: "edgeDragEvaluator"
66 id: dragEvaluator
167 id: dragEvaluator
67− trackedPosition: Direction.isHorizontal(direction) ? parent.x + touchX : parent.y + touchY
168+ trackedPosition: sceneDistance
68 maxDragDistance: maxTotalDragDistance
169 maxDragDistance: maxTotalDragDistance
69 direction: dragArea.direction
170 direction: dragArea.direction
70 }
171 }
@@ -74,75 +175,38 @@ DirectionalDragArea {
74 // don't go the whole distance in order to smooth out the movement
175 // don't go the whole distance in order to smooth out the movement
75 var step = distance * 0.3;
176 var step = distance * 0.3;
76
177
77− step = __limitMovement(step);
178+ step = d.limitMovement(step);
78
179
79− __totalDragDistance += step;
180+ parent[d.targetProp] += step;
80−
81− if (Direction.isHorizontal(direction)) {
82− parent.x += step;
83− } else {
84− parent.y += step;
85− }
86 }
181 }
87 }
182 }
88
183
89−
184+ onDraggingChanged: {
90− function __limitMovement(step) {
185+ if (dragging) {
91− if (Direction.isPositive(direction)) {
186+ if (hintDisplacement > 0) {
92− if (__totalDragDistance + step > maxTotalDragDistance) {
187+ hintingAnimation.targetValue = d.startValue;
93− step = maxTotalDragDistance - __totalDragDistance;
188+ hintingAnimation.start();
94− } else if (__totalDragDistance + step < 0) {
95− step = 0 - __totalDragDistance;
96 }
189 }
97 } else {
190 } else {
98− if (__totalDragDistance + step < -maxTotalDragDistance) {
191+ hintingAnimation.stop();
99− step = -maxTotalDragDistance - __totalDragDistance;
100− } else if (__totalDragDistance + step > 0) {
101− step = 0 - __totalDragDistance;
102− }
103 }
192 }
104−
105− return step;
106 }
193 }
107
194
108 onStatusChanged: {
195 onStatusChanged: {
109 if (status === DirectionalDragArea.WaitingForTouch) {
196 if (status === DirectionalDragArea.WaitingForTouch) {
110− if (__previousStatus === DirectionalDragArea.Recognized) {
197+ if (d.previousStatus === DirectionalDragArea.Recognized) {
111− __onFinishedRecognizedGesture();
198+ d.onFinishedRecognizedGesture();
112 }
199 }
113− __totalDragDistance = 0;
200+ d.startValue = parent[d.targetProp];
114 }
201 }
202+
115− else if (status === DirectionalDragArea.Undecided) {
203+ if (d.previousStatus === DirectionalDragArea.WaitingForTouch ||
204+ d.previousStatus === undefined) {
116 dragEvaluator.reset();
205 dragEvaluator.reset();
206+ d.startValue = parent[d.targetProp];
117 }
207 }
118− else if (status === DirectionalDragArea.Recognized) {
119− if (__previousStatus === DirectionalDragArea.WaitingForTouch)
120− dragEvaluator.reset();
121− }
122− __previousStatus = status;
123− }
124
208
125− function __onFinishedRecognizedGesture() {
209+ d.previousStatus = status;
126− if (dragEvaluator.shouldAutoComplete()) {
127− __completeDrag();
128− } else {
129− __rollbackDrag();
130− }
131 }
210 }
132
211
133− function __completeDrag() {
134− if (parent.shown) {
135− parent.hide();
136− } else {
137− parent.show();
138− }
139− }
140−
141− function __rollbackDrag() {
142− if (parent.shown) {
143− parent.show();
144− } else {
145− parent.hide();
146− }
147− }
148}
212}
Components/EdgeDragArea.qmlnew file+36
@@ -0,0 +1,36 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
20+
21+/*
22+ A DirectionalDragArea wrapper that provides some well-chosen defaults
23+ for its gesture recognition parameters.
24+*/
25+DirectionalDragArea {
26+
27+ // TODO: Re-evaluate those or even the recognition heuristics itself once
28+ // we have gesture cancelling/forwarding in place.
29+ //
30+ // The idea here is that it's better having lax rules than false negatives.
31+ // False negatives are very frustrating to the user.
32+ maxDeviation: units.gu(3)
33+ wideningAngle: 50
34+ distanceThreshold: units.gu(1.5)
35+ minSpeed: units.gu(0) // some people were getting false negatives with it enabled.
36+}
Components/FilterGrid.qml+1−1
@@ -108,7 +108,7 @@ Item {
108
108
109 anchors.fill: parent
109 anchors.fill: parent
110 radius: "small"
110 radius: "small"
111− borderSource: ItemStyle.style.borderPressed
111+ borderSource: "radius_pressed.sci"
112 opacity: button.pressed ? 1.0 : 0.0
112 opacity: button.pressed ? 1.0 : 0.0
113 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
113 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
114 }
114 }
Components/ListItems/Base.qml+19−21
@@ -54,35 +54,33 @@ AbstractButton {
54 */
54 */
55 function __showDivider() {
55 function __showDivider() {
56 // if we're not in ListView, always show a thin dividing line at the bottom
56 // if we're not in ListView, always show a thin dividing line at the bottom
57− if (ListView.view !== null) {
57+ var model = null;
58−
58+ if (typeof ListViewWithPageHeader !== 'undefined') {
59− // if we're last item in ListView don't show divider
59+ if (typeof ListViewWithPageHeader.model !== 'undefined') {
60− if (index === ListView.view.model.count - 1) return false;
60+ model = ListViewWithPageHeader.model;
61+ }
62+ } else if (ListView.view !== null) {
63+ model = ListView.view.model;
61 }
64 }
65+ // if we're last item in ListView don't show divider
66+ if (model && index === model.count - 1) return false;
67+
62 return true;
68 return true;
63 }
69 }
64
70
65− property bool __clippingRequired: ListView.view !== null
71+ /* Relevant really only for ListViewWithPageHeader case: specify how many pixels we can overlap with the section header */
66− && ListView.view.section.labelPositioning & ViewSection.CurrentLabelAtStart
72+ readonly property int allowedOverlap: units.dp(1)
67−
68− property real __yPositionRelativeToListView: ListView.view ? y - ListView.view.contentY : y
69
73
70 property real __heightToClip: {
74 property real __heightToClip: {
71 // Check this is in position where clipping is needed
75 // Check this is in position where clipping is needed
72− if (__clippingRequired && __yPositionRelativeToListView <= __sectionDelegateHeight
76+ if (typeof ListViewWithPageHeader !== 'undefined') {
73− && __yPositionRelativeToListView > -height) {
77+ if (typeof heightToClip !== 'undefined') {
74− return Math.min(__sectionDelegateHeight - __yPositionRelativeToListView, height);
78+ if (heightToClip >= allowedOverlap) {
75− } else {
79+ return heightToClip - allowedOverlap;
76− return 0;
80+ }
77− }
81+ }
78− }
79−
80− property int __sectionDelegateHeight: {
81− if (__clippingRequired && ListView.view.hasOwnProperty("__sectionDelegateHeight")) {
82− return ListView.view.__sectionDelegateHeight;
83− } else {
84− return 0;
85 }
82 }
83+ return 0;
86 }
84 }
87
85
88 /*!
86 /*!
Components/ListItems/Header.qml−6
@@ -25,12 +25,6 @@ Item {
25 */
25 */
26 property alias text: label.text
26 property alias text: label.text
27
27
28− /* When this header is used in a ListViewWithPageHeader with CurrentLabelAtStart mode, to stop
29− the list's delegates contents being visible through this header, they clip themselves
30− depending on their position. However to allow delegates underlap the bottom X pixels of this
31− header (so delegate appears through ThinDivider say) we set an offset property here. */
32− readonly property int bottomBorderAllowedOverlap: bottomBorder.allowedBottomOverlap
33−
34 height: units.gu(4.5)
28 height: units.gu(4.5)
35
29
36 Item {
30 Item {
Components/ListItems/ThinDivider.qml−4
@@ -23,9 +23,5 @@ Image {
23 }
23 }
24 height: (visible) ? units.dp(2) : 0
24 height: (visible) ? units.dp(2) : 0
25
25
26− /* Relevant really only for ListViewWithPageHeader with Base delegates: should this image
27− be overlapping content, specify how many pixels from the bottom should be transparent */
28− readonly property int allowedBottomOverlap: units.dp(1)
29−
30 source: "graphics/ListItemDividerHorizontal.png"
26 source: "graphics/ListItemDividerHorizontal.png"
31}
27}
Components/ListViewWithPageHeader.qmldeleted−189
@@ -1,189 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import "Math.js" as MathLocal
19−
20−Item {
21− id: root
22− property Item pageHeader: null
23− property Component sectionDelegate: null
24− property string sectionProperty: ""
25− property alias model: listView.model
26− property alias delegate: listView.delegate
27− property ListView view: listView
28− property alias moving: flicker.moving
29− property alias atYEnd: flicker.atYEnd
30− property bool clipListView: true
31−
32− readonly property real __headerHeight: (pageHeader) ? pageHeader.implicitHeight : 0
33− property real __headerVisibleHeight: __headerHeight
34− readonly property real __overshootHeight: (flicker.contentY < 0) ? -flicker.contentY : 0
35−
36−
37− // TODO move to AnimationController
38− ParallelAnimation {
39− id: headerAnimation
40− property real targetContentY
41− NumberAnimation {
42− target: root
43− property: "__headerVisibleHeight"
44− to: root.__headerHeight
45− duration: 200
46− easing.type: Easing.OutQuad
47− }
48− NumberAnimation {
49− target: listView
50− property: "contentY"
51− to: headerAnimation.targetContentY
52− duration: 200
53− easing.type: Easing.OutQuad
54− }
55− }
56−
57− function positionAtBeginning() {
58− __headerVisibleHeight = __headerHeight
59− flicker.contentY = 0
60− }
61−
62− function showHeader() {
63− headerAnimation.targetContentY = listView.contentY + (__headerHeight - __headerVisibleHeight)
64− headerAnimation.start()
65− }
66−
67− function flick(xVelocity, yVelocity) {
68− flicker.flick(xVelocity, yVelocity);
69− }
70−
71− onPageHeaderChanged: {
72− pageHeader.parent = pageHeaderContainer;
73− pageHeader.anchors.fill = pageHeaderContainer;
74− }
75−
76− Item {
77− id: pageHeaderClipper
78− parent: flicker // parent to Flickable so mouse click events passed through to the header component
79− anchors {
80− top: parent.top
81− left: parent.left
82− right: parent.right
83− }
84− height: __headerVisibleHeight + __overshootHeight
85−
86− Item {
87− id: pageHeaderContainer
88− anchors {
89− left: parent.left
90− right: parent.right
91− bottom: parent.bottom
92− }
93− height: __headerHeight + __overshootHeight
94− }
95− }
96−
97− ListView {
98− id: listView
99− parent: flicker // parent to Flickable so mouse click events passed through to List delegates
100− anchors {
101− left: parent.left
102− right: parent.right
103− top: parent.top
104− topMargin: __headerVisibleHeight
105− }
106− height: root.height
107−
108− // FIXME scrolling workaround, see below
109− cacheBuffer: height*10
110−
111− section.property: sectionProperty
112− section.criteria: ViewSection.FullString
113− section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
114− section.delegate: sectionDelegate
115−
116− interactive: false
117− clip: root.clipListView
118−
119− property int __sectionDelegateHeight: __getHeight(section.delegate)
120− function __getHeight(component) {
121− // want height (minus allowed overlap) of the section delegate as is needed for clipping
122− if (component === null) return 0;
123− var object = component.createObject(); //FIXME: throws 'section' not defined error
124− var value = object.height - object.bottomBorderAllowedOverlap;
125− object.destroy();
126− return value;
127− }
128−
129− property real previousOriginY: 0
130− onOriginYChanged: {
131− var deltaOriginY = originY - previousOriginY;
132− previousOriginY = originY;
133− /* When originY changes, it causes the top of the flicker and the top of the list to fall
134− out of sync - and thus the contentY positioning will be broken. To correct for this we
135− manually change the flickable's contentY here */
136− flicker.contentY -= deltaOriginY;
137− }
138−
139− /* For case when list content greater than container height and list scrolled down so header
140− hidden. If content shrinks to be smaller than the container height, we want the header to
141− re-appear */
142− property real __previousContentHeight: 0
143− onContentHeightChanged: {
144− var deltaContentHeight = contentHeight - __previousContentHeight;
145− __previousContentHeight = contentHeight;
146− if (contentHeight < height && deltaContentHeight < 0 && __headerVisibleHeight < height - contentHeight) {
147− __headerVisibleHeight = Math.min(height - contentHeight, __headerHeight);
148− }
149− }
150− }
151−
152− Flickable {
153− id: flicker
154− anchors.fill: parent
155− contentHeight: listView.contentHeight + __headerHeight
156− maximumFlickVelocity: height * 10
157− flickDeceleration: height * 2
158− onContentYChanged: {
159− var deltaContentY = contentY - __previousContentY;
160− __previousContentY = contentY;
161−
162− // first decide if movement will prompt the page header to change height
163− if ((deltaContentY < 0 && __headerVisibleHeight >= 0) ||
164− (deltaContentY > 0 && __headerVisibleHeight <= __headerHeight)) {
165−
166− // calculate header height - but prevent bounce from changing it
167− if (contentY > 0 && contentY < contentHeight - height) {
168− __headerVisibleHeight = MathLocal.clamp(__headerVisibleHeight - deltaContentY, 0, __headerHeight);
169− }
170− }
171−
172− // now we move list position, taking into account page header height
173−
174− // BUG: With section headers enabled, the values of originY and contentY appear not
175− // correct at the exact point originY changes. originY changes when the ListView
176− // deletes/creates hidden delegates which are above the visible delegates.
177− // As a result of this bug, you experience jittering scrolling when rapidly moving
178− // around in large lists. See https://bugreports.qt-project.org/browse/QTBUG-27997
179− // A workaround is to use a large enough cacheBuffer to prevent deletions/creations
180− // so effectively originY is always zero.
181− var newContentY = flicker.contentY + listView.originY - __headerHeight + __headerVisibleHeight
182− if (newContentY < listView.contentHeight) {
183− listView.contentY = newContentY;
184− }
185− }
186−
187− property real __previousContentY: 0
188− }
189−}
Components/PageHeader.qml+2−2
@@ -149,7 +149,7 @@ Item {
149
149
150 primaryItem: Button {
150 primaryItem: Button {
151 enabled: searchField.text != ""
151 enabled: searchField.text != ""
152− ItemStyle.class: "transparent-button"
152+ color: "transparent"
153 onClicked: {
153 onClicked: {
154 if (searchField.text != "") {
154 if (searchField.text != "") {
155 searchHistory.addQuery(searchField.text)
155 searchHistory.addQuery(searchField.text)
@@ -272,7 +272,7 @@ Item {
272 // FIXME: this should go into the first item below, but enable: false
272 // FIXME: this should go into the first item below, but enable: false
273 // prevents mouse events propagation
273 // prevents mouse events propagation
274 Button {
274 Button {
275− ItemStyle.class: "transparent-button"
275+ color: "transparent"
276 anchors {
276 anchors {
277 top: parent.top
277 top: parent.top
278 right: parent.right
278 right: parent.right
Components/Stage.qml+1−5
@@ -423,7 +423,7 @@ Showable {
423 maxDragDistance: stage.width
423 maxDragDistance: stage.width
424 }
424 }
425
425
426− DirectionalDragArea {
426+ EdgeDragArea {
427 id: rightEdgeDraggingArea
427 id: rightEdgeDraggingArea
428
428
429 enabled: {
429 enabled: {
@@ -441,10 +441,6 @@ Showable {
441 anchors.right: parent.right
441 anchors.right: parent.right
442
442
443 direction: Direction.Leftwards
443 direction: Direction.Leftwards
444− maxDeviation: units.gu(1)
445− wideningAngle: 20
446− distanceThreshold: units.gu(3)
447− minSpeed: units.gu(5)
448
444
449 onDistanceChanged: {
445 onDistanceChanged: {
450 if (status !== DirectionalDragArea.Recognized)
446 if (status !== DirectionalDragArea.Recognized)
Components/Tile.qml+1−1
@@ -51,7 +51,7 @@ AbstractButton {
51
51
52 anchors.fill: icon
52 anchors.fill: icon
53 radius: "medium"
53 radius: "medium"
54− borderSource: ItemStyle.style.borderPressed
54+ borderSource: "radius_pressed.sci"
55 opacity: root.pressed ? 1.0 : 0.0
55 opacity: root.pressed ? 1.0 : 0.0
56 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
56 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
57 }
57 }
Components/UbuntuShapeForItem.qml+12−56
@@ -15,7 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18−import Ubuntu.Components 0.1 as Theming
18+import Ubuntu.Components 0.1
19
19
20/* FIXME: This component is duplicating the UbuntuShape from the SDK, but shapes more
20/* FIXME: This component is duplicating the UbuntuShape from the SDK, but shapes more
21 * general (Item-based) components. This ability should be incorporated into the SDK's
21 * general (Item-based) components. This ability should be incorporated into the SDK's
@@ -24,66 +24,22 @@ import Ubuntu.Components 0.1 as Theming
24 */
24 */
25
25
26Item {
26Item {
27− id: shape
27+ id: root
28
28
29− Theming.ItemStyle.class: "UbuntuShape-radius-" + radius
29+ property alias radius: shape.radius
30+ property alias image: source.sourceItem
30
31
31− property string radius: "small"
32+ ShaderEffectSource {
32− property url maskSource: Theming.ComponentUtils.style(shape, "maskSource", "")
33− property url borderSource: Theming.ComponentUtils.style(shape, "borderIdle", "")
34− property Item image
35−
36− implicitWidth: units.gu(8)
37− implicitHeight: units.gu(8)
38−
39− onWidthChanged: __updateImageDimensions()
40− onHeightChanged: __updateImageDimensions()
41− onImageChanged: __updateImageDimensions()
42−
43− function __updateImageDimensions() {
44− if (!image) return;
45− image.width = shape.width;
46− image.height = shape.height;
47− image.visible = false;
48− }
49−
50− ShaderEffect {
51− anchors.fill: parent
52− visible: shape.image
53−
54− property ShaderEffectSource mask: ShaderEffectSource {
55− sourceItem: BorderImage {
56− width: shape.width
57− height: shape.height
58− source: shape.maskSource
33+ id: source
59− visible: false
34+ width: 1
60− }
35+ height: 1
61− }
36+ hideSource: true
62−
63− property ShaderEffectSource image: ShaderEffectSource {
64− sourceItem: shape.image
65− }
66−
67− fragmentShader:
68− "
69− varying highp vec2 qt_TexCoord0;
70− uniform lowp float qt_Opacity;
71− uniform sampler2D mask;
72− uniform sampler2D image;
73−
74− void main(void)
75− {
76− lowp vec4 maskColor = texture2D(mask, qt_TexCoord0.st);
77− lowp vec4 imageColor = texture2D(image, qt_TexCoord0.st);
78− gl_FragColor = imageColor * maskColor.a * qt_Opacity;
79− }
80− "
81 }
37 }
82
38
83− BorderImage {
39+ Shape {
84− id: border
40+ id: shape
41+ image: source
85
42
86 anchors.fill: parent
43 anchors.fill: parent
87− source: shape.borderSource
88 }
44 }
89}
45}
Dash/Apps/RunningApplicationTile.qml+1−1
@@ -87,7 +87,7 @@ AbstractButton {
87
87
88 anchors.fill: shapedApplicationImage
88 anchors.fill: shapedApplicationImage
89 radius: "medium"
89 radius: "medium"
90− borderSource: ItemStyle.style.borderPressed
90+ borderSource: "radius_pressed.sci"
91 opacity: root.pressed ? 1.0 : 0.0
91 opacity: root.pressed ? 1.0 : 0.0
92 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
92 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
93 }
93 }
Dash/DashApps.qml+2−3
@@ -99,7 +99,7 @@ ScopeView {
99 When using Loader to load external QML file in the list deelgate, the ListView has
99 When using Loader to load external QML file in the list deelgate, the ListView has
100 a bug where it can position the delegate content to overlap the section header
100 a bug where it can position the delegate content to overlap the section header
101 of the ListView - a workaround is to use sourceComponent of Loader instead */
101 of the ListView - a workaround is to use sourceComponent of Loader instead */
102− Component { id: runningApplicationsGrid; RunningApplicationsGrid {} }
102+ Component { id: runningApplicationsGrid; RunningApplicationsGrid { clip: true } }
103 Component {
103 Component {
104 id: applicationsFilterGrid;
104 id: applicationsFilterGrid;
105 ApplicationsFilterGrid {
105 ApplicationsFilterGrid {
@@ -112,7 +112,7 @@ ScopeView {
112 "ApplicationsFilterGrid": applicationsFilterGrid,
112 "ApplicationsFilterGrid": applicationsFilterGrid,
113 }
113 }
114
114
115− ListViewWithPageHeader {
115+ ScopeListView {
116 id: categoryView
116 id: categoryView
117 anchors.fill: parent
117 anchors.fill: parent
118 model: SortFilterProxyModel {
118 model: SortFilterProxyModel {
@@ -150,7 +150,6 @@ ScopeView {
150 item.model = categoryModels[modelName]
150 item.model = categoryModels[modelName]
151 }
151 }
152 }
152 }
153− asynchronous: true
154 }
153 }
155
154
156 ListView.onRemove: SequentialAnimation {
155 ListView.onRemove: SequentialAnimation {
Dash/DashHome.qml+1−2
@@ -130,7 +130,7 @@ ScopeView {
130 "VideosGrid": videosGrid,
130 "VideosGrid": videosGrid,
131 }
131 }
132
132
133− ListViewWithPageHeader {
133+ ScopeListView {
134 id: listView
134 id: listView
135 anchors.fill: parent
135 anchors.fill: parent
136 model: categoryListModel
136 model: categoryListModel
@@ -161,7 +161,6 @@ ScopeView {
161 item.clicked.connect(playVideo);
161 item.clicked.connect(playVideo);
162 }
162 }
163 }
163 }
164− asynchronous: true
165 }
164 }
166 }
165 }
167
166
Dash/DashMusic.qml+1−2
@@ -64,7 +64,7 @@ ScopeView {
64 }
64 }
65 }
65 }
66
66
67− ListViewWithPageHeader {
67+ ScopeListView {
68 id: categoryView
68 id: categoryView
69 anchors.fill: parent
69 anchors.fill: parent
70 model: scopeView.categories
70 model: scopeView.categories
@@ -84,7 +84,6 @@ ScopeView {
84 onLoaded: {
84 onLoaded: {
85 item.model = results
85 item.model = results
86 }
86 }
87− asynchronous: true
88 }
87 }
89 }
88 }
90
89
Dash/DashVideos.qml+2−3
@@ -97,11 +97,11 @@ ScopeView {
97 }
97 }
98 }
98 }
99
99
100− ListViewWithPageHeader {
100+ ScopeListView {
101 id: categoryView
101 id: categoryView
102 anchors.fill: parent
102 anchors.fill: parent
103 model: scopeView.categories
103 model: scopeView.categories
104− clipListView: !previewLoader.onScreen
104+ forceNoClip: previewLoader.onScreen
105
105
106 onAtYEndChanged: if (atYEnd) endReached()
106 onAtYEndChanged: if (atYEnd) endReached()
107 onMovingChanged: if (moving && atYEnd) endReached()
107 onMovingChanged: if (moving && atYEnd) endReached()
@@ -119,7 +119,6 @@ ScopeView {
119 onLoaded: {
119 onLoaded: {
120 item.model = results
120 item.model = results
121 }
121 }
122− asynchronous: true
123
122
124 Connections {
123 Connections {
125 target: loader.item
124 target: loader.item
Dash/GenericScopeView.qml+1−1
@@ -43,7 +43,7 @@ ScopeView {
43 }
43 }
44 }
44 }
45
45
46− ListViewWithPageHeader {
46+ ScopeListView {
47 id: categoryView
47 id: categoryView
48 anchors.fill: parent
48 anchors.fill: parent
49 model: scopeView.categories
49 model: scopeView.categories
Dash/Music/AlbumTile.qml+1−1
@@ -45,7 +45,7 @@ AbstractButton {
45
45
46 anchors.fill: icon
46 anchors.fill: icon
47 radius: "medium"
47 radius: "medium"
48− borderSource: ItemStyle.style.borderPressed
48+ borderSource: "radius_pressed.sci"
49 opacity: root.pressed ? 1.0 : 0.0
49 opacity: root.pressed ? 1.0 : 0.0
50 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
50 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
51 }
51 }
Dash/ScopeListView.qmlnew file+23
@@ -0,0 +1,23 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import ListViewWithPageHeader 0.1
19+
20+ListViewWithPageHeader {
21+ maximumFlickVelocity: height * 10
22+ flickDeceleration: height * 2
23+}
Greeter/Greeter.qml+2−2
@@ -48,7 +48,7 @@ Showable {
48 signal unlocked(int uid)
48 signal unlocked(int uid)
49
49
50 onRightTeaserPressedChanged: {
50 onRightTeaserPressedChanged: {
51− if (rightTeaserPressed) {
51+ if (rightTeaserPressed && !locked) {
52 teasingTimer.start();
52 teasingTimer.start();
53 }
53 }
54 }
54 }
@@ -81,7 +81,7 @@ Showable {
81 states: [
81 states: [
82 State {
82 State {
83 name: "teasing"
83 name: "teasing"
84− when: greeter.rightTeaserPressed || teasingTimer.running
84+ when: teasingTimer.running
85 PropertyChanges {
85 PropertyChanges {
86 target: greeter
86 target: greeter
87 x: -units.gu(2)
87 x: -units.gu(2)
Hud/SearchBar.qml−7
@@ -55,13 +55,6 @@ Item {
55 }
55 }
56 }
56 }
57
57
58− // Don't let the style play with our opacity
59− Binding {
60− target: searchBar.ItemStyle.style
61− property: "opacity"
62− value: 1
63− }
64−
65 Item {
58 Item {
66 id: primary
59 id: primary
67 height: searchBar.height
60 height: searchBar.height
Hud/ToolBarIcon.qml+1−1
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19
19
20Button {
20Button {
21− ItemStyle.class: "transparent-button"
21+ color: "transparent"
22
22
23 property alias source: image.source
23 property alias source: image.source
24
24
Launcher/Launcher.qml+4−12
@@ -25,7 +25,6 @@ Item {
25
25
26 property bool available: true // can be used to disable all interactions
26 property bool available: true // can be used to disable all interactions
27
27
28− property bool teasing: false
29 property int panelWidth: units.gu(8.5)
28 property int panelWidth: units.gu(8.5)
30 property int dragAreaWidth: units.gu(1)
29 property int dragAreaWidth: units.gu(1)
31 property real progress: dragArea.dragging && dragArea.touchX > panel.width ? dragArea.touchX : 0
30 property real progress: dragArea.dragging && dragArea.touchX > panel.width ? dragArea.touchX : 0
@@ -58,8 +57,8 @@ Item {
58 animateTimer.start();
57 animateTimer.start();
59 }
58 }
60
59
61− onTeasingChanged: {
60+ function tease() {
62− if (teasing) {
61+ if (available) {
63 teaseTimer.start();
62 teaseTimer.start();
64 }
63 }
65 }
64 }
@@ -181,18 +180,11 @@ Item {
181 }
180 }
182 }
181 }
183
182
184− DirectionalDragArea {
183+ EdgeDragArea {
185 id: dragArea
184 id: dragArea
186
185
187 direction: Direction.Rightwards
186 direction: Direction.Rightwards
188
187
189− // values to be tweaked and later removed from here and set in stone as defaults
190− // once we are confident it's all good.
191− maxDeviation: units.gu(1)
192− wideningAngle: 30
193− distanceThreshold: units.gu(3)
194− minSpeed: units.gu(5)
195−
196 enabled: root.available && root.state !== "visible"
188 enabled: root.available && root.state !== "visible"
197 width: root.dragAreaWidth
189 width: root.dragAreaWidth
198 height: root.height
190 height: root.height
@@ -244,7 +236,7 @@ Item {
244 },
236 },
245 State {
237 State {
246 name: "teasing"
238 name: "teasing"
247− when: root.teasing || teaseTimer.running
239+ when: teaseTimer.running
248 PropertyChanges {
240 PropertyChanges {
249 target: panel
241 target: panel
250 x: -root.panelWidth + units.gu(2)
242 x: -root.panelWidth + units.gu(2)
Shell.qml+6−1
@@ -439,6 +439,12 @@ FocusScope {
439 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
439 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
440 shell.background = bgPath ? bgPath : default_background
440 shell.background = bgPath ? bgPath : default_background
441 }
441 }
442+
443+ onLeftTeaserPressedChanged: {
444+ if (leftTeaserPressed) {
445+ launcher.tease();
446+ }
447+ }
442 }
448 }
443
449
444 InputFilterArea {
450 InputFilterArea {
@@ -532,7 +538,6 @@ FocusScope {
532 width: parent.width
538 width: parent.width
533 dragAreaWidth: shell.edgeSize
539 dragAreaWidth: shell.edgeSize
534 available: !greeter.locked
540 available: !greeter.locked
535− teasing: available && greeter.leftTeaserPressed
536 onDashItemSelected: {
541 onDashItemSelected: {
537 greeter.hide()
542 greeter.hide()
538 // Animate if moving between application and dash
543 // Animate if moving between application and dash
build+2
@@ -49,6 +49,8 @@ install_dependencies() {
49 indicators-client-plugin-* \
49 indicators-client-plugin-* \
50 ubuntu-mobile-icons \
50 ubuntu-mobile-icons \
51 unity-notifications-impl-1 \
51 unity-notifications-impl-1 \
52+ unity-scope-home \
53+ unity-lens-applications \
52 || exit 5
54 || exit 5
53}
55}
54
56
debian/changelog+6
@@ -5,6 +5,12 @@ unity8 (7.81.3-0ubuntu1) UNRELEASED; urgency=low
5
5
6 -- Didier Roche <didrocks@ubuntu.com> Wed, 26 Jun 2013 11:01:09 +0200
6 -- Didier Roche <didrocks@ubuntu.com> Wed, 26 Jun 2013 11:01:09 +0200
7
7
8+unity8 (7.81.3) UNRELEASED; urgency=low
9+
10+ * Choose more appropriate values for edge-drag gestures (LP: #1194150)
11+
12+ -- Daniel d'Andrada <daniel.dandrada@canonical.com> Fri, 28 Jun 2013 16:28:24 -0300
13+
8unity8 (7.81.2) saucy; urgency=low
14unity8 (7.81.2) saucy; urgency=low
9
15
10 * Translation updates.
16 * Translation updates.
debian/control+2
@@ -10,6 +10,7 @@ Build-Depends: cmake,
10 libgles2-mesa-dev[armhf],
10 libgles2-mesa-dev[armhf],
11 libhud-client2-dev,
11 libhud-client2-dev,
12 libpulse-dev,
12 libpulse-dev,
13+ libqt5v8-5-private-dev,
13 libunity-core-6.0-dev (<< 7.80),
14 libunity-core-6.0-dev (<< 7.80),
14 libxcb1-dev,
15 libxcb1-dev,
15 pkg-config,
16 pkg-config,
@@ -21,6 +22,7 @@ Build-Depends: cmake,
21 qtbase5-private-dev,
22 qtbase5-private-dev,
22 qtdeclarative5-dev,
23 qtdeclarative5-dev,
23 qtdeclarative5-dev-tools,
24 qtdeclarative5-dev-tools,
25+ qtdeclarative5-private-dev,
24 qtdeclarative5-qtquick2-plugin,
26 qtdeclarative5-qtquick2-plugin,
25 qtdeclarative5-test-plugin,
27 qtdeclarative5-test-plugin,
26 qtdeclarative5-ubuntu-ui-toolkit-plugin,
28 qtdeclarative5-ubuntu-ui-toolkit-plugin,
debian/unity8-private.install+1
@@ -1,5 +1,6 @@
1usr/lib/*/unity8/qml/HudClient
1usr/lib/*/unity8/qml/HudClient
2usr/lib/*/unity8/qml/LightDM
2usr/lib/*/unity8/qml/LightDM
3+usr/lib/*/unity8/qml/ListViewWithPageHeader
3usr/lib/*/unity8/qml/Ubuntu
4usr/lib/*/unity8/qml/Ubuntu
4usr/lib/*/unity8/qml/Unity
5usr/lib/*/unity8/qml/Unity
5usr/lib/*/unity8/qml/Utils
6usr/lib/*/unity8/qml/Utils
plugins/CMakeLists.txt+1
@@ -3,3 +3,4 @@ add_subdirectory(Utils)
3add_subdirectory(Unity)
3add_subdirectory(Unity)
4add_subdirectory(HudClient)
4add_subdirectory(HudClient)
5add_subdirectory(LightDM)
5add_subdirectory(LightDM)
6+add_subdirectory(ListViewWithPageHeader)
plugins/ListViewWithPageHeader/CMakeLists.txtnew file+44
@@ -0,0 +1,44 @@
1+# There's no cmake var for v8 include path :-/
2+# so create one
3+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
4+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
5+
6+include_directories(
7+ ${CMAKE_CURRENT_SOURCE_DIR}
8+ ${CMAKE_CURRENT_BINARY_DIR}
9+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
10+ ${Qt5Quick_INCLUDE_DIRS}
11+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
12+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
13+)
14+
15+remove_definitions(-DQT_NO_KEYWORDS)
16+
17+set(QMLPLUGIN_SRC
18+ plugin.cpp
19+ listviewwithpageheader.cpp
20+ )
21+
22+add_library(ListViewWithPageHeader-qml MODULE
23+ ${QMLPLUGIN_SRC}
24+ )
25+
26+target_link_libraries(ListViewWithPageHeader-qml
27+ ${Qt5Gui_LIBRARIES}
28+ ${Qt5Quick_LIBRARIES}
29+ )
30+
31+qt5_use_modules(ListViewWithPageHeader-qml Qml Quick)
32+
33+# copy qmldir file into build directory for shadow builds
34+file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
35+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
36+ )
37+
38+install(TARGETS ListViewWithPageHeader-qml
39+ DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
40+ )
41+
42+install(FILES qmldir
43+ DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
44+ )
plugins/ListViewWithPageHeader/listviewwithpageheader.cppnew file+1059
@@ -0,0 +1,1059 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+/*
18+ * Some documentation on how this thing works:
19+ *
20+ * A flickable has two very important concepts that define the top and
21+ * height of the flickable area.
22+ * The top is returned in minYExtent()
23+ * The height is set using setContentHeight()
24+ * By changing those two values we can make the list grow up or down
25+ * as needed. e.g. if we are in the middle of the list
26+ * and something that is above the viewport grows, since we do not
27+ * want to change the viewport because of that we just adjust the
28+ * minYExtent so that the list grows up.
29+ *
30+ * The implementation on the list relies on the delegateModel doing
31+ * most of the instantiation work. You call createItem() when you
32+ * need to create an item asking for it async or not. If returns null
33+ * it means the item will be created async and the model will call the
34+ * itemCreated slot with the item.
35+ *
36+ * updatePolish is the central point of dispatch for the work of the
37+ * class. It is called by the scene graph just before drawing the class.
38+ * In it we:
39+ * * Make sure all items are positioned correctly
40+ * * Add/Remove items if needed
41+ * * Update the content height if it was dirty
42+ *
43+ * m_visibleItems contains all the items we have created at the moment.
44+ * Actually not all of them are visible since it includes the ones
45+ * in the cache area we create asynchronously to help performance.
46+ * The first item in m_visibleItems has the m_firstVisibleIndex in
47+ * the model. If you actually want to know what is the first
48+ * item in the viewport you have to find the first non culled element
49+ * in m_visibleItems
50+ *
51+ * All the items (except the header) are childs of m_clipItem which
52+ * is a child of the contentItem() of the flickable (The contentItem()
53+ * is what actually 'moves' in a a flickable). This way
54+ * we can implement the clipping needed so we can have the header
55+ * shown in the middle of the list over the items without the items
56+ * leaking under the header in case it is transparent.
57+ *
58+ * The first item of m_visibleItems is the one that defines the
59+ * positions of all the rest of items (see updatePolish()) and
60+ * this is why sometimes we move it even if it's not the item
61+ * that has triggered the function (i.e. in itemGeometryChanged())
62+ *
63+ * m_visibleItems is a list of ListItem. Each ListItem
64+ * will contain a item and potentially a sectionItem. The sectionItem
65+ * is only there when the list is using sectionDelegate+sectionProperty
66+ * and this is the first item of the section. Each ListItem is vertically
67+ * layouted with the sectionItem first and then the item.
68+ *
69+ * For sectioning we also have a section item alone (m_topSectionItem)
70+ * that is used for the cases we need to show the sticky section item at
71+ * the top of the view.
72+ *
73+ * Each delegate item has a context property called heightToClip that is
74+ * used to communicate to the delegate implementation in case it has to
75+ * clip itself because of overlapping with the top sticky section item.
76+ * This is an implementation decision since it has been agreed it
77+ * is easier to implement the clipping in QML with this info than to
78+ * do it at the C++ level.
79+ *
80+ * Note that minYExtent and height are not always totally accurate, since
81+ * we don't have the items created we can't guess their heights
82+ * so we can only guarantee the values are correct when the first/last
83+ * items of the list are visible, otherwise we just live with good enough
84+ * values that make the list scrollable
85+ *
86+ * There are a few things that are not really implemented or tested properly
87+ * which we don't use at the moment like changing the model, changing
88+ * the section delegate, having a section delegate that changes its size, etc.
89+ * The known missing features are marked with TODOs along the code.
90+ */
91+
92+#include "listviewwithpageheader.h"
93+
94+#include <QDebug>
95+#include <qqmlinfo.h>
96+#include <qqmlengine.h>
97+#include <private/qquickvisualdatamodel_p.h>
98+#include <private/qqmlglobal_p.h>
99+#include <private/qquickitem_p.h>
100+#include <private/qquickanimation_p.h>
101+// #include <private/qquickrectangle_p.h>
102+
103+static const qreal bufferRatio = 0.5;
104+
105+qreal ListViewWithPageHeader::ListItem::height() const
106+{
107+ return m_item->height() + (m_sectionItem ? m_sectionItem->height() : 0);
108+}
109+
110+qreal ListViewWithPageHeader::ListItem::y() const
111+{
112+ return m_item->y() - (m_sectionItem ? m_sectionItem->height() : 0);
113+}
114+
115+void ListViewWithPageHeader::ListItem::setY(qreal newY)
116+{
117+ if (m_sectionItem) {
118+ m_sectionItem->setY(newY);
119+ m_item->setY(newY + m_sectionItem->height());
120+ } else {
121+ m_item->setY(newY);
122+ }
123+}
124+
125+bool ListViewWithPageHeader::ListItem::culled() const
126+{
127+ return QQuickItemPrivate::get(m_item)->culled;
128+}
129+
130+void ListViewWithPageHeader::ListItem::setCulled(bool culled)
131+{
132+ QQuickItemPrivate::get(m_item)->setCulled(culled);
133+ if (m_sectionItem)
134+ QQuickItemPrivate::get(m_sectionItem)->setCulled(culled);
135+}
136+
137+ListViewWithPageHeader::ListViewWithPageHeader()
138+ : m_delegateModel(nullptr)
139+ , m_asyncRequestedIndex(-1)
140+ , m_delegateValidated(false)
141+ , m_firstVisibleIndex(-1)
142+ , m_minYExtent(0)
143+ , m_contentHeightDirty(false)
144+ , m_headerItem(nullptr)
145+ , m_previousContentY(0)
146+ , m_headerItemShownHeight(0)
147+ , m_sectionDelegate(nullptr)
148+ , m_topSectionItem(nullptr)
149+ , m_forceNoClip(false)
150+ , m_inLayout(false)
151+{
152+ m_clipItem = new QQuickItem(contentItem());
153+// m_clipItem = new QQuickRectangle(contentItem());
154+// ((QQuickRectangle*)m_clipItem)->setColor(Qt::gray);
155+
156+ m_headerShowAnimation = new QQuickNumberAnimation(this);
157+ m_headerShowAnimation->setEasing(QEasingCurve::OutQuad);
158+ m_headerShowAnimation->setProperty("contentY");
159+ m_headerShowAnimation->setDuration(200);
160+ m_headerShowAnimation->setTargetObject(this);
161+
162+ connect(this, SIGNAL(contentWidthChanged()), this, SLOT(onContentWidthChanged()));
163+ connect(this, SIGNAL(contentHeightChanged()), this, SLOT(onContentHeightChanged()));
164+ connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
165+ connect(m_headerShowAnimation, SIGNAL(stopped()), this, SLOT(onShowHeaderAnimationFinished()));
166+}
167+
168+ListViewWithPageHeader::~ListViewWithPageHeader()
169+{
170+}
171+
172+QAbstractItemModel *ListViewWithPageHeader::model() const
173+{
174+ return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
175+}
176+
177+void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
178+{
179+ if (model != this->model()) {
180+ if (!m_delegateModel) {
181+ createDelegateModel();
182+ } else {
183+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
184+ }
185+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
186+ connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
187+ Q_EMIT modelChanged();
188+ // TODO?
189+// Q_EMIT contentHeightChanged();
190+// Q_EMIT contentYChanged();
191+ }
192+}
193+
194+QQmlComponent *ListViewWithPageHeader::delegate() const
195+{
196+ return m_delegateModel ? m_delegateModel->delegate() : nullptr;
197+}
198+
199+void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
200+{
201+ if (delegate != this->delegate()) {
202+ if (!m_delegateModel) {
203+ createDelegateModel();
204+ }
205+
206+ // Cleanup the existing items
207+ Q_FOREACH(ListItem *item, m_visibleItems)
208+ releaseItem(item);
209+ m_visibleItems.clear();
210+ m_firstVisibleIndex = -1;
211+ adjustMinYExtent();
212+ setContentY(0);
213+ m_clipItem->setY(0);
214+ if (m_topSectionItem) {
215+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
216+ }
217+
218+ m_delegateModel->setDelegate(delegate);
219+
220+ Q_EMIT delegateChanged();
221+ m_delegateValidated = false;
222+ m_contentHeightDirty = true;
223+ polish();
224+ }
225+}
226+
227+QQuickItem *ListViewWithPageHeader::header() const
228+{
229+ return m_headerItem;
230+}
231+
232+void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
233+{
234+ if (m_headerItem != headerItem) {
235+ qreal oldHeaderHeight = 0;
236+ qreal oldHeaderY = 0;
237+ if (m_headerItem) {
238+ oldHeaderHeight = m_headerItem->height();
239+ oldHeaderY = m_headerItem->y();
240+ m_headerItem->setParentItem(nullptr);
241+ }
242+ m_headerItem = headerItem;
243+ if (m_headerItem) {
244+ m_headerItem->setParentItem(contentItem());
245+ m_headerItem->setZ(1);
246+ }
247+ qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
248+ if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
249+ headerHeightChanged(newHeaderHeight, oldHeaderHeight, oldHeaderY);
250+ polish();
251+ m_contentHeightDirty = true;
252+ }
253+ Q_EMIT headerChanged();
254+ }
255+}
256+
257+QQmlComponent *ListViewWithPageHeader::sectionDelegate() const
258+{
259+ return m_sectionDelegate;
260+}
261+
262+void ListViewWithPageHeader::setSectionDelegate(QQmlComponent *delegate)
263+{
264+ if (delegate != m_sectionDelegate) {
265+ // TODO clean existing sections
266+
267+ m_sectionDelegate = delegate;
268+
269+ m_topSectionItem = getSectionItem(QString());
270+ m_topSectionItem->setZ(3);
271+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
272+
273+ // TODO create sections for existing items
274+
275+ Q_EMIT sectionDelegateChanged();
276+ }
277+}
278+
279+QString ListViewWithPageHeader::sectionProperty() const
280+{
281+ return m_sectionProperty;
282+}
283+
284+void ListViewWithPageHeader::setSectionProperty(const QString &property)
285+{
286+ if (property != m_sectionProperty) {
287+ m_sectionProperty = property;
288+
289+ updateWatchedRoles();
290+
291+ // TODO recreate sections
292+
293+ Q_EMIT sectionPropertyChanged();
294+ }
295+}
296+
297+bool ListViewWithPageHeader::forceNoClip() const
298+{
299+ return m_forceNoClip;
300+}
301+
302+void ListViewWithPageHeader::setForceNoClip(bool noClip)
303+{
304+ if (noClip != m_forceNoClip) {
305+ m_forceNoClip = noClip;
306+ updateClipItem();
307+ Q_EMIT forceNoClipChanged();
308+ }
309+}
310+
311+void ListViewWithPageHeader::positionAtBeginning()
312+{
313+ qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
314+ if (m_firstVisibleIndex != 0) {
315+ // TODO This could be optimized by trying to reuse the interesection
316+ // of items that may end up intersecting between the existing
317+ // m_visibleItems and the items we are creating in the next loop
318+ Q_FOREACH(ListItem *item, m_visibleItems)
319+ releaseItem(item);
320+ m_visibleItems.clear();
321+ m_firstVisibleIndex = -1;
322+
323+ // Create the item 0, it will be already correctly positioned at createItem()
324+ m_clipItem->setY(0);
325+ ListItem *item = createItem(0, false);
326+ // Create the subsequent items
327+ int modelIndex = 1;
328+ qreal pos = item->y() + item->height();
329+ const qreal buffer = height() * bufferRatio;
330+ const qreal bufferTo = height() + buffer;
331+ while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
332+ if (!(item = createItem(modelIndex, false)))
333+ break;
334+ pos += item->height();
335+ ++modelIndex;
336+ }
337+
338+ m_previousContentY = m_visibleItems.first()->y() - headerHeight;
339+ }
340+ setContentY(m_visibleItems.first()->y() + m_clipItem->y() - headerHeight);
341+}
342+
343+void ListViewWithPageHeader::showHeader()
344+{
345+ auto to = qMax(-minYExtent(), contentY() - m_headerItem->height() + m_headerItemShownHeight);
346+ if (to != contentY()) {
347+ m_headerShowAnimation->setTo(to);
348+ m_headerShowAnimation->start();
349+ }
350+}
351+
352+qreal ListViewWithPageHeader::minYExtent() const
353+{
354+// qDebug() << "ListViewWithPageHeader::minYExtent" << m_minYExtent;
355+ return m_minYExtent;
356+}
357+
358+void ListViewWithPageHeader::componentComplete()
359+{
360+ if (m_delegateModel)
361+ m_delegateModel->componentComplete();
362+
363+ QQuickFlickable::componentComplete();
364+
365+ polish();
366+}
367+
368+void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
369+{
370+ QQuickFlickable::viewportMoved(orient);
371+// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
372+ qreal diff = m_previousContentY - contentY();
373+ if (m_headerItem) {
374+ auto oldHeaderItemShownHeight = m_headerItemShownHeight;
375+ if (contentY() < -m_minYExtent) {
376+ // Stick the header item to the top when dragging down
377+ m_headerItem->setY(contentY());
378+ m_headerItem->setHeight(m_headerItem->implicitHeight() + (-m_minYExtent - contentY()));
379+ } else {
380+ m_headerItem->setHeight(m_headerItem->implicitHeight());
381+ // We are going down (but it's not because of the rebound at the end)
382+ // (but the header was not shown by it's own position)
383+ // or the header is partially shown
384+ const bool scrolledUp = m_previousContentY > contentY();
385+ const bool notRebounding = contentY() + height() < contentHeight();
386+ const bool notShownByItsOwn = contentY() + diff > m_headerItem->y() + m_headerItem->height();
387+
388+ if (!scrolledUp && contentY() == -m_minYExtent) {
389+ m_headerItemShownHeight = 0;
390+ m_headerItem->setY(contentY());
391+ } else if ((scrolledUp && notRebounding && notShownByItsOwn) || (m_headerItemShownHeight > 0)) {
392+ m_headerItemShownHeight += diff;
393+ if (contentY() == -m_minYExtent) {
394+ m_headerItemShownHeight = 0;
395+ } else {
396+ m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, m_headerItem->height());
397+ }
398+ if (m_headerItemShownHeight > 0) {
399+ m_headerItem->setY(contentY() - m_headerItem->height() + m_headerItemShownHeight);
400+ } else {
401+ m_headerItem->setY(-m_minYExtent);
402+ }
403+ }
404+ }
405+ // We will be changing the clip item, need to accomadate for it
406+ // otherwise we move the firstItem down/up twice (unless the
407+ // show header animation is running, where we want to keep the viewport stable)
408+ if (!m_headerShowAnimation->isRunning()) {
409+ diff += oldHeaderItemShownHeight - m_headerItemShownHeight;
410+ } else {
411+ diff = -diff;
412+ }
413+ }
414+ if (!m_visibleItems.isEmpty()) {
415+ updateClipItem();
416+ ListItem *firstItem = m_visibleItems.first();
417+ firstItem->setY(firstItem->y() + diff);
418+ if (m_headerShowAnimation->isRunning()) {
419+ adjustMinYExtent();
420+ }
421+ }
422+
423+ m_previousContentY = contentY();
424+ layout();
425+ polish();
426+}
427+
428+void ListViewWithPageHeader::createDelegateModel()
429+{
430+ m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
431+ connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
432+ if (isComponentComplete())
433+ m_delegateModel->componentComplete();
434+ updateWatchedRoles();
435+}
436+
437+void ListViewWithPageHeader::refill()
438+{
439+ if (m_inLayout) {
440+ return;
441+ }
442+ if (!isComponentComplete()) {
443+ return;
444+ }
445+
446+ const qreal buffer = height() * bufferRatio;
447+ const qreal from = contentY();
448+ const qreal to = from + height();
449+ const qreal bufferFrom = from - buffer;
450+ const qreal bufferTo = to + buffer;
451+
452+ bool added = addVisibleItems(from, to, false);
453+ bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
454+ added |= addVisibleItems(bufferFrom, bufferTo, true);
455+
456+ if (added || removed) {
457+ m_contentHeightDirty = true;
458+ }
459+}
460+
461+bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
462+{
463+ if (!delegate())
464+ return false;
465+
466+ if (m_delegateModel->count() == 0)
467+ return false;
468+
469+ ListItem *item;
470+// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
471+
472+ int modelIndex = 0;
473+ qreal pos = 0;
474+ if (!m_visibleItems.isEmpty()) {
475+ modelIndex = m_firstVisibleIndex + m_visibleItems.count();
476+ item = m_visibleItems.last();
477+ pos = item->y() + item->height() + m_clipItem->y();
478+ }
479+ bool changed = false;
480+// qDebug() << (modelIndex < m_delegateModel->count()) << pos << fillTo;
481+ while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
482+// qDebug() << "refill: append item" << modelIndex << "pos" << pos << "asynchronous" << asynchronous;
483+ if (!(item = createItem(modelIndex, asynchronous)))
484+ break;
485+ pos += item->height();
486+ ++modelIndex;
487+ changed = true;
488+ }
489+
490+ modelIndex = 0;
491+ pos = 0;
492+ if (!m_visibleItems.isEmpty()) {
493+ modelIndex = m_firstVisibleIndex - 1;
494+ item = m_visibleItems.first();
495+ pos = item->y() + m_clipItem->y();
496+ }
497+ while (modelIndex >= 0 && pos > fillFrom) {
498+// qDebug() << "refill: prepend item" << modelIndex << "pos" << pos << "fillFrom" << fillFrom << "asynchronous" << asynchronous;
499+ if (!(item = createItem(modelIndex, asynchronous)))
500+ break;
501+ pos -= item->height();
502+ --modelIndex;
503+ changed = true;
504+ }
505+
506+ return changed;
507+}
508+
509+void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
510+{
511+ QQuickItem *item = listItem->m_item;
512+ QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
513+ if (flags & QQuickVisualModel::Destroyed) {
514+ item->setParentItem(nullptr);
515+ }
516+ delete listItem->m_sectionItem;
517+ delete listItem;
518+}
519+
520+void ListViewWithPageHeader::releaseItem(ListItem *listItem)
521+{
522+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(listItem->m_item);
523+ itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
524+ m_itemsToRelease << listItem;
525+}
526+
527+void ListViewWithPageHeader::updateWatchedRoles()
528+{
529+ if (m_delegateModel) {
530+ QList<QByteArray> roles;
531+ if (!m_sectionProperty.isEmpty())
532+ roles << m_sectionProperty.toUtf8();
533+ m_delegateModel->setWatchedRoles(roles);
534+ }
535+}
536+
537+QQuickItem *ListViewWithPageHeader::getSectionItem(int modelIndex, bool alreadyInserted)
538+{
539+ if (!m_sectionDelegate)
540+ return nullptr;
541+
542+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
543+ if (modelIndex > 0) {
544+ const QString prevSection = m_delegateModel->stringValue(modelIndex - 1, m_sectionProperty);
545+ if (section == prevSection)
546+ return nullptr;
547+ }
548+ if (modelIndex + 1 < model()->rowCount() && !alreadyInserted) {
549+ // Already inserted items can't steal next section header
550+ const QString nextSection = m_delegateModel->stringValue(modelIndex + 1, m_sectionProperty);
551+ if (section == nextSection) {
552+ // Steal the section header
553+ ListItem *nextItem = itemAtIndex(modelIndex); // Not +1 since not yet inserted into m_visibleItems
554+ if (nextItem) {
555+ QQuickItem *sectionItem = nextItem->m_sectionItem;
556+ nextItem->m_sectionItem = nullptr;
557+ return sectionItem;
558+ }
559+ }
560+ }
561+
562+ return getSectionItem(section);
563+}
564+
565+QQuickItem *ListViewWithPageHeader::getSectionItem(const QString &sectionText)
566+{
567+ QQuickItem *sectionItem = nullptr;
568+
569+ QQmlContext *creationContext = m_sectionDelegate->creationContext();
570+ QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this));
571+ context->setContextProperty(QLatin1String("section"), sectionText);
572+ QObject *nobj = m_sectionDelegate->beginCreate(context);
573+ if (nobj) {
574+ QQml_setParent_noEvent(context, nobj);
575+ sectionItem = qobject_cast<QQuickItem *>(nobj);
576+ if (!sectionItem) {
577+ delete nobj;
578+ } else {
579+ sectionItem->setZ(2);
580+ QQml_setParent_noEvent(sectionItem, m_clipItem);
581+ sectionItem->setParentItem(m_clipItem);
582+ }
583+ } else {
584+ delete context;
585+ }
586+ m_sectionDelegate->completeCreate();
587+
588+ // TODO attach to sectionItem so we can accomodate to it changing its height
589+
590+ return sectionItem;
591+}
592+
593+bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
594+{
595+// qDebug() << "ListViewWithPageHeader::removeNonVisibleItems" << bufferFrom << bufferTo;
596+ // Do not remove items if we are overshooting up or down, since we'll come back
597+ // to the "stable" position and delete/create items without any reason
598+ if (contentY() < -m_minYExtent) {
599+ return false;
600+ } else if (contentY() + height() > contentHeight()) {
601+ return false;
602+ }
603+ bool changed = false;
604+
605+ bool foundVisible = false;
606+ int i = 0;
607+ int removedItems = 0;
608+ const auto oldFirstVisibleIndex = m_firstVisibleIndex;
609+ while (i < m_visibleItems.count()) {
610+ ListItem *item = m_visibleItems[i];
611+ const qreal pos = item->y() + m_clipItem->y();
612+// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
613+ if (pos + item->height() < bufferFrom || pos > bufferTo) {
614+// qDebug() << "Releasing" << i << (pos + item->height() < bufferFrom) << pos + item->height() << bufferFrom << (pos > bufferTo) << pos << bufferTo;
615+ releaseItem(item);
616+ m_visibleItems.removeAt(i);
617+ changed = true;
618+ ++removedItems;
619+ } else {
620+ if (!foundVisible) {
621+ foundVisible = true;
622+ const int itemIndex = m_firstVisibleIndex + removedItems + i;
623+ m_firstVisibleIndex = itemIndex;
624+ }
625+ ++i;
626+ }
627+ }
628+ if (m_firstVisibleIndex != oldFirstVisibleIndex) {
629+ adjustMinYExtent();
630+ }
631+
632+ return changed;
633+}
634+
635+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
636+{
637+// qDebug() << "CREATE ITEM" << modelIndex;
638+ if (asynchronous && m_asyncRequestedIndex != -1)
639+ return nullptr;
640+
641+ m_asyncRequestedIndex = -1;
642+ QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
643+ if (!item) {
644+ m_asyncRequestedIndex = modelIndex;
645+ return 0;
646+ } else {
647+// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
648+ ListItem *listItem = new ListItem;
649+ listItem->m_item = item;
650+ listItem->m_sectionItem = getSectionItem(modelIndex, false /*Not yet inserted into m_visibleItems*/);
651+ QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
652+ ListItem *prevItem = itemAtIndex(modelIndex - 1);
653+ bool lostItem = false; // Is an item that we requested async but because of model changes
654+ // it is no longer attached to any of the existing items (has no prev nor next item)
655+ // nor is the first item
656+ if (prevItem) {
657+ listItem->setY(prevItem->y() + prevItem->height());
658+ } else {
659+ ListItem *currItem = itemAtIndex(modelIndex);
660+ if (currItem) {
661+ // There's something already in m_visibleItems at out index, meaning this is an insert, so attach to its top
662+ listItem->setY(currItem->y() - listItem->height());
663+ } else {
664+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
665+ if (nextItem) {
666+ listItem->setY(nextItem->y() - listItem->height());
667+ } else if (modelIndex == 0 && m_headerItem) {
668+ listItem->setY(m_headerItem->height());
669+ } else if (!m_visibleItems.isEmpty()) {
670+ lostItem = true;
671+ }
672+ }
673+ }
674+ if (lostItem) {
675+ releaseItem(listItem);
676+ listItem = nullptr;
677+ } else {
678+ listItem->setCulled(listItem->y() + listItem->height() + m_clipItem->y() < contentY() || listItem->y() + m_clipItem->y() >= contentY() + height());
679+ if (m_visibleItems.isEmpty()) {
680+ m_visibleItems << listItem;
681+ } else {
682+ m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
683+ }
684+ if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
685+ m_firstVisibleIndex = modelIndex;
686+ polish();
687+ }
688+ adjustMinYExtent();
689+ m_contentHeightDirty = true;
690+ }
691+ return listItem;
692+ }
693+}
694+
695+void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
696+{
697+// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
698+
699+ item->setParentItem(m_clipItem);
700+ QQmlContext *context = QQmlEngine::contextForObject(item)->parentContext();
701+ context->setContextProperty(QLatin1String("ListViewWithPageHeader"), this);
702+ context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
703+ if (modelIndex == m_asyncRequestedIndex) {
704+ createItem(modelIndex, false);
705+ refill();
706+ }
707+}
708+
709+void ListViewWithPageHeader::updateClipItem()
710+{
711+ m_clipItem->setHeight(height() - m_headerItemShownHeight);
712+ m_clipItem->setY(contentY() + m_headerItemShownHeight);
713+ m_clipItem->setClip(!m_forceNoClip && m_headerItemShownHeight > 0);
714+}
715+
716+void ListViewWithPageHeader::onContentHeightChanged()
717+{
718+ updateClipItem();
719+}
720+
721+void ListViewWithPageHeader::onContentWidthChanged()
722+{
723+ m_clipItem->setWidth(contentItem()->width());
724+}
725+
726+void ListViewWithPageHeader::onHeightChanged()
727+{
728+ polish();
729+}
730+
731+void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
732+{
733+ // TODO Do something with reset
734+// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
735+ const auto oldFirstVisibleIndex = m_firstVisibleIndex;
736+
737+ Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
738+// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
739+ if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
740+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
741+ // If all the items we are removing are either not created or culled
742+ // we have to grow down to avoid viewport changing
743+ bool growDown = true;
744+ for (int i = 0; growDown && i < remove.count; ++i) {
745+ const int modelIndex = remove.index + i;
746+ ListItem *item = itemAtIndex(modelIndex);
747+ if (item && !item->culled()) {
748+ growDown = false;
749+ }
750+ }
751+ for (int i = remove.count - 1; i >= 0; --i) {
752+ const int visibleIndex = remove.index + i - m_firstVisibleIndex;
753+ if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
754+ ListItem *item = m_visibleItems[visibleIndex];
755+ // Pass the section item down if needed
756+ if (item->m_sectionItem && visibleIndex + 1 < m_visibleItems.count()) {
757+ ListItem *nextItem = m_visibleItems[visibleIndex + 1];
758+ if (!nextItem->m_sectionItem) {
759+ nextItem->m_sectionItem = item->m_sectionItem;
760+ item->m_sectionItem = nullptr;
761+ }
762+ }
763+ releaseItem(item);
764+ m_visibleItems.removeAt(visibleIndex);
765+ }
766+ }
767+ if (growDown) {
768+ adjustMinYExtent();
769+ } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
770+ // We removed the first item that is the one that positions the rest
771+ // position the new first item correctly
772+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
773+ }
774+ } else if (remove.index + remove.count <= m_firstVisibleIndex) {
775+ m_firstVisibleIndex -= remove.count;
776+ }
777+ for (int i = remove.count - 1; i >= 0; --i) {
778+ const int modelIndex = remove.index + i;
779+ if (modelIndex == m_asyncRequestedIndex) {
780+ m_asyncRequestedIndex = -1;
781+ } else if (modelIndex < m_asyncRequestedIndex) {
782+ m_asyncRequestedIndex--;
783+ }
784+ }
785+ }
786+
787+ Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
788+// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
789+ const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
790+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
791+ if (insertingInValidIndexes || firstItemWithViewOnTop)
792+ {
793+ // If the items we are adding won't be really visible
794+ // we grow up instead of down to not change the viewport
795+ bool growUp = false;
796+ if (!firstItemWithViewOnTop) {
797+ for (int i = 0; i < m_visibleItems.count(); ++i) {
798+ if (!m_visibleItems[i]->culled()) {
799+ if (insert.index <= m_firstVisibleIndex + i) {
800+ growUp = true;
801+ }
802+ break;
803+ }
804+ }
805+ }
806+
807+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
808+ for (int i = insert.count - 1; i >= 0; --i) {
809+ const int modelIndex = insert.index + i;
810+ ListItem *item = createItem(modelIndex, false);
811+ if (growUp) {
812+ ListItem *firstItem = m_visibleItems.first();
813+ firstItem->setY(firstItem->y() - item->height());
814+ adjustMinYExtent();
815+ }
816+ // Adding an item may break a "same section" chain, so check
817+ // if we need adding a new section item
818+ if (m_sectionDelegate) {
819+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
820+ if (nextItem && !nextItem->m_sectionItem) {
821+ nextItem->m_sectionItem = getSectionItem(modelIndex + 1, true /* alredy inserted into m_visibleItems*/);
822+ if (growUp && nextItem->m_sectionItem) {
823+ ListItem *firstItem = m_visibleItems.first();
824+ firstItem->setY(firstItem->y() - nextItem->m_sectionItem->height());
825+ adjustMinYExtent();
826+ }
827+ }
828+ }
829+ }
830+ if (firstItemWithViewOnTop) {
831+ ListItem *firstItem = m_visibleItems.first();
832+ firstItem->setY(oldFirstValidIndexPos);
833+ }
834+ } else if (insert.index <= m_firstVisibleIndex) {
835+ m_firstVisibleIndex += insert.count;
836+ }
837+
838+ for (int i = insert.count - 1; i >= 0; --i) {
839+ const int modelIndex = insert.index + i;
840+ if (modelIndex <= m_asyncRequestedIndex) {
841+ m_asyncRequestedIndex++;
842+ }
843+ }
844+ }
845+
846+ if (m_firstVisibleIndex != oldFirstVisibleIndex) {
847+ adjustMinYExtent();
848+ }
849+
850+ layout();
851+ polish();
852+ m_contentHeightDirty = true;
853+}
854+
855+void ListViewWithPageHeader::onShowHeaderAnimationFinished()
856+{
857+ m_contentHeightDirty = true;
858+ polish();
859+}
860+
861+void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QRectF &newGeometry, const QRectF &oldGeometry)
862+{
863+ const qreal heightDiff = newGeometry.height() - oldGeometry.height();
864+ if (heightDiff != 0) {
865+ if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
866+ ListItem *firstItem = m_visibleItems.first();
867+ firstItem->setY(firstItem->y() - heightDiff);
868+ adjustMinYExtent();
869+ layout();
870+ }
871+ refill();
872+ adjustMinYExtent();
873+ polish();
874+ m_contentHeightDirty = true;
875+ }
876+}
877+
878+void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
879+{
880+ const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
881+ if (m_headerItemShownHeight > 0) {
882+ // If the header is shown because of the clip
883+ // Change its size
884+ m_headerItemShownHeight += heightDiff;
885+ m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, newHeaderHeight);
886+ updateClipItem();
887+ adjustMinYExtent();
888+ } else {
889+ if (oldHeaderY + oldHeaderHeight > contentY()) {
890+ // If the header is shown because its position
891+ // Change its size
892+ ListItem *firstItem = m_visibleItems.first();
893+ firstItem->setY(firstItem->y() + heightDiff);
894+ layout();
895+ } else {
896+ // If the header is not on screen, just change the start of the list
897+ // so the viewport is not changed
898+ adjustMinYExtent();
899+ }
900+ }
901+}
902+
903+
904+void ListViewWithPageHeader::adjustMinYExtent()
905+{
906+ if (m_visibleItems.isEmpty()) {
907+ m_minYExtent = 0;
908+ } else {
909+ qreal nonCreatedHeight = 0;
910+ if (m_firstVisibleIndex != 0) {
911+ // Calculate the average height of items to estimate the position of the list start
912+ const int visibleItems = m_visibleItems.count();
913+ qreal visibleItemsHeight = 0;
914+ Q_FOREACH(ListItem *item, m_visibleItems) {
915+ visibleItemsHeight += item->height();
916+ }
917+ nonCreatedHeight = m_firstVisibleIndex * visibleItemsHeight / visibleItems;
918+// qDebug() << m_firstVisibleIndex << visibleItemsHeight << visibleItems << nonCreatedHeight;
919+ }
920+ m_minYExtent = nonCreatedHeight - m_visibleItems.first()->y() - m_clipItem->y() + (m_headerItem ? m_headerItem->implicitHeight() : 0);
921+ }
922+}
923+
924+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
925+{
926+ const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
927+ if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
928+ return m_visibleItems[visibleIndexedModelIndex];
929+
930+ return nullptr;
931+}
932+
933+void ListViewWithPageHeader::layout()
934+{
935+ if (m_inLayout)
936+ return;
937+
938+ m_inLayout = true;
939+ if (!m_visibleItems.isEmpty()) {
940+ const qreal visibleFrom = contentY() - m_clipItem->y() + m_headerItemShownHeight;
941+ const qreal visibleTo = contentY() + height() - m_clipItem->y();
942+
943+ qreal pos = m_visibleItems.first()->y();
944+
945+// qDebug() << "ListViewWithPageHeader::layout Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
946+ int firstReallyVisibleItem = -1;
947+ int modelIndex = m_firstVisibleIndex;
948+ Q_FOREACH(ListItem *item, m_visibleItems) {
949+ const bool cull = pos + item->height() < visibleFrom || pos >= visibleTo;
950+ item->setCulled(cull);
951+ item->setY(pos);
952+ if (!cull && firstReallyVisibleItem == -1) {
953+ firstReallyVisibleItem = modelIndex;
954+ if (m_topSectionItem) {
955+ // Update the top sticky section header
956+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
957+ QQmlContext *context = QQmlEngine::contextForObject(m_topSectionItem)->parentContext();
958+ context->setContextProperty(QLatin1String("section"), section);
959+
960+ // Positing the top section sticky item is a two step process
961+ // First we set it either we cull it (because it doesn't need to be sticked to the top)
962+ // or stick it to the top
963+ // Then after the loop we'll make sure that if there's another section just below it
964+ // pushed the sticky section up to make it disappear
965+ const qreal topSectionStickPos = m_headerItemShownHeight + contentY() - m_clipItem->y();
966+ if (topSectionStickPos <= pos) {
967+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
968+ if (item->m_sectionItem) {
969+ // This seems it should happen since why would we cull the top section
970+ // if the first visible item has no section header? This only happens briefly
971+ // when increasing the height of a list that is at the bottom, the m_topSectionItem
972+ // gets shown shortly in the next polish call
973+ QQuickItemPrivate::get(item->m_sectionItem)->setCulled(false);
974+ }
975+ } else {
976+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(false);
977+ m_topSectionItem->setY(topSectionStickPos);
978+ if (item->m_sectionItem) {
979+ QQuickItemPrivate::get(item->m_sectionItem)->setCulled(true);
980+ }
981+ }
982+ }
983+ }
984+ QQmlContext *context = QQmlEngine::contextForObject(item->m_item)->parentContext();
985+ const qreal clipFrom = visibleFrom + (!item->m_sectionItem && m_topSectionItem && !QQuickItemPrivate::get(m_topSectionItem)->culled ? m_topSectionItem->height() : 0);
986+ if (!cull && pos < clipFrom) {
987+ context->setContextProperty(QLatin1String("heightToClip"), clipFrom - pos);
988+ } else {
989+ context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
990+ }
991+// qDebug() << "ListViewWithPageHeader::layout" << item->m_item;
992+ pos += item->height();
993+ ++modelIndex;
994+ }
995+
996+ // Second step of section sticky item positioning
997+ // Look at the next section header, check if it's pushing up the sticky one
998+ if (m_topSectionItem) {
999+ if (firstReallyVisibleItem >= 0) {
1000+ for (int i = firstReallyVisibleItem - m_firstVisibleIndex + 1; i < m_visibleItems.count(); ++i) {
1001+ ListItem *item = m_visibleItems[i];
1002+ if (item->m_sectionItem) {
1003+ if (m_topSectionItem->y() + m_topSectionItem->height() > item->y()) {
1004+ m_topSectionItem->setY(item->y() - m_topSectionItem->height());
1005+ }
1006+ break;
1007+ }
1008+ }
1009+ }
1010+ }
1011+ }
1012+ m_inLayout = false;
1013+}
1014+
1015+void ListViewWithPageHeader::updatePolish()
1016+{
1017+ Q_FOREACH(ListItem *item, m_itemsToRelease)
1018+ reallyReleaseItem(item);
1019+ m_itemsToRelease.clear();
1020+
1021+ if (!model())
1022+ return;
1023+
1024+ layout();
1025+
1026+ refill();
1027+
1028+ if (m_contentHeightDirty) {
1029+ qreal contentHeight;
1030+ if (m_visibleItems.isEmpty()) {
1031+ contentHeight = m_headerItem ? m_headerItem->height() : 0;
1032+ } else {
1033+ const int modelCount = model()->rowCount();
1034+ const int visibleItems = m_visibleItems.count();
1035+ const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
1036+ qreal nonCreatedHeight = 0;
1037+ if (lastValidIndex != modelCount - 1) {
1038+ const int visibleItems = m_visibleItems.count();
1039+ qreal visibleItemsHeight = 0;
1040+ Q_FOREACH(ListItem *item, m_visibleItems) {
1041+ visibleItemsHeight += item->height();
1042+ }
1043+ const int unknownSizes = modelCount - (m_firstVisibleIndex + visibleItems);
1044+ nonCreatedHeight = unknownSizes * visibleItemsHeight / visibleItems;
1045+ }
1046+ ListItem *item = m_visibleItems.last();
1047+ contentHeight = nonCreatedHeight + item->y() + item->height() + m_clipItem->y();
1048+ if (m_firstVisibleIndex != 0) {
1049+ // Make sure that if we are shrinking we tell the view we still fit
1050+ m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
1051+ }
1052+ }
1053+
1054+ m_contentHeightDirty = false;
1055+ setContentHeight(contentHeight);
1056+ }
1057+}
1058+
1059+#include "moc_listviewwithpageheader.cpp"
plugins/ListViewWithPageHeader/listviewwithpageheader.hnew file+180
@@ -0,0 +1,180 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef LISTVIEWWITHPAGEHEADER_H
18+#define LISTVIEWWITHPAGEHEADER_H
19+
20+#include <private/qquickitemchangelistener_p.h>
21+#include <private/qquickflickable_p.h>
22+
23+class QAbstractItemModel;
24+class QQuickChangeSet;
25+class QQuickNumberAnimation;
26+class QQuickVisualDataModel;
27+
28+
29+/**
30+ Note for users of this class
31+
32+ ListViewWithPageHeader already loads delegates async when appropiate so if
33+ your delegate uses a Loader you should not enable the asynchronous feature since
34+ that will need to introduce sizing problems
35+
36+ With the double async it may happen what while we are scrolling down
37+ we reach to a point where given the size of the just created delegate with loader not yet loaded (which will be very close to 0)
38+ we are already "at the end" of the list, but then a few milliseconds later the loader finishes loading and we could
39+ have kept scrolling. This is specially visible at the end of the list where you realize
40+ that scrolling ended a bit before the end of the list but the speed of the flicking was good
41+ to reach the end
42+
43+ By not having the second async we get a better sizing when the delegate is created and things work better
44+*/
45+
46+class ListViewWithPageHeader : public QQuickFlickable, public QQuickItemChangeListener
47+{
48+ Q_OBJECT
49+ Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
50+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
51+ Q_PROPERTY(QQuickItem *pageHeader READ header WRITE setHeader NOTIFY headerChanged)
52+ Q_PROPERTY(QQmlComponent *sectionDelegate READ sectionDelegate WRITE setSectionDelegate NOTIFY sectionDelegateChanged)
53+ Q_PROPERTY(QString sectionProperty READ sectionProperty WRITE setSectionProperty NOTIFY sectionPropertyChanged)
54+ Q_PROPERTY(bool forceNoClip READ forceNoClip WRITE setForceNoClip NOTIFY forceNoClipChanged)
55+
56+ friend class ListViewWithPageHeaderTest;
57+ friend class ListViewWithPageHeaderTestSection;
58+
59+public:
60+ ListViewWithPageHeader();
61+ ~ListViewWithPageHeader();
62+
63+ QAbstractItemModel *model() const;
64+ void setModel(QAbstractItemModel *model);
65+
66+ QQmlComponent *delegate() const;
67+ void setDelegate(QQmlComponent *delegate);
68+
69+ QQuickItem *header() const;
70+ void setHeader(QQuickItem *header);
71+
72+ QQmlComponent *sectionDelegate() const;
73+ void setSectionDelegate(QQmlComponent *delegate);
74+
75+ QString sectionProperty() const;
76+ void setSectionProperty(const QString &property);
77+
78+ bool forceNoClip() const;
79+ void setForceNoClip(bool noClip);
80+
81+ Q_INVOKABLE void positionAtBeginning();
82+ Q_INVOKABLE void showHeader();
83+
84+Q_SIGNALS:
85+ void modelChanged();
86+ void delegateChanged();
87+ void headerChanged();
88+ void sectionDelegateChanged();
89+ void sectionPropertyChanged();
90+ void forceNoClipChanged();
91+
92+protected:
93+ void componentComplete();
94+ void viewportMoved(Qt::Orientations orient);
95+ qreal minYExtent() const;
96+ void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry);
97+ void updatePolish();
98+
99+private Q_SLOTS:
100+ void itemCreated(int modelIndex, QQuickItem *item);
101+ void onContentHeightChanged();
102+ void onContentWidthChanged();
103+ void onHeightChanged();
104+ void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
105+ void onShowHeaderAnimationFinished();
106+
107+private:
108+ class ListItem
109+ {
110+ public:
111+ qreal height() const;
112+
113+ qreal y() const;
114+ void setY(qreal newY);
115+
116+ bool culled() const;
117+ void setCulled(bool culled);
118+
119+ QQuickItem *m_item;
120+ QQuickItem *m_sectionItem;
121+ };
122+
123+ void createDelegateModel();
124+
125+ void layout();
126+ void refill();
127+ bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
128+ bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
129+ ListItem *createItem(int modelIndex, bool asynchronous);
130+
131+ void adjustMinYExtent();
132+ void updateClipItem();
133+ void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
134+ ListItem *itemAtIndex(int modelIndex) const; // Returns the item at modelIndex if has been created
135+ void releaseItem(ListItem *item);
136+ void reallyReleaseItem(ListItem *item);
137+ void updateWatchedRoles();
138+ QQuickItem *getSectionItem(int modelIndex, bool alreadyInserted);
139+ QQuickItem *getSectionItem(const QString &sectionText);
140+
141+ QQuickVisualDataModel *m_delegateModel;
142+
143+ // Index we are waiting because we requested it asynchronously
144+ int m_asyncRequestedIndex;
145+
146+ // Used to only give a warning once if the delegate does not return objects
147+ bool m_delegateValidated;
148+
149+ // Visible indexes, [0] is m_firstValidIndex, [0+1] is m_firstValidIndex +1, ...
150+ QList<ListItem *> m_visibleItems;
151+ int m_firstVisibleIndex;
152+
153+ qreal m_minYExtent;
154+
155+ QQuickItem *m_clipItem;
156+
157+ // If any of the heights has changed
158+ // or new items have been added/removed
159+ bool m_contentHeightDirty;
160+
161+ QQuickItem *m_headerItem;
162+ qreal m_previousContentY;
163+ qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
164+ // i.e. it's being shown after dragging down in the middle of the list
165+ QQuickNumberAnimation *m_headerShowAnimation;
166+
167+ QQmlComponent *m_sectionDelegate;
168+ QString m_sectionProperty;
169+ QQuickItem *m_topSectionItem;
170+
171+ bool m_forceNoClip;
172+ bool m_inLayout;
173+
174+ // Qt 5.0 doesn't like releasing the items just after itemCreated
175+ // so we delay the releasing until the next updatePolish
176+ QList<ListItem *> m_itemsToRelease;
177+};
178+
179+
180+#endif
plugins/ListViewWithPageHeader/plugin.cppnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ */
17+
18+#include "plugin.h"
19+
20+#include "listviewwithpageheader.h"
21+
22+#include <QAbstractItemModel>
23+
24+void ListViewWithPageHeaderPlugin::registerTypes(const char *uri)
25+{
26+ Q_ASSERT(uri == QLatin1String("ListViewWithPageHeader"));
27+ qmlRegisterType<QAbstractItemModel>();
28+ qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
29+}
plugins/ListViewWithPageHeader/plugin.hnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2012 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ */
17+
18+#ifndef LISTVIEWWITHPAGEHEADER_PLUGIN_H
19+#define LISTVIEWWITHPAGEHEADER_PLUGIN_H
20+
21+#include <QtQml/QQmlEngine>
22+#include <QtQml/QQmlExtensionPlugin>
23+
24+class ListViewWithPageHeaderPlugin : public QQmlExtensionPlugin
25+{
26+ Q_OBJECT
27+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
28+
29+public:
30+ void registerTypes(const char *uri);
31+};
32+
33+#endif
plugins/ListViewWithPageHeader/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module ListViewWithPageHeader
2+plugin ListViewWithPageHeader-qml
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+52−2
@@ -155,6 +155,15 @@ qreal DirectionalDragArea::distance() const
155 }
155 }
156}
156}
157
157
158+qreal DirectionalDragArea::sceneDistance() const
159+{
160+ if (Direction::isHorizontal(m_direction)) {
161+ return m_previousScenePos.x() - m_startScenePos.x();
162+ } else {
163+ return m_previousScenePos.y() - m_startScenePos.y();
164+ }
165+}
166+
158qreal DirectionalDragArea::touchX() const
167qreal DirectionalDragArea::touchX() const
159{
168{
160 return m_previousPos.x();
169 return m_previousPos.x();
@@ -165,6 +174,16 @@ qreal DirectionalDragArea::touchY() const
165 return m_previousPos.y();
174 return m_previousPos.y();
166}
175}
167
176
177+qreal DirectionalDragArea::touchSceneX() const
178+{
179+ return m_previousScenePos.x();
180+}
181+
182+qreal DirectionalDragArea::touchSceneY() const
183+{
184+ return m_previousScenePos.y();
185+}
186+
168void DirectionalDragArea::touchEvent(QTouchEvent *event)
187void DirectionalDragArea::touchEvent(QTouchEvent *event)
169{
188{
170 if (!isEnabled() || !isVisible()) {
189 if (!isEnabled() || !isVisible()) {
@@ -192,14 +211,17 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
192{
211{
193 if ((event->touchPointStates() && (Qt::TouchPointPressed || Qt::TouchPointMoved))
212 if ((event->touchPointStates() && (Qt::TouchPointPressed || Qt::TouchPointMoved))
194 && event->touchPoints().count() == 1) {
213 && event->touchPoints().count() == 1) {
214+ const QTouchEvent::TouchPoint &touchPoint = event->touchPoints()[0];
195− m_startPos = event->touchPoints()[0].pos();
215+ m_startPos = touchPoint.pos();
216+ m_startScenePos = touchPoint.scenePos();
196− m_touchId = event->touchPoints()[0].id();
217+ m_touchId = touchPoint.id();
197 m_dampedPos.reset(m_startPos);
218 m_dampedPos.reset(m_startPos);
198 updateVelocityCalculator(m_startPos);
219 updateVelocityCalculator(m_startPos);
199 m_velocityCalculator->reset();
220 m_velocityCalculator->reset();
200 m_numSamplesOnLastSpeedCheck = 0;
221 m_numSamplesOnLastSpeedCheck = 0;
201 m_silenceTime = 0;
222 m_silenceTime = 0;
202 setPreviousPos(m_startPos);
223 setPreviousPos(m_startPos);
224+ setPreviousScenePos(m_startScenePos);
203
225
204 if (m_distanceThreshold > 0)
226 if (m_distanceThreshold > 0)
205 setStatus(Undecided);
227 setStatus(Undecided);
@@ -242,6 +264,7 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
242 }
264 }
243
265
244 setPreviousPos(touchPos);
266 setPreviousPos(touchPos);
267+ setPreviousScenePos(touchPoint->scenePos());
245
268
246 if (movedFarEnough(touchPos)) {
269 if (movedFarEnough(touchPos)) {
247 setStatus(Recognized);
270 setStatus(Recognized);
@@ -253,6 +276,7 @@ void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
253 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
276 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
254
277
255 setPreviousPos(touchPoint->pos());
278 setPreviousPos(touchPoint->pos());
279+ setPreviousScenePos(touchPoint->scenePos());
256
280
257 if (touchPoint->state() == Qt::TouchPointReleased) {
281 if (touchPoint->state() == Qt::TouchPointReleased) {
258 setStatus(WaitingForTouch);
282 setStatus(WaitingForTouch);
@@ -367,6 +391,10 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
367 m_recognitionTimer->start();
391 m_recognitionTimer->start();
368 Q_EMIT draggingChanged(true);
392 Q_EMIT draggingChanged(true);
369 break;
393 break;
394+ case Recognized:
395+ if (oldStatus == WaitingForTouch)
396+ Q_EMIT draggingChanged(true);
397+ break;
370 case Rejected:
398 case Rejected:
371 Q_EMIT draggingChanged(false);
399 Q_EMIT draggingChanged(false);
372 break;
400 break;
@@ -398,6 +426,28 @@ void DirectionalDragArea::setPreviousPos(QPointF point)
398 }
426 }
399}
427}
400
428
429+void DirectionalDragArea::setPreviousScenePos(QPointF point)
430+{
431+ Q_ASSERT(m_status != Rejected);
432+
433+ bool xChanged = m_previousScenePos.x() != point.x();
434+ bool yChanged = m_previousScenePos.y() != point.y();
435+
436+ m_previousScenePos = point;
437+
438+ if (xChanged) {
439+ Q_EMIT touchSceneXChanged(point.x());
440+ if (Direction::isHorizontal(m_direction))
441+ Q_EMIT sceneDistanceChanged(sceneDistance());
442+ }
443+
444+ if (yChanged) {
445+ Q_EMIT touchSceneYChanged(point.y());
446+ if (Direction::isVertical(m_direction))
447+ Q_EMIT sceneDistanceChanged(sceneDistance());
448+ }
449+}
450+
401void DirectionalDragArea::updateVelocityCalculator(QPointF point)
451void DirectionalDragArea::updateVelocityCalculator(QPointF point)
402{
452{
403 if (Direction::isHorizontal(m_direction)) {
453 if (Direction::isHorizontal(m_direction)) {
plugins/Ubuntu/Gestures/DirectionalDragArea.h+19−1
@@ -60,10 +60,18 @@ class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
60 // DirectionalDragArea's direction.
60 // DirectionalDragArea's direction.
61 Q_PROPERTY(qreal distance READ distance NOTIFY distanceChanged)
61 Q_PROPERTY(qreal distance READ distance NOTIFY distanceChanged)
62
62
63+ // The distance travelled by the finger along the axis specified by
64+ // DirectionalDragArea's direction in scene coordinates
65+ Q_PROPERTY(qreal sceneDistance READ sceneDistance NOTIFY sceneDistanceChanged)
66+
63− // Position of the touch point performing the drag.
67+ // Position of the touch point performing the drag relative to this item.
64 Q_PROPERTY(qreal touchX READ touchX NOTIFY touchXChanged)
68 Q_PROPERTY(qreal touchX READ touchX NOTIFY touchXChanged)
65 Q_PROPERTY(qreal touchY READ touchY NOTIFY touchYChanged)
69 Q_PROPERTY(qreal touchY READ touchY NOTIFY touchYChanged)
66
70
71+ // Position of the touch point performing the drag, in scene's coordinate system
72+ Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
73+ Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
74+
67 // The current status of the directional drag gesture area.
75 // The current status of the directional drag gesture area.
68 Q_PROPERTY(Status status READ status NOTIFY statusChanged)
76 Q_PROPERTY(Status status READ status NOTIFY statusChanged)
69
77
@@ -146,10 +154,14 @@ public:
146 Status status() const { return m_status; }
154 Status status() const { return m_status; }
147
155
148 qreal distance() const;
156 qreal distance() const;
157+ qreal sceneDistance() const;
149
158
150 qreal touchX() const;
159 qreal touchX() const;
151 qreal touchY() const;
160 qreal touchY() const;
152
161
162+ qreal touchSceneX() const;
163+ qreal touchSceneY() const;
164+
153 bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
165 bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
154
166
155 qreal maxDeviation() const { return m_dampedPos.maxDelta(); }
167 qreal maxDeviation() const { return m_dampedPos.maxDelta(); }
@@ -180,6 +192,7 @@ Q_SIGNALS:
180 void statusChanged(Status value);
192 void statusChanged(Status value);
181 void draggingChanged(bool value);
193 void draggingChanged(bool value);
182 void distanceChanged(qreal value);
194 void distanceChanged(qreal value);
195+ void sceneDistanceChanged(qreal value);
183 void maxDeviationChanged(qreal value);
196 void maxDeviationChanged(qreal value);
184 void wideningAngleChanged(qreal value);
197 void wideningAngleChanged(qreal value);
185 void distanceThresholdChanged(qreal value);
198 void distanceThresholdChanged(qreal value);
@@ -187,6 +200,8 @@ Q_SIGNALS:
187 void maxSilenceTimeChanged(int value);
200 void maxSilenceTimeChanged(int value);
188 void touchXChanged(qreal value);
201 void touchXChanged(qreal value);
189 void touchYChanged(qreal value);
202 void touchYChanged(qreal value);
203+ void touchSceneXChanged(qreal value);
204+ void touchSceneYChanged(qreal value);
190
205
191protected:
206protected:
192 virtual void touchEvent(QTouchEvent *event);
207 virtual void touchEvent(QTouchEvent *event);
@@ -205,12 +220,15 @@ private:
205 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
220 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
206 void setStatus(Status newStatus);
221 void setStatus(Status newStatus);
207 void setPreviousPos(QPointF point);
222 void setPreviousPos(QPointF point);
223+ void setPreviousScenePos(QPointF point);
208 void updateVelocityCalculator(QPointF point);
224 void updateVelocityCalculator(QPointF point);
209
225
210 Status m_status;
226 Status m_status;
211
227
212 QPointF m_startPos;
228 QPointF m_startPos;
229+ QPointF m_startScenePos;
213 QPointF m_previousPos;
230 QPointF m_previousPos;
231+ QPointF m_previousScenePos;
214 int m_touchId;
232 int m_touchId;
215
233
216 // A movement damper is used in some of the gesture recognition calculations
234 // A movement damper is used in some of the gesture recognition calculations
po/de.po+4−4
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11−"PO-Revision-Date: 2013-06-23 18:17+0000\n"
11+"PO-Revision-Date: 2013-06-29 12:53+0000\n"
12"Last-Translator: Phillip Sz <Unknown>\n"
12"Last-Translator: Phillip Sz <Unknown>\n"
13"Language-Team: German <de@li.org>\n"
13"Language-Team: German <de@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-06-27 16:16+0000\n"
17+"X-Launchpad-Export-Date: 2013-06-30 07:12+0000\n"
18−"X-Generator: Launchpad (build 16681)\n"
18+"X-Generator: Launchpad (build 16692)\n"
19
19
20#: Applications/applications.js:23
20#: Applications/applications.js:23
21msgid "Phone"
21msgid "Phone"
@@ -217,7 +217,7 @@ msgstr "Entschuldigung, es wurden keine Treffer erzielt."
217#: Shell.qml:383
217#: Shell.qml:383
218#, qt-format
218#, qt-format
219msgid "Please enter %1"
219msgid "Please enter %1"
220−msgstr ""
220+msgstr "Bitte geben Sie %1 ein"
221
221
222#~ msgid "People"
222#~ msgid "People"
223#~ msgstr "Personen"
223#~ msgstr "Personen"
po/sl.po+27−27
@@ -8,82 +8,82 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11−"PO-Revision-Date: 2013-06-09 10:23+0000\n"
11+"PO-Revision-Date: 2013-06-29 10:26+0000\n"
12"Last-Translator: Damir Jerovšek <Unknown>\n"
12"Last-Translator: Damir Jerovšek <Unknown>\n"
13"Language-Team: Slovenian <sl@li.org>\n"
13"Language-Team: Slovenian <sl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-06-27 16:16+0000\n"
17+"X-Launchpad-Export-Date: 2013-06-30 07:12+0000\n"
18−"X-Generator: Launchpad (build 16681)\n"
18+"X-Generator: Launchpad (build 16692)\n"
19
19
20#: Applications/applications.js:23
20#: Applications/applications.js:23
21msgid "Phone"
21msgid "Phone"
22−msgstr ""
22+msgstr "Telefon"
23
23
24#: Applications/applications.js:29
24#: Applications/applications.js:29
25msgid "Camera"
25msgid "Camera"
26−msgstr ""
26+msgstr "Fotoaparat"
27
27
28#: Applications/applications.js:35
28#: Applications/applications.js:35
29msgid "Gallery"
29msgid "Gallery"
30−msgstr ""
30+msgstr "Galerija"
31
31
32#: Applications/applications.js:40
32#: Applications/applications.js:40
33msgid "Facebook"
33msgid "Facebook"
34−msgstr ""
34+msgstr "Facebook"
35
35
36#: Applications/applications.js:46
36#: Applications/applications.js:46
37msgid "Browser"
37msgid "Browser"
38−msgstr ""
38+msgstr "Brskalnik"
39
39
40#: Applications/applications.js:51
40#: Applications/applications.js:51
41msgid "Twitter"
41msgid "Twitter"
42−msgstr ""
42+msgstr "Twitter"
43
43
44#: Applications/applications.js:57
44#: Applications/applications.js:57
45msgid "GMail"
45msgid "GMail"
46−msgstr ""
46+msgstr "GMail"
47
47
48#: Applications/applications.js:62
48#: Applications/applications.js:62
49msgid "Weather"
49msgid "Weather"
50−msgstr ""
50+msgstr "Vreme"
51
51
52#: Applications/applications.js:68
52#: Applications/applications.js:68
53msgid "Notepad"
53msgid "Notepad"
54−msgstr ""
54+msgstr "Beležnica"
55
55
56#: Applications/applications.js:74
56#: Applications/applications.js:74
57msgid "Calendar"
57msgid "Calendar"
58−msgstr ""
58+msgstr "Koledar"
59
59
60#: Applications/applications.js:80
60#: Applications/applications.js:80
61msgid "Media Player"
61msgid "Media Player"
62−msgstr ""
62+msgstr "Predvajalnik predstavnosti"
63
63
64#: Applications/applications.js:86
64#: Applications/applications.js:86
65msgid "Evernote"
65msgid "Evernote"
66−msgstr ""
66+msgstr "Evernote"
67
67
68#: Applications/applications.js:91
68#: Applications/applications.js:91
69msgid "Map"
69msgid "Map"
70−msgstr ""
70+msgstr "Zemljevid"
71
71
72#: Applications/applications.js:96
72#: Applications/applications.js:96
73msgid "Pinterest"
73msgid "Pinterest"
74−msgstr ""
74+msgstr "Pinterest"
75
75
76#: Applications/applications.js:101
76#: Applications/applications.js:101
77msgid "SoundCloud"
77msgid "SoundCloud"
78−msgstr ""
78+msgstr "SoundCloud"
79
79
80#: Applications/applications.js:106
80#: Applications/applications.js:106
81msgid "Wikipedia"
81msgid "Wikipedia"
82−msgstr ""
82+msgstr "Wikipedija"
83
83
84#: Applications/applications.js:111
84#: Applications/applications.js:111
85msgid "YouTube"
85msgid "YouTube"
86−msgstr ""
86+msgstr "YouTube"
87
87
88#: Components/PageHeader.qml:300
88#: Components/PageHeader.qml:300
89msgid "Recent searches"
89msgid "Recent searches"
@@ -131,11 +131,11 @@ msgstr ""
131
131
132#: Dash/DashMusic.qml:28 Dash/DashVideos.qml:29
132#: Dash/DashMusic.qml:28 Dash/DashVideos.qml:29
133msgid "Recent"
133msgid "Recent"
134−msgstr ""
134+msgstr "Nedavno"
135
135
136#: Dash/DashMusic.qml:29 Dash/DashVideos.qml:30
136#: Dash/DashMusic.qml:29 Dash/DashVideos.qml:30
137msgid "New Releases"
137msgid "New Releases"
138−msgstr ""
138+msgstr "Nove izdaje"
139
139
140#: Dash/DashMusic.qml:30
140#: Dash/DashMusic.qml:30
141msgid "Top Charting"
141msgid "Top Charting"
@@ -163,20 +163,20 @@ msgstr ""
163
163
164#: Dash/Video/VideoPreview.qml:144
164#: Dash/Video/VideoPreview.qml:144
165msgid "Author:"
165msgid "Author:"
166−msgstr ""
166+msgstr "Avtor:"
167
167
168#: Greeter/Lockscreen.qml:133
168#: Greeter/Lockscreen.qml:133
169msgid "Emergency call"
169msgid "Emergency call"
170−msgstr ""
170+msgstr "Klic v sili"
171
171
172#: Greeter/PassphraseLockscreen.qml:59
172#: Greeter/PassphraseLockscreen.qml:59
173#, qt-format
173#, qt-format
174msgid "Hello %1"
174msgid "Hello %1"
175−msgstr ""
175+msgstr "Pozdravljeni, %1"
176
176
177#: Greeter/PassphraseLockscreen.qml:59
177#: Greeter/PassphraseLockscreen.qml:59
178msgid "Hello"
178msgid "Hello"
179−msgstr ""
179+msgstr "Pozdravljeni"
180
180
181#: Hud/HudParametrizedActionsPage.qml:132
181#: Hud/HudParametrizedActionsPage.qml:132
182msgid "Confirm"
182msgid "Confirm"
@@ -217,7 +217,7 @@ msgstr "Nič se ne ujema z vašim iskalnim nizom."
217#: Shell.qml:383
217#: Shell.qml:383
218#, qt-format
218#, qt-format
219msgid "Please enter %1"
219msgid "Please enter %1"
220−msgstr ""
220+msgstr "Vnesite %1"
221
221
222#~ msgid "People"
222#~ msgid "People"
223#~ msgstr "Ljudje"
223#~ msgstr "Ljudje"
po/th.ponew file+220
@@ -0,0 +1,220 @@
1+# Thai translation for unity
2+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
3+# This file is distributed under the same license as the unity package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11+"PO-Revision-Date: 2013-06-28 15:34+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Thai <th@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"X-Launchpad-Export-Date: 2013-06-30 07:12+0000\n"
18+"X-Generator: Launchpad (build 16692)\n"
19+
20+#: Applications/applications.js:23
21+msgid "Phone"
22+msgstr ""
23+
24+#: Applications/applications.js:29
25+msgid "Camera"
26+msgstr ""
27+
28+#: Applications/applications.js:35
29+msgid "Gallery"
30+msgstr ""
31+
32+#: Applications/applications.js:40
33+msgid "Facebook"
34+msgstr ""
35+
36+#: Applications/applications.js:46
37+msgid "Browser"
38+msgstr ""
39+
40+#: Applications/applications.js:51
41+msgid "Twitter"
42+msgstr ""
43+
44+#: Applications/applications.js:57
45+msgid "GMail"
46+msgstr ""
47+
48+#: Applications/applications.js:62
49+msgid "Weather"
50+msgstr ""
51+
52+#: Applications/applications.js:68
53+msgid "Notepad"
54+msgstr ""
55+
56+#: Applications/applications.js:74
57+msgid "Calendar"
58+msgstr ""
59+
60+#: Applications/applications.js:80
61+msgid "Media Player"
62+msgstr ""
63+
64+#: Applications/applications.js:86
65+msgid "Evernote"
66+msgstr ""
67+
68+#: Applications/applications.js:91
69+msgid "Map"
70+msgstr ""
71+
72+#: Applications/applications.js:96
73+msgid "Pinterest"
74+msgstr ""
75+
76+#: Applications/applications.js:101
77+msgid "SoundCloud"
78+msgstr ""
79+
80+#: Applications/applications.js:106
81+msgid "Wikipedia"
82+msgstr ""
83+
84+#: Applications/applications.js:111
85+msgid "YouTube"
86+msgstr ""
87+
88+#: Components/PageHeader.qml:300
89+msgid "Recent searches"
90+msgstr ""
91+
92+#: Dash/DashApps.qml:65
93+msgid "Running apps"
94+msgstr ""
95+
96+#: Dash/DashApps.qml:68
97+msgid "Frequently used"
98+msgstr ""
99+
100+#: Dash/DashApps.qml:71
101+msgid "Installed"
102+msgstr ""
103+
104+#: Dash/DashApps.qml:74
105+msgid "Available for download"
106+msgstr ""
107+
108+#: Dash/DashApps.qml:178
109+msgid "Apps"
110+msgstr ""
111+
112+#: Dash/DashHome.qml:40
113+msgid "Frequent Apps"
114+msgstr ""
115+
116+#: Dash/DashHome.qml:43
117+msgid "Recent Music"
118+msgstr ""
119+
120+#: Dash/DashHome.qml:46
121+msgid "Videos Popular Online"
122+msgstr ""
123+
124+#: Dash/DashHome.qml:175
125+msgid "Home"
126+msgstr ""
127+
128+#: Dash/DashMusic.qml:27 Dash/DashVideos.qml:28
129+msgid "Featured"
130+msgstr ""
131+
132+#: Dash/DashMusic.qml:28 Dash/DashVideos.qml:29
133+msgid "Recent"
134+msgstr ""
135+
136+#: Dash/DashMusic.qml:29 Dash/DashVideos.qml:30
137+msgid "New Releases"
138+msgstr ""
139+
140+#: Dash/DashMusic.qml:30
141+msgid "Top Charting"
142+msgstr ""
143+
144+#: Dash/DashMusic.qml:99
145+msgid "Music"
146+msgstr ""
147+
148+#: Dash/DashVideos.qml:31
149+msgid "Popular Online"
150+msgstr ""
151+
152+#: Dash/DashVideos.qml:155
153+msgid "Videos"
154+msgstr ""
155+
156+#: Dash/Video/VideoPreview.qml:98
157+msgid "Directed by:"
158+msgstr ""
159+
160+#: Dash/Video/VideoPreview.qml:121
161+msgid "Starring:"
162+msgstr ""
163+
164+#: Dash/Video/VideoPreview.qml:144
165+msgid "Author:"
166+msgstr ""
167+
168+#: Greeter/Lockscreen.qml:133
169+msgid "Emergency call"
170+msgstr ""
171+
172+#: Greeter/PassphraseLockscreen.qml:59
173+#, qt-format
174+msgid "Hello %1"
175+msgstr ""
176+
177+#: Greeter/PassphraseLockscreen.qml:59
178+msgid "Hello"
179+msgstr ""
180+
181+#: Hud/HudParametrizedActionsPage.qml:132
182+msgid "Confirm"
183+msgstr ""
184+
185+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
186+msgid "Type or say a command"
187+msgstr ""
188+
189+#: Hud/Hud.qml:84
190+msgid "Loading. Please Wait..."
191+msgstr ""
192+
193+#: Hud/Hud.qml:89
194+msgid "Speak Now..."
195+msgstr ""
196+
197+#: Hud/Hud.qml:94
198+msgid "Speaking..."
199+msgstr ""
200+
201+#: Panel/MenuContent.qml:192
202+msgid "Device"
203+msgstr ""
204+
205+#: Panel/Menus/Overview/FlightModeWidget.qml:42
206+msgid "Flight mode"
207+msgstr ""
208+
209+#: Panel/SearchIndicator.qml:26
210+msgid "Search"
211+msgstr ""
212+
213+#: plugins/Unity/scope.cpp:268
214+msgid "Sorry, there is nothing that matches your search."
215+msgstr ""
216+
217+#: Shell.qml:383
218+#, qt-format
219+msgid "Please enter %1"
220+msgstr ""
po/ug.po+2−2
@@ -14,8 +14,8 @@ msgstr ""
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-06-28 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2013-06-30 07:12+0000\n"
18−"X-Generator: Launchpad (build 16681)\n"
18+"X-Generator: Launchpad (build 16692)\n"
19
19
20#: Applications/applications.js:23
20#: Applications/applications.js:23
21msgid "Phone"
21msgid "Phone"
po/uk.po+5−5
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11−"PO-Revision-Date: 2013-06-25 08:08+0000\n"
11+"PO-Revision-Date: 2013-06-29 14:27+0000\n"
12−"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
12+"Last-Translator: Roman Koshkin <mar2008m@rambler.ru>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-06-27 16:16+0000\n"
17+"X-Launchpad-Export-Date: 2013-06-30 07:12+0000\n"
18−"X-Generator: Launchpad (build 16681)\n"
18+"X-Generator: Launchpad (build 16692)\n"
19
19
20#: Applications/applications.js:23
20#: Applications/applications.js:23
21msgid "Phone"
21msgid "Phone"
@@ -217,7 +217,7 @@ msgstr "Вибачте, для вказаних вами ключових слі
217#: Shell.qml:383
217#: Shell.qml:383
218#, qt-format
218#, qt-format
219msgid "Please enter %1"
219msgid "Please enter %1"
220−msgstr ""
220+msgstr "Будь ласка, введіть %1"
221
221
222#~ msgid "People"
222#~ msgid "People"
223#~ msgstr "Люди"
223#~ msgstr "Люди"
run_on_device+1−1
@@ -75,7 +75,7 @@ install_dependencies() {
75sync_code() {
75sync_code() {
76 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
76 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
77 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
77 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
78− rsync -crlOzv -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
78+ rsync -crlOzv --delete --exclude builddir -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
79 rm -rf /tmp/$CODE_DIR
79 rm -rf /tmp/$CODE_DIR
80}
80}
81
81
tests/plugins/CMakeLists.txt+1
@@ -1,2 +1,3 @@
1add_subdirectory(Utils)
1add_subdirectory(Utils)
2add_subdirectory(Ubuntu)
2add_subdirectory(Ubuntu)
3+add_subdirectory(ListViewWithPageHeader)
tests/plugins/ListViewWithPageHeader/CMakeLists.txtnew file+31
@@ -0,0 +1,31 @@
1+# There's no cmake var for v8 include path :-/
2+# so create one
3+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
4+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
5+
6+include_directories(
7+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
8+ ${Qt5Quick_INCLUDE_DIRS}
9+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
11+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader
12+ ${CMAKE_CURRENT_BINARY_DIR}
13+ )
14+
15+remove_definitions(-DQT_NO_KEYWORDS)
16+
17+add_definitions(-DLISTVIEWWITHPAGEHEADER_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}")
18+add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
19+
20+add_executable(listviewwithpageheadertestExec listviewwithpageheadertest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
21+qt5_use_modules(listviewwithpageheadertestExec Test Core Qml)
22+target_link_libraries(listviewwithpageheadertestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
23+add_custom_target(testlistviewwithpageheader ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheader.xml,xunitxml -o -,txt)
24+
25+add_executable(listviewwithpageheadertestsectionExec listviewwithpageheadertestsection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
26+qt5_use_modules(listviewwithpageheadertestsectionExec Test Core Qml)
27+target_link_libraries(listviewwithpageheadertestsectionExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
28+add_custom_target(testlistviewwithpageheadersection ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersection.xml,xunitxml -o -,txt)
29+
30+add_dependencies(qmluitests testlistviewwithpageheader)
31+add_dependencies(qmluitests testlistviewwithpageheadersection)
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cppnew file+1311
@@ -0,0 +1,1311 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "listviewwithpageheader.h"
18+
19+#include <QAbstractItemModel>
20+#include <QQmlEngine>
21+#include <QQuickView>
22+#include <QtTestGui>
23+#include <private/qquicklistmodel_p.h>
24+#include <private/qquickanimation_p.h>
25+#include <private/qquickitem_p.h>
26+
27+class ListViewWithPageHeaderTest : public QObject
28+{
29+ Q_OBJECT
30+
31+private:
32+ void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled)
33+ {
34+ ListViewWithPageHeader::ListItem *item = lvwph->m_visibleItems[visibleIndex];
35+ QTRY_COMPARE(item->y(), pos);
36+ QTRY_COMPARE(item->height(), height);
37+ QCOMPARE(QQuickItemPrivate::get(item->m_item)->culled, culled);
38+ }
39+
40+ void changeContentY(qreal change)
41+ {
42+ const qreal dest = lvwph->contentY() + change;
43+ if (dest > lvwph->contentY()) {
44+ const qreal jump = 25;
45+ while (lvwph->contentY() + jump < dest) {
46+ lvwph->setContentY(lvwph->contentY() + jump);
47+ QTest::qWait(1);
48+ }
49+ } else {
50+ const qreal jump = -25;
51+ while (lvwph->contentY() + jump > dest) {
52+ lvwph->setContentY(lvwph->contentY() + jump);
53+ QTest::qWait(1);
54+ }
55+ }
56+ lvwph->setContentY(dest);
57+ QTest::qWait(1);
58+ }
59+
60+ void scrollToTop()
61+ {
62+ const qreal jump = -25;
63+ while (!lvwph->isAtYBeginning()) {
64+ if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
65+ lvwph->setContentY(lvwph->contentY() + jump);
66+ } else {
67+ lvwph->setContentY(lvwph->contentY() - 1);
68+ }
69+ QTest::qWait(1);
70+ }
71+ }
72+
73+ void scrollToBottom()
74+ {
75+ const qreal jump = 25;
76+ while (!lvwph->isAtYEnd()) {
77+ if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
78+ lvwph->setContentY(lvwph->contentY() + jump);
79+ } else {
80+ lvwph->setContentY(lvwph->contentY() + 1);
81+ }
82+ QTest::qWait(1);
83+ }
84+ }
85+
86+ void verifyInitialTopPosition()
87+ {
88+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
89+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
90+ verifyItem(0, 50., 150., false);
91+ verifyItem(1, 200., 200., false);
92+ verifyItem(2, 400., 350., false);
93+ verifyItem(3, 750., 350., true);
94+ QCOMPARE(lvwph->m_minYExtent, 0.);
95+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
96+ QCOMPARE(lvwph->m_clipItem->clip(), false);
97+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
98+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
99+ QCOMPARE(lvwph->contentY(), 0.);
100+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
101+ }
102+
103+private Q_SLOTS:
104+
105+ void initTestCase()
106+ {
107+ }
108+
109+ void init()
110+ {
111+ view = new QQuickView();
112+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
113+ view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test.qml"));
114+ lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
115+ model = view->rootObject()->findChild<QQuickListModel*>();
116+ otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
117+ QVERIFY(lvwph);
118+ QVERIFY(model);
119+ QVERIFY(otherDelegate);
120+ view->show();
121+ QTest::qWaitForWindowExposed(view);
122+
123+ verifyInitialTopPosition();
124+ }
125+
126+ void cleanup()
127+ {
128+ delete view;
129+ }
130+
131+ void testCreationDeletion()
132+ {
133+ // Nothing, init/cleanup already tests this
134+ }
135+
136+ void testDrag1PixelUp()
137+ {
138+ lvwph->setContentY(lvwph->contentY() + 1);
139+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
140+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
141+ verifyItem(0, 49., 150., false);
142+ verifyItem(1, 199., 200., false);
143+ verifyItem(2, 399., 350., false);
144+ verifyItem(3, 749., 350., true);
145+ QCOMPARE(lvwph->m_minYExtent, 0.);
146+ QCOMPARE(lvwph->m_clipItem->y(), 1.);
147+ QCOMPARE(lvwph->m_clipItem->clip(), false);
148+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
149+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
150+ QCOMPARE(lvwph->contentY(), 1.);
151+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
152+ }
153+
154+ void testHeaderDetachDragDown()
155+ {
156+ QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
157+ QTest::qWait(100);
158+ QTest::mouseMove(view, QPoint(0, 5));
159+ QTest::qWait(100);
160+ QTest::mouseMove(view, QPoint(0, 10));
161+ QTest::qWait(100);
162+ QTest::mouseMove(view, QPoint(0, 15));
163+ QTest::qWait(100);
164+ QTest::mouseMove(view, QPoint(0, 20));
165+ QTest::qWait(100);
166+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
167+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
168+ verifyItem(0, 55., 150., false);
169+ verifyItem(1, 205., 200., false);
170+ verifyItem(2, 405., 350., false);
171+ verifyItem(3, 755., 350., true);
172+ QCOMPARE(lvwph->m_minYExtent, 0.);
173+ QCOMPARE(lvwph->m_clipItem->y(), -5.);
174+ QCOMPARE(lvwph->m_clipItem->clip(), false);
175+ QCOMPARE(lvwph->m_headerItem->y(), -5.);
176+ QCOMPARE(lvwph->m_headerItem->height(), 55.);
177+ QCOMPARE(lvwph->contentY(), -5.);
178+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
179+
180+ QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
181+
182+ verifyInitialTopPosition();
183+ }
184+
185+ void testDrag375PixelUp()
186+ {
187+ changeContentY(375);
188+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
189+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
190+ verifyItem(0, -325., 150., true);
191+ verifyItem(1, -175, 200., false);
192+ verifyItem(2, 25, 350., false);
193+ verifyItem(3, 375, 350., false);
194+ verifyItem(4, 725, 350., true);
195+ QCOMPARE(lvwph->m_minYExtent, 0.);
196+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
197+ QCOMPARE(lvwph->m_clipItem->clip(), false);
198+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
199+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
200+ QCOMPARE(lvwph->contentY(), 375.);
201+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
202+ }
203+
204+ void testDrag520PixelUp()
205+ {
206+ changeContentY(520);
207+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
208+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
209+ verifyItem(0, -320., 200., true);
210+ verifyItem(1, -120, 350., false);
211+ verifyItem(2, 230, 350., false);
212+ verifyItem(3, 580, 350., true);
213+ // Just here as first check against m_minYExtent when m_firstVisibleIndex is not 0
214+ // We as humans know that m_minYExtent will be 0 but since the first delegate is not there anymore
215+ // we have to estimate its size, the average item height is 312.5 which is 162.5 more than the
216+ // "real" size of 150 and that's why the m_minYExtent has that "peculiar" value
217+ // It's fine since what it means is that we could scroll more up than the original position
218+ // but we will recalculate m_minYExtent when the item 0 is created and set it correctly
219+ QCOMPARE(lvwph->m_minYExtent, 162.5);
220+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
221+ QCOMPARE(lvwph->m_clipItem->clip(), false);
222+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
223+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
224+ QCOMPARE(lvwph->contentY(), 520.);
225+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
226+ }
227+
228+ void testDragHeaderUpThenShow()
229+ {
230+ changeContentY(120);
231+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
232+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
233+ verifyItem(0, -70., 150., false);
234+ verifyItem(1, 80., 200., false);
235+ verifyItem(2, 280., 350., false);
236+ verifyItem(3, 630., 350., true);
237+ QCOMPARE(lvwph->m_minYExtent, 0.);
238+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
239+ QCOMPARE(lvwph->m_clipItem->clip(), false);
240+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
241+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
242+ QCOMPARE(lvwph->contentY(), 120.);
243+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
244+
245+ changeContentY(-30);
246+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
247+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
248+ verifyItem(0, -70., 150., false);
249+ verifyItem(1, 80., 200., false);
250+ verifyItem(2, 280., 350., false);
251+ verifyItem(3, 630., 350., true);
252+ QCOMPARE(lvwph->m_minYExtent, 0.);
253+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
254+ QCOMPARE(lvwph->m_clipItem->clip(), true);
255+ QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
256+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
257+ QCOMPARE(lvwph->contentY(), 90.);
258+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
259+ }
260+
261+ void testDragHeaderUpThenShowWithoutHidingTotally()
262+ {
263+ changeContentY(10);
264+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
265+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
266+ verifyItem(0, 40., 150., false);
267+ verifyItem(1, 190., 200., false);
268+ verifyItem(2, 390., 350., false);
269+ verifyItem(3, 740., 350., true);
270+ QCOMPARE(lvwph->m_minYExtent, 0.);
271+ QCOMPARE(lvwph->m_clipItem->y(), 10.);
272+ QCOMPARE(lvwph->m_clipItem->clip(), false);
273+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
274+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
275+ QCOMPARE(lvwph->contentY(), 10.);
276+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
277+
278+ changeContentY(-1);
279+
280+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
281+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
282+ verifyItem(0, 41., 150., false);
283+ verifyItem(1, 191., 200., false);
284+ verifyItem(2, 391., 350., false);
285+ verifyItem(3, 741., 350., true);
286+ QCOMPARE(lvwph->m_minYExtent, 0.);
287+ QCOMPARE(lvwph->m_clipItem->y(), 9.);
288+ QCOMPARE(lvwph->m_clipItem->clip(), false);
289+ QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
290+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
291+ QCOMPARE(lvwph->contentY(), 9.);
292+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
293+ }
294+
295+ void testPositionAtBeginningIndex0Visible()
296+ {
297+ changeContentY(375);
298+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
299+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
300+ verifyItem(0, -325., 150., true);
301+ verifyItem(1, -175, 200., false);
302+ verifyItem(2, 25, 350., false);
303+ verifyItem(3, 375, 350., false);
304+ verifyItem(4, 725, 350., true);
305+ QCOMPARE(lvwph->m_minYExtent, 0.);
306+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
307+ QCOMPARE(lvwph->m_clipItem->clip(), false);
308+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
309+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
310+ QCOMPARE(lvwph->contentY(), 375.);
311+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
312+
313+ lvwph->positionAtBeginning();
314+
315+ verifyInitialTopPosition();
316+ }
317+
318+ void testPositionAtBeginningIndex0NotVisible()
319+ {
320+ changeContentY(520);
321+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
322+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
323+ verifyItem(0, -320., 200., true);
324+ verifyItem(1, -120, 350., false);
325+ verifyItem(2, 230, 350., false);
326+ verifyItem(3, 580, 350., true);
327+ QCOMPARE(lvwph->m_minYExtent, 162.5);
328+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
329+ QCOMPARE(lvwph->m_clipItem->clip(), false);
330+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
331+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
332+ QCOMPARE(lvwph->contentY(), 520.);
333+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
334+
335+ lvwph->positionAtBeginning();
336+
337+ verifyInitialTopPosition();
338+ }
339+
340+ void testIndex0GrowOnScreen()
341+ {
342+ model->setProperty(0, "size", 400);
343+
344+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
345+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
346+ verifyItem(0, 50., 400., false);
347+ verifyItem(1, 450., 200., false);
348+ verifyItem(2, 650., 350., true);
349+ QCOMPARE(lvwph->m_minYExtent, 0.);
350+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
351+ QCOMPARE(lvwph->m_clipItem->clip(), false);
352+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
353+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
354+ QCOMPARE(lvwph->contentY(), 0.);
355+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
356+ }
357+
358+ void testIndex0GrowOffScreen()
359+ {
360+ changeContentY(375);
361+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
362+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
363+ verifyItem(0, -325., 150., true);
364+ verifyItem(1, -175, 200., false);
365+ verifyItem(2, 25, 350., false);
366+ verifyItem(3, 375, 350., false);
367+ verifyItem(4, 725, 350., true);
368+ QCOMPARE(lvwph->m_minYExtent, 0.);
369+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
370+ QCOMPARE(lvwph->m_clipItem->clip(), false);
371+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
372+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
373+ QCOMPARE(lvwph->contentY(), 375.);
374+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
375+
376+ model->setProperty(0, "size", 400);
377+
378+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
379+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
380+ verifyItem(0, -575., 400., true);
381+ verifyItem(1, -175, 200., false);
382+ verifyItem(2, 25, 350., false);
383+ verifyItem(3, 375, 350., false);
384+ verifyItem(4, 725, 350., true);
385+ QCOMPARE(lvwph->m_minYExtent, 250.);
386+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
387+ QCOMPARE(lvwph->m_clipItem->clip(), false);
388+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
389+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
390+ QCOMPARE(lvwph->contentY(), 375.);
391+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
392+
393+ scrollToTop();
394+
395+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
396+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
397+ verifyItem(0, 50., 400., false);
398+ verifyItem(1, 450, 200., false);
399+ verifyItem(2, 650, 350., true);
400+ QCOMPARE(lvwph->m_minYExtent, 250.);
401+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
402+ QCOMPARE(lvwph->m_clipItem->clip(), false);
403+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
404+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
405+ QCOMPARE(lvwph->contentY(), -250.);
406+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
407+
408+ changeContentY(30);
409+
410+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
411+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
412+ verifyItem(0, 20., 400., false);
413+ verifyItem(1, 420, 200., false);
414+ verifyItem(2, 620, 350., true);
415+ QCOMPARE(lvwph->m_minYExtent, 250.);
416+ QCOMPARE(lvwph->m_clipItem->y(), -220.);
417+ QCOMPARE(lvwph->m_clipItem->clip(), false);
418+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
419+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
420+ QCOMPARE(lvwph->contentY(), -220.);
421+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
422+ }
423+
424+ void testIndex0GrowNotCreated()
425+ {
426+ changeContentY(520);
427+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
428+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
429+ verifyItem(0, -320., 200., true);
430+ verifyItem(1, -120, 350., false);
431+ verifyItem(2, 230, 350., false);
432+ verifyItem(3, 580, 350., true);
433+ QCOMPARE(lvwph->m_minYExtent, 162.5);
434+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
435+ QCOMPARE(lvwph->m_clipItem->clip(), false);
436+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
437+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
438+ QCOMPARE(lvwph->contentY(), 520.);
439+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
440+
441+ model->setProperty(0, "size", 400);
442+
443+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
444+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
445+ verifyItem(0, -320., 200., true);
446+ verifyItem(1, -120, 350., false);
447+ verifyItem(2, 230, 350., false);
448+ verifyItem(3, 580, 350., true);
449+ QCOMPARE(lvwph->m_minYExtent, 162.5);
450+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
451+ QCOMPARE(lvwph->m_clipItem->clip(), false);
452+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
453+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
454+ QCOMPARE(lvwph->contentY(), 520.);
455+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
456+
457+ scrollToTop();
458+
459+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
460+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
461+ verifyItem(0, 50., 400., false);
462+ verifyItem(1, 450, 200., false);
463+ verifyItem(2, 650, 350., true);
464+ QCOMPARE(lvwph->m_minYExtent, 250.);
465+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
466+ QCOMPARE(lvwph->m_clipItem->clip(), false);
467+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
468+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
469+ QCOMPARE(lvwph->contentY(), -250.);
470+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
471+ }
472+
473+ void testShowHideShowHeaderAtBottom()
474+ {
475+ scrollToBottom();
476+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
477+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
478+ verifyItem(0, -508., 350., true);
479+ verifyItem(1, -158, 350., false);
480+ verifyItem(2, 192, 350., false);
481+ QCOMPARE(lvwph->m_minYExtent, 350.);
482+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
483+ QCOMPARE(lvwph->m_clipItem->clip(), false);
484+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
485+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
486+ QCOMPARE(lvwph->contentY(), 1258.);
487+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
488+
489+ changeContentY(-30);
490+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
491+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
492+ verifyItem(0, -508., 350., true);
493+ verifyItem(1, -158, 350., false);
494+ verifyItem(2, 192, 350., false);
495+ QCOMPARE(lvwph->m_minYExtent, 350.);
496+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
497+ QCOMPARE(lvwph->m_clipItem->clip(), true);
498+ QCOMPARE(lvwph->m_headerItem->y(), 1208.);
499+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
500+ QCOMPARE(lvwph->contentY(), 1228.);
501+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
502+
503+ changeContentY(30);
504+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
505+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
506+ verifyItem(0, -508., 350., true);
507+ verifyItem(1, -158, 350., false);
508+ verifyItem(2, 192, 350., false);
509+ QCOMPARE(lvwph->m_minYExtent, 350.);
510+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
511+ QCOMPARE(lvwph->m_clipItem->clip(), false);
512+ QCOMPARE(lvwph->m_headerItem->y(), -350.);
513+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
514+ QCOMPARE(lvwph->contentY(), 1258.);
515+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
516+
517+ changeContentY(-30);
518+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
519+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
520+ verifyItem(0, -508., 350., true);
521+ verifyItem(1, -158, 350., false);
522+ verifyItem(2, 192, 350., false);
523+ QCOMPARE(lvwph->m_minYExtent, 350.);
524+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
525+ QCOMPARE(lvwph->m_clipItem->clip(), true);
526+ QCOMPARE(lvwph->m_headerItem->y(), 1208.);
527+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
528+ QCOMPARE(lvwph->contentY(), 1228.);
529+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
530+ }
531+
532+ void testChangeDelegateAtBottom()
533+ {
534+ scrollToBottom();
535+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
536+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
537+ verifyItem(0, -508., 350., true);
538+ verifyItem(1, -158, 350., false);
539+ verifyItem(2, 192, 350., false);
540+ QCOMPARE(lvwph->m_minYExtent, 350.);
541+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
542+ QCOMPARE(lvwph->m_clipItem->clip(), false);
543+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
544+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
545+ QCOMPARE(lvwph->contentY(), 1258.);
546+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
547+
548+ lvwph->setDelegate(otherDelegate);
549+
550+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
551+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
552+ verifyItem(0, 50., 35., false);
553+ verifyItem(1, 85, 35., false);
554+ verifyItem(2, 120, 35., false);
555+ verifyItem(3, 155, 35., false);
556+ verifyItem(4, 190, 35., false);
557+ verifyItem(5, 225, 35., false);
558+ QCOMPARE(lvwph->m_minYExtent, 0.);
559+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
560+ QCOMPARE(lvwph->m_clipItem->clip(), false);
561+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
562+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
563+ QCOMPARE(lvwph->contentY(), 0.);
564+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
565+ }
566+
567+ void testSetEmptyHeaderAtTop()
568+ {
569+ lvwph->setHeader(nullptr);
570+
571+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
572+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
573+ verifyItem(0, 0., 150., false);
574+ verifyItem(1, 150., 200., false);
575+ verifyItem(2, 350., 350., false);
576+ verifyItem(3, 700., 350., true);
577+ QCOMPARE(lvwph->m_minYExtent, 0.);
578+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
579+ QCOMPARE(lvwph->m_clipItem->clip(), false);
580+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
581+ QCOMPARE(lvwph->contentY(), 0.);
582+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
583+ }
584+
585+ void testSetEmptyHeaderAtBottom()
586+ {
587+ scrollToBottom();
588+ lvwph->setHeader(nullptr);
589+
590+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
591+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
592+ verifyItem(0, -508., 350., true);
593+ verifyItem(1, -158, 350., false);
594+ verifyItem(2, 192, 350., false);
595+ QCOMPARE(lvwph->m_minYExtent, 300.);
596+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
597+ QCOMPARE(lvwph->m_clipItem->clip(), false);
598+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
599+ QCOMPARE(lvwph->contentY(), 1258.);
600+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
601+
602+ scrollToTop();
603+
604+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
605+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
606+ verifyItem(0, 0., 150., false);
607+ verifyItem(1, 150., 200., false);
608+ verifyItem(2, 350., 350., false);
609+ verifyItem(3, 700., 350., true);
610+ QCOMPARE(lvwph->m_minYExtent, -50.);
611+ QCOMPARE(lvwph->m_clipItem->y(), 50.);
612+ QCOMPARE(lvwph->m_clipItem->clip(), false);
613+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
614+ QCOMPARE(lvwph->contentY(), 50.);
615+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
616+ }
617+
618+ void testSetEmptyHeaderWhenPartlyShownClipped()
619+ {
620+ scrollToBottom();
621+ changeContentY(-30);
622+ lvwph->setHeader(nullptr);
623+
624+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
625+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
626+ verifyItem(0, -508., 350., true);
627+ verifyItem(1, -158, 350., false);
628+ verifyItem(2, 192, 350., false);
629+ QCOMPARE(lvwph->m_minYExtent, 330.);
630+ QCOMPARE(lvwph->m_clipItem->y(), 1228.);
631+ QCOMPARE(lvwph->m_clipItem->clip(), false);
632+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
633+ QCOMPARE(lvwph->contentY(), 1228.);
634+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
635+ QTRY_VERIFY(lvwph->isAtYEnd());
636+
637+ scrollToTop();
638+
639+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
640+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
641+ verifyItem(0, 0., 150., false);
642+ verifyItem(1, 150., 200., false);
643+ verifyItem(2, 350., 350., false);
644+ verifyItem(3, 700., 350., true);
645+ QCOMPARE(lvwph->m_minYExtent, -20.);
646+ QCOMPARE(lvwph->m_clipItem->y(), 20.);
647+ QCOMPARE(lvwph->m_clipItem->clip(), false);
648+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
649+ QCOMPARE(lvwph->contentY(), 20.);
650+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
651+ }
652+
653+ void testSetEmptyHeaderWhenPartlyShownNotClipped()
654+ {
655+ changeContentY(30);
656+ lvwph->setHeader(nullptr);
657+
658+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
659+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
660+ verifyItem(0, -30., 150., false);
661+ verifyItem(1, 120., 200., false);
662+ verifyItem(2, 320., 350., false);
663+ verifyItem(3, 670., 350., true);
664+ QCOMPARE(lvwph->m_minYExtent, 0.);
665+ QCOMPARE(lvwph->m_clipItem->y(), 30.);
666+ QCOMPARE(lvwph->m_clipItem->clip(), false);
667+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
668+ QCOMPARE(lvwph->contentY(), 30.);
669+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
670+ }
671+
672+ void testSetNullDelegate()
673+ {
674+ lvwph->setDelegate(nullptr);
675+
676+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
677+ QCOMPARE(lvwph->m_firstVisibleIndex, -1);
678+ QCOMPARE(lvwph->m_minYExtent, 0.);
679+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
680+ QCOMPARE(lvwph->m_clipItem->clip(), false);
681+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
682+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
683+ QCOMPARE(lvwph->contentY(), 0.);
684+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
685+ QTRY_COMPARE(lvwph->contentHeight(), 50.);
686+ QVERIFY(lvwph->isAtYBeginning());
687+ QVERIFY(lvwph->isAtYEnd());
688+ }
689+
690+ void testInsertItems()
691+ {
692+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
693+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
694+
695+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
696+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
697+ verifyItem(0, 50., 150., false);
698+ verifyItem(1, 200., 125., false);
699+ verifyItem(2, 325., 100., false);
700+ verifyItem(3, 425., 200., false);
701+ verifyItem(4, 625., 350., true);
702+ QCOMPARE(lvwph->m_minYExtent, 0.);
703+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
704+ QCOMPARE(lvwph->m_clipItem->clip(), false);
705+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
706+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
707+ QCOMPARE(lvwph->contentY(), 0.);
708+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
709+ }
710+
711+ void testInsertItemsOnNotShownPosition()
712+ {
713+ changeContentY(700);
714+
715+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
716+ QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
717+ verifyItem(0, -300., 350., false);
718+ verifyItem(1, 50, 350., false);
719+ verifyItem(2, 400, 350., false);
720+ verifyItem(3, 750, 350., true);
721+ QCOMPARE(lvwph->m_minYExtent, 350.);
722+ QCOMPARE(lvwph->m_clipItem->y(), 700.);
723+ QCOMPARE(lvwph->m_clipItem->clip(), false);
724+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
725+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
726+ QCOMPARE(lvwph->contentY(), 700.);
727+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
728+
729+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
730+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
731+
732+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
733+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
734+ verifyItem(0, -300., 350., false);
735+ verifyItem(1, 50, 350., false);
736+ verifyItem(2, 400, 350., false);
737+ verifyItem(3, 750, 350., true);
738+ QCOMPARE(lvwph->m_minYExtent, 1050.);
739+ QCOMPARE(lvwph->m_clipItem->y(), 700.);
740+ QCOMPARE(lvwph->m_clipItem->clip(), false);
741+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
742+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
743+ QCOMPARE(lvwph->contentY(), 700.);
744+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
745+
746+ scrollToTop();
747+
748+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
749+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
750+ verifyItem(0, 50., 150., false);
751+ verifyItem(1, 200., 125., false);
752+ verifyItem(2, 325., 100., false);
753+ verifyItem(3, 425., 200., false);
754+ verifyItem(4, 625., 350., true);
755+ QCOMPARE(lvwph->m_minYExtent, 225.);
756+ QCOMPARE(lvwph->m_clipItem->y(), -225.);
757+ QCOMPARE(lvwph->m_clipItem->clip(), false);
758+ QCOMPARE(lvwph->m_headerItem->y(), -225.);
759+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
760+ QCOMPARE(lvwph->contentY(), -225.);
761+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
762+ }
763+
764+ void testInsertItemsAtEndOfViewport()
765+ {
766+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100));
767+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125));
768+
769+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
770+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
771+ verifyItem(0, 50., 150., false);
772+ verifyItem(1, 200., 200., false);
773+ verifyItem(2, 400., 350., false);
774+ verifyItem(3, 750., 125., true);
775+ QCOMPARE(lvwph->m_minYExtent, 0.);
776+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
777+ QCOMPARE(lvwph->m_clipItem->clip(), false);
778+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
779+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
780+ QCOMPARE(lvwph->contentY(), 0.);
781+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
782+ }
783+
784+ void testInsertItemsBeforeValidIndex()
785+ {
786+ changeContentY(520);
787+
788+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
789+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
790+ verifyItem(0, -320., 200., true);
791+ verifyItem(1, -120, 350., false);
792+ verifyItem(2, 230, 350., false);
793+ verifyItem(3, 580, 350., true);
794+ QCOMPARE(lvwph->m_minYExtent, 162.5);
795+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
796+ QCOMPARE(lvwph->m_clipItem->clip(), false);
797+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
798+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
799+ QCOMPARE(lvwph->contentY(), 520.);
800+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
801+
802+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
803+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
804+
805+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
806+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
807+ verifyItem(0, -320., 200., true);
808+ verifyItem(1, -120, 350., false);
809+ verifyItem(2, 230, 350., false);
810+ verifyItem(3, 580, 350., true);
811+ QCOMPARE(lvwph->m_minYExtent, 787.5);
812+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
813+ QCOMPARE(lvwph->m_clipItem->clip(), false);
814+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
815+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
816+ QCOMPARE(lvwph->contentY(), 520.);
817+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
818+ }
819+
820+ void testInsertItemsBeforeViewport()
821+ {
822+ changeContentY(375);
823+
824+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
825+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
826+ verifyItem(0, -325., 150., true);
827+ verifyItem(1, -175, 200., false);
828+ verifyItem(2, 25, 350., false);
829+ verifyItem(3, 375, 350., false);
830+ verifyItem(4, 725, 350., true);
831+ QCOMPARE(lvwph->m_minYExtent, 0.);
832+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
833+ QCOMPARE(lvwph->m_clipItem->clip(), false);
834+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
835+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
836+ QCOMPARE(lvwph->contentY(), 375.);
837+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
838+
839+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
840+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
841+
842+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
843+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
844+ verifyItem(0, -275., 100., true);
845+ verifyItem(1, -175, 200., false);
846+ verifyItem(2, 25, 350., false);
847+ verifyItem(3, 375, 350., false);
848+ verifyItem(4, 725, 350., true);
849+ QCOMPARE(lvwph->m_minYExtent, 490.);
850+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
851+ QCOMPARE(lvwph->m_clipItem->clip(), false);
852+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
853+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
854+ QCOMPARE(lvwph->contentY(), 375.);
855+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
856+
857+ scrollToTop();
858+
859+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
860+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
861+ verifyItem(0, 50., 150., false);
862+ verifyItem(1, 200., 125., false);
863+ verifyItem(2, 325., 100., false);
864+ verifyItem(3, 425., 200., false);
865+ verifyItem(4, 625., 350., true);
866+ QCOMPARE(lvwph->m_minYExtent, 225.);
867+ QCOMPARE(lvwph->m_clipItem->y(), -225.);
868+ QCOMPARE(lvwph->m_clipItem->clip(), false);
869+ QCOMPARE(lvwph->m_headerItem->y(), -225.);
870+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
871+ QCOMPARE(lvwph->contentY(), -225.);
872+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
873+ }
874+
875+ void testInsertItemsAtBottom()
876+ {
877+ scrollToBottom();
878+
879+ QVERIFY(lvwph->isAtYEnd());
880+
881+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100));
882+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125));
883+
884+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
885+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
886+ verifyItem(0, -508., 350., true);
887+ verifyItem(1, -158, 350., false);
888+ verifyItem(2, 192, 350., false);
889+ verifyItem(3, 542, 125., true);
890+ verifyItem(4, 667, 100., true);
891+ QCOMPARE(lvwph->m_minYExtent, 65.);
892+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
893+ QCOMPARE(lvwph->m_clipItem->clip(), false);
894+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
895+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
896+ QCOMPARE(lvwph->contentY(), 1258.);
897+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
898+ QVERIFY(!lvwph->isAtYEnd());
899+
900+ scrollToBottom();
901+
902+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
903+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
904+ verifyItem(0, -383., 350., true);
905+ verifyItem(1, -33, 350., false);
906+ verifyItem(2, 317, 125., false);
907+ verifyItem(3, 442, 100., false);
908+ QCOMPARE(lvwph->m_minYExtent, -125.);
909+ QCOMPARE(lvwph->m_clipItem->y(), 1483.);
910+ QCOMPARE(lvwph->m_clipItem->clip(), false);
911+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
912+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
913+ QCOMPARE(lvwph->contentY(), 1483.);
914+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
915+ QVERIFY(lvwph->isAtYEnd());
916+ }
917+
918+ void testInsertItemAtTop()
919+ {
920+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
921+
922+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
923+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
924+ verifyItem(0, 50., 75., false);
925+ verifyItem(1, 125., 150., false);
926+ verifyItem(2, 275., 200., false);
927+ verifyItem(3, 475., 350., false);
928+ QCOMPARE(lvwph->m_minYExtent, 0.);
929+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
930+ QCOMPARE(lvwph->m_clipItem->clip(), false);
931+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
932+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
933+ QCOMPARE(lvwph->contentY(), 0.);
934+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
935+ QVERIFY(lvwph->isAtYBeginning());
936+ }
937+
938+ void testInsertItem10SmallItemsAtTopWhenAtBottom()
939+ {
940+ scrollToBottom();
941+
942+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
943+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
944+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
945+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
946+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
947+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
948+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
949+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
950+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
951+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
952+
953+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
954+ QCOMPARE(lvwph->m_firstVisibleIndex, 13);
955+ verifyItem(0, -508., 350., true);
956+ verifyItem(1, -158, 350., false);
957+ verifyItem(2, 192, 350., false);
958+ QCOMPARE(lvwph->m_minYExtent, 3850.);
959+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
960+ QCOMPARE(lvwph->m_clipItem->clip(), false);
961+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
962+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
963+ QCOMPARE(lvwph->contentY(), 1258.);
964+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
965+
966+ changeContentY(-1700);
967+
968+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 12);
969+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
970+ verifyItem(0, -308., 75., true);
971+ verifyItem(1, -233., 75., true);
972+ verifyItem(2, -158., 75., true);
973+ verifyItem(3, -83., 75., true);
974+ verifyItem(4, -8., 75., false);
975+ verifyItem(5, 67., 75., false);
976+ verifyItem(6, 142., 75., false);
977+ verifyItem(7, 217., 75., false);
978+ verifyItem(8, 292., 75., false);
979+ verifyItem(9, 367., 75., false);
980+ verifyItem(10, 442., 150., false);
981+ verifyItem(11, 592., 200., true);
982+ QCOMPARE(lvwph->m_minYExtent, 750.);
983+ QCOMPARE(lvwph->m_clipItem->y(), -392.);
984+ QCOMPARE(lvwph->m_clipItem->clip(), true);
985+ QCOMPARE(lvwph->m_headerItem->y(), -442.);
986+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
987+ QCOMPARE(lvwph->contentY(), -442.);
988+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
989+ }
990+
991+ void testRemoveItemsAtTop()
992+ {
993+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
994+
995+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
996+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
997+ verifyItem(0, 50., 350., false);
998+ verifyItem(1, 400., 350., false);
999+ verifyItem(2, 750., 350., true);
1000+ QCOMPARE(lvwph->m_minYExtent, 0.);
1001+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1002+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1003+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1004+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1005+ QCOMPARE(lvwph->contentY(), 0.);
1006+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1007+ QVERIFY(lvwph->isAtYBeginning());
1008+ }
1009+
1010+ void testRemoveNonCreatedItemsAtTopWhenAtBottom()
1011+ {
1012+ scrollToBottom();
1013+
1014+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1015+
1016+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1017+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1018+ verifyItem(0, -508., 350., true);
1019+ verifyItem(1, -158, 350., false);
1020+ verifyItem(2, 192, 350., false);
1021+ QCOMPARE(lvwph->m_minYExtent, -350.);
1022+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1023+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1024+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1025+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1026+ QCOMPARE(lvwph->contentY(), 1258.);
1027+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1028+ }
1029+
1030+ void testRemoveLastItemsAtBottom()
1031+ {
1032+ scrollToBottom();
1033+
1034+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
1035+
1036+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1037+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1038+ verifyItem(0, -358., 200., true);
1039+ verifyItem(1, -158, 350., false);
1040+ verifyItem(2, 192, 350., false);
1041+ QCOMPARE(lvwph->m_minYExtent, 150.);
1042+ QCOMPARE(lvwph->m_clipItem->y(), 558.);
1043+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1044+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1045+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1046+ QCOMPARE(lvwph->contentY(), 558.);
1047+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1048+ }
1049+
1050+ void testRemoveItemOutOfViewport()
1051+ {
1052+ changeContentY(520);
1053+
1054+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1055+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1056+ verifyItem(0, -320., 200., true);
1057+ verifyItem(1, -120, 350., false);
1058+ verifyItem(2, 230, 350., false);
1059+ verifyItem(3, 580, 350., true);
1060+ QCOMPARE(lvwph->m_minYExtent, 162.5);
1061+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1062+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1063+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1064+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1065+ QCOMPARE(lvwph->contentY(), 520.);
1066+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1067+
1068+
1069+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1070+
1071+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1072+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1073+ verifyItem(0, -270., 150., true);
1074+ verifyItem(1, -120., 350., false);
1075+ verifyItem(2, 230, 350., false);
1076+ verifyItem(3, 580, 350., true);
1077+ QCOMPARE(lvwph->m_minYExtent, -200.);
1078+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1079+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1080+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1081+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1082+ QCOMPARE(lvwph->contentY(), 520.);
1083+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1084+ }
1085+
1086+ void testMoveFirstItems()
1087+ {
1088+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1089+
1090+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1091+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1092+ verifyItem(0, 50., 200., false);
1093+ verifyItem(1, 250., 150., false);
1094+ verifyItem(2, 400., 350., false);
1095+ verifyItem(3, 750., 350., true);
1096+ QCOMPARE(lvwph->m_minYExtent, 0.);
1097+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1098+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1099+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1100+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1101+ QCOMPARE(lvwph->contentY(), 0.);
1102+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1103+
1104+ }
1105+
1106+ void testMoveFirstOutOfVisibleItems()
1107+ {
1108+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
1109+
1110+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1111+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1112+ verifyItem(0, 50., 200., false);
1113+ verifyItem(1, 250., 350., false);
1114+ verifyItem(2, 600., 350., true);
1115+ QCOMPARE(lvwph->m_minYExtent, 0.);
1116+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1117+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1118+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1119+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1120+ QCOMPARE(lvwph->contentY(), 0.);
1121+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1122+
1123+ }
1124+
1125+ void testMoveFirstToLastAtBottom()
1126+ {
1127+ scrollToBottom();
1128+
1129+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1130+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1131+ verifyItem(0, -508., 350., true);
1132+ verifyItem(1, -158, 350., false);
1133+ verifyItem(2, 192, 350., false);
1134+ QCOMPARE(lvwph->m_minYExtent, 350.);
1135+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1136+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1137+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1138+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1139+ QCOMPARE(lvwph->contentY(), 1258.);
1140+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1141+
1142+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
1143+
1144+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1145+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1146+ verifyItem(0, -508., 350., true);
1147+ verifyItem(1, -158, 350., false);
1148+ verifyItem(2, 192, 350., false);
1149+ verifyItem(3, 542, 150., true);
1150+ QCOMPARE(lvwph->m_minYExtent, -100.);
1151+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1152+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1153+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1154+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1155+ QCOMPARE(lvwph->contentY(), 1258.);
1156+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1157+ QVERIFY(!lvwph->isAtYEnd());
1158+ }
1159+
1160+ void testChangeSizeVisibleItemNotOnViewport()
1161+ {
1162+ changeContentY(440);
1163+
1164+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1165+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1166+ verifyItem(0, -390., 150., true);
1167+ verifyItem(1, -240., 200., true);
1168+ verifyItem(2, -40, 350., false);
1169+ verifyItem(3, 310, 350., false);
1170+ verifyItem(4, 660, 350., true);
1171+ QCOMPARE(lvwph->m_minYExtent, 0.);
1172+ QCOMPARE(lvwph->m_clipItem->y(), 440.);
1173+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1174+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1175+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1176+ QCOMPARE(lvwph->contentY(), 440.);
1177+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1178+
1179+ model->setProperty(1, "size", 100);
1180+
1181+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1182+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1183+ verifyItem(0, -290., 150., true);
1184+ verifyItem(1, -140., 100., true);
1185+ verifyItem(2, -40, 350., false);
1186+ verifyItem(3, 310, 350., false);
1187+ verifyItem(4, 660, 350., true);
1188+ QCOMPARE(lvwph->m_minYExtent, -100.);
1189+ QCOMPARE(lvwph->m_clipItem->y(), 440.);
1190+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1191+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1192+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1193+ QCOMPARE(lvwph->contentY(), 440.);
1194+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1195+ }
1196+
1197+ void testShowHeaderCloseToTheTop()
1198+ {
1199+ changeContentY(375);
1200+
1201+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1202+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1203+ verifyItem(0, -325., 150., true);
1204+ verifyItem(1, -175, 200., false);
1205+ verifyItem(2, 25, 350., false);
1206+ verifyItem(3, 375, 350., false);
1207+ verifyItem(4, 725, 350., true);
1208+ QCOMPARE(lvwph->m_minYExtent, 0.);
1209+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
1210+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1211+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1212+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1213+ QCOMPARE(lvwph->contentY(), 375.);
1214+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
1215+
1216+ lvwph->showHeader();
1217+
1218+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1219+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1220+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1221+ verifyItem(0, -375., 150., true);
1222+ verifyItem(1, -225, 200., true);
1223+ verifyItem(2, -25, 350., false);
1224+ verifyItem(3, 325, 350., false);
1225+ verifyItem(4, 675, 350., true);
1226+ QCOMPARE(lvwph->m_minYExtent, 50.);
1227+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
1228+ QCOMPARE(lvwph->m_clipItem->clip(), true);
1229+ QCOMPARE(lvwph->m_headerItem->y(), 325.);
1230+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1231+ QCOMPARE(lvwph->contentY(), 325.);
1232+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);;
1233+
1234+ scrollToTop();
1235+
1236+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1237+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1238+ verifyItem(0, 50., 150., false);
1239+ verifyItem(1, 200., 200., false);
1240+ verifyItem(2, 400., 350., false);
1241+ verifyItem(3, 750., 350., true);
1242+ QCOMPARE(lvwph->m_minYExtent, 50.);
1243+ QCOMPARE(lvwph->m_clipItem->y(), -50.);
1244+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1245+ QCOMPARE(lvwph->m_headerItem->y(), -50.);
1246+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1247+ QCOMPARE(lvwph->contentY(), -50.);
1248+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1249+ }
1250+
1251+ void testShowHeaderAtBottom()
1252+ {
1253+ scrollToBottom();
1254+
1255+ lvwph->showHeader();
1256+
1257+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1258+ QTRY_VERIFY (lvwph->isAtYEnd());
1259+ }
1260+
1261+ void growWindow()
1262+ {
1263+ view->rootObject()->setHeight(850);
1264+
1265+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1266+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1267+ verifyItem(0, 50., 150., false);
1268+ verifyItem(1, 200., 200., false);
1269+ verifyItem(2, 400., 350., false);
1270+ verifyItem(3, 750., 350., false);
1271+ verifyItem(4, 1100., 350., true);
1272+ QCOMPARE(lvwph->m_minYExtent, 0.);
1273+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1274+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1275+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1276+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1277+ QCOMPARE(lvwph->contentY(), 0.);
1278+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1279+ }
1280+
1281+ void growWindowAtBottom()
1282+ {
1283+ scrollToBottom();
1284+
1285+ view->rootObject()->setHeight(850);
1286+
1287+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1288+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1289+ verifyItem(0, -550., 350., true);
1290+ verifyItem(1, -200., 350., false);
1291+ verifyItem(2, 150, 350., false);
1292+ verifyItem(3, 500, 350., false);
1293+ QCOMPARE(lvwph->m_minYExtent, 350.);
1294+ QCOMPARE(lvwph->m_clipItem->y(), 950.);
1295+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1296+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1297+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1298+ QCOMPARE(lvwph->contentY(), 950.);
1299+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1300+ }
1301+
1302+private:
1303+ QQuickView *view;
1304+ ListViewWithPageHeader *lvwph;
1305+ QQuickListModel *model;
1306+ QQmlComponent *otherDelegate;
1307+};
1308+
1309+QTEST_MAIN(ListViewWithPageHeaderTest)
1310+
1311+#include "listviewwithpageheadertest.moc"
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cppnew file+1567
@@ -0,0 +1,1567 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "listviewwithpageheader.h"
18+
19+#include <QAbstractItemModel>
20+#include <QQmlEngine>
21+#include <QQuickView>
22+#include <QtTestGui>
23+#include <private/qquicklistmodel_p.h>
24+#include <private/qquickanimation_p.h>
25+#include <private/qquickitem_p.h>
26+
27+class ListViewWithPageHeaderTestSection : public QObject
28+{
29+ Q_OBJECT
30+
31+private:
32+ void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled, const QString &sectionHeader, bool sectionHeaderCulled)
33+ {
34+ QTRY_VERIFY(visibleIndex < lvwph->m_visibleItems.count());
35+ QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->y(), pos);
36+ QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
37+ QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
38+ QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->m_sectionItem), sectionHeader);
39+ if (!sectionHeader.isNull()) {
40+ QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_sectionItem)->culled, sectionHeaderCulled);
41+ }
42+ }
43+
44+ void changeContentY(qreal change)
45+ {
46+ const qreal dest = lvwph->contentY() + change;
47+ if (dest > lvwph->contentY()) {
48+ const qreal jump = 25;
49+ while (lvwph->contentY() + jump < dest) {
50+ lvwph->setContentY(lvwph->contentY() + jump);
51+ QTest::qWait(1);
52+ }
53+ } else {
54+ const qreal jump = -25;
55+ while (lvwph->contentY() + jump > dest) {
56+ lvwph->setContentY(lvwph->contentY() + jump);
57+ QTest::qWait(1);
58+ }
59+ }
60+ lvwph->setContentY(dest);
61+ QTest::qWait(1);
62+ }
63+
64+ void scrollToTop()
65+ {
66+ const qreal jump = -25;
67+ while (!lvwph->isAtYBeginning()) {
68+ if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
69+ lvwph->setContentY(lvwph->contentY() + jump);
70+ } else {
71+ lvwph->setContentY(lvwph->contentY() - 1);
72+ }
73+ QTest::qWait(1);
74+ }
75+ }
76+
77+ void scrollToBottom()
78+ {
79+ const qreal jump = 25;
80+ while (!lvwph->isAtYEnd()) {
81+ if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
82+ lvwph->setContentY(lvwph->contentY() + jump);
83+ } else {
84+ lvwph->setContentY(lvwph->contentY() + 1);
85+ }
86+ QTest::qWait(1);
87+ }
88+ }
89+
90+ void verifyInitialTopPosition()
91+ {
92+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
93+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
94+ verifyItem(0, 50., 190., false, "Agressive", false);
95+ verifyItem(1, 240., 240., false, "Regular", false);
96+ verifyItem(2, 480., 390., false, "Mild", false);
97+ QCOMPARE(lvwph->m_minYExtent, 0.);
98+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
99+ QCOMPARE(lvwph->m_clipItem->clip(), false);
100+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
101+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
102+ QCOMPARE(lvwph->contentY(), 0.);
103+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
104+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
105+ }
106+
107+ QString section(QQuickItem *item)
108+ {
109+ return item ? QQmlEngine::contextForObject(item)->parentContext()->contextProperty(QLatin1String("section")).toString() : QString();
110+ }
111+
112+private Q_SLOTS:
113+
114+ void initTestCase()
115+ {
116+ }
117+
118+ void init()
119+ {
120+ view = new QQuickView();
121+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
122+ view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test_section.qml"));
123+ lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
124+ model = view->rootObject()->findChild<QQuickListModel*>();
125+ otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
126+ QVERIFY(lvwph);
127+ QVERIFY(model);
128+ QVERIFY(otherDelegate);
129+ view->show();
130+ QTest::qWaitForWindowExposed(view);
131+
132+ verifyInitialTopPosition();
133+ }
134+
135+ void cleanup()
136+ {
137+ delete view;
138+ }
139+
140+ void testCreationDeletion()
141+ {
142+ // Nothing, init/cleanup already tests this
143+ }
144+
145+ void testDrag1PixelUp()
146+ {
147+ lvwph->setContentY(lvwph->contentY() + 1);
148+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
149+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
150+ verifyItem(0, 49., 190., false, "Agressive", false);
151+ verifyItem(1, 239., 240., false, "Regular", false);
152+ verifyItem(2, 479., 390., false, "Mild", false);
153+ QCOMPARE(lvwph->m_minYExtent, 0.);
154+ QCOMPARE(lvwph->m_clipItem->y(), 1.);
155+ QCOMPARE(lvwph->m_clipItem->clip(), false);
156+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
157+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
158+ QCOMPARE(lvwph->contentY(), 1.);
159+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
160+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
161+ }
162+
163+ void testHeaderDetachDragDown()
164+ {
165+ QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
166+ QTest::qWait(100);
167+ QTest::mouseMove(view, QPoint(0, 5));
168+ QTest::qWait(100);
169+ QTest::mouseMove(view, QPoint(0, 10));
170+ QTest::qWait(100);
171+ QTest::mouseMove(view, QPoint(0, 15));
172+ QTest::qWait(100);
173+ QTest::mouseMove(view, QPoint(0, 20));
174+ QTest::qWait(100);
175+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
176+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
177+ verifyItem(0, 55., 190., false, "Agressive", false);
178+ verifyItem(1, 245., 240., false, "Regular", false);
179+ verifyItem(2, 485., 390., false, "Mild", false);
180+ QCOMPARE(lvwph->m_minYExtent, 0.);
181+ QCOMPARE(lvwph->m_clipItem->y(), -5.);
182+ QCOMPARE(lvwph->m_clipItem->clip(), false);
183+ QCOMPARE(lvwph->m_headerItem->y(), -5.);
184+ QCOMPARE(lvwph->m_headerItem->height(), 55.);
185+ QCOMPARE(lvwph->contentY(), -5.);
186+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
187+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
188+
189+ QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
190+
191+ verifyInitialTopPosition();
192+ }
193+
194+ void testDrag375PixelUp()
195+ {
196+ changeContentY(375);
197+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
198+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
199+ verifyItem(0, -325., 190., true, "Agressive", true);
200+ verifyItem(1, -135, 240., false, "Regular", true);
201+ verifyItem(2, 105, 390., false, "Mild", false);
202+ verifyItem(3, 495, 390., false, "Bold", false);
203+ QCOMPARE(lvwph->m_minYExtent, 0.);
204+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
205+ QCOMPARE(lvwph->m_clipItem->clip(), false);
206+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
207+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
208+ QCOMPARE(lvwph->contentY(), 375.);
209+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
210+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
211+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
212+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
213+ }
214+
215+ void testDrag520PixelUp()
216+ {
217+ changeContentY(520);
218+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
219+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
220+ verifyItem(0, -280., 240., true, "Regular", true);
221+ verifyItem(1, -40, 390., false, "Mild", true);
222+ verifyItem(2, 350, 390., false, "Bold", false);
223+ verifyItem(3, 740, 350., true, QString(), true);
224+ QCOMPARE(lvwph->m_minYExtent, 152.5);
225+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
226+ QCOMPARE(lvwph->m_clipItem->clip(), false);
227+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
228+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
229+ QCOMPARE(lvwph->contentY(), 520.);
230+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
231+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
232+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
233+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
234+ }
235+
236+ void testDragHeaderUpThenShow()
237+ {
238+ changeContentY(120);
239+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
240+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
241+ verifyItem(0, -70., 190., false, "Agressive", true);
242+ verifyItem(1, 120., 240., false, "Regular", false);
243+ verifyItem(2, 360., 390., false, "Mild", false);
244+ verifyItem(3, 750., 390., true, "Bold", true);
245+ QCOMPARE(lvwph->m_minYExtent, 0.);
246+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
247+ QCOMPARE(lvwph->m_clipItem->clip(), false);
248+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
249+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
250+ QCOMPARE(lvwph->contentY(), 120.);
251+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
252+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
253+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
254+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
255+
256+ changeContentY(-30);
257+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
258+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
259+ verifyItem(0, -70., 190., false, "Agressive", true);
260+ verifyItem(1, 120., 240., false, "Regular", false);
261+ verifyItem(2, 360., 390., false, "Mild", false);
262+ verifyItem(3, 750., 390., true, "Bold", true);
263+ QCOMPARE(lvwph->m_minYExtent, 0.);
264+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
265+ QCOMPARE(lvwph->m_clipItem->clip(), true);
266+ QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
267+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
268+ QCOMPARE(lvwph->contentY(), 90.);
269+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
270+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
271+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
272+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
273+ }
274+
275+ void testDragHeaderUpThenShowWithoutHidingTotally()
276+ {
277+ changeContentY(10);
278+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
279+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
280+ verifyItem(0, 40., 190., false, "Agressive", false);
281+ verifyItem(1, 230., 240., false, "Regular", false);
282+ verifyItem(2, 470., 390., false, "Mild", false);
283+ QCOMPARE(lvwph->m_minYExtent, 0.);
284+ QCOMPARE(lvwph->m_clipItem->y(), 10.);
285+ QCOMPARE(lvwph->m_clipItem->clip(), false);
286+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
287+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
288+ QCOMPARE(lvwph->contentY(), 10.);
289+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
290+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
291+
292+ changeContentY(-1);
293+
294+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
295+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
296+ verifyItem(0, 41., 190., false, "Agressive", false);
297+ verifyItem(1, 231., 240., false, "Regular", false);
298+ verifyItem(2, 471., 390., false, "Mild", false);
299+ QCOMPARE(lvwph->m_minYExtent, 0.);
300+ QCOMPARE(lvwph->m_clipItem->y(), 9.);
301+ QCOMPARE(lvwph->m_clipItem->clip(), false);
302+ QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
303+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
304+ QCOMPARE(lvwph->contentY(), 9.);
305+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
306+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
307+ }
308+
309+ void testPositionAtBeginningIndex0Visible()
310+ {
311+ changeContentY(375);
312+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
313+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
314+ verifyItem(0, -325., 190., true, "Agressive", true);
315+ verifyItem(1, -135, 240., false, "Regular", true);
316+ verifyItem(2, 105, 390., false, "Mild", false);
317+ verifyItem(3, 495, 390., false, "Bold", false);
318+ QCOMPARE(lvwph->m_minYExtent, 0.);
319+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
320+ QCOMPARE(lvwph->m_clipItem->clip(), false);
321+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
322+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
323+ QCOMPARE(lvwph->contentY(), 375.);
324+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
325+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
326+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
327+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
328+
329+ lvwph->positionAtBeginning();
330+
331+ verifyInitialTopPosition();
332+ }
333+
334+ void testPositionAtBeginningIndex0NotVisible()
335+ {
336+ changeContentY(520);
337+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
338+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
339+ verifyItem(0, -280., 240., true, "Regular", true);
340+ verifyItem(1, -40, 390., false, "Mild", true);
341+ verifyItem(2, 350, 390., false, "Bold", false);
342+ verifyItem(3, 740, 350., true, QString(), true);
343+ QCOMPARE(lvwph->m_minYExtent, 152.5);
344+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
345+ QCOMPARE(lvwph->m_clipItem->clip(), false);
346+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
347+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
348+ QCOMPARE(lvwph->contentY(), 520.);
349+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
350+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
351+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
352+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
353+
354+ lvwph->positionAtBeginning();
355+
356+ verifyInitialTopPosition();
357+ }
358+
359+ void testIndex0GrowOnScreen()
360+ {
361+ model->setProperty(0, "size", 400);
362+
363+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
364+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
365+ verifyItem(0, 50., 440., false, "Agressive", false);
366+ verifyItem(1, 490., 240., false, "Regular", false);
367+ verifyItem(2, 730., 390., true, "Mild", true);
368+ QCOMPARE(lvwph->m_minYExtent, 0.);
369+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
370+ QCOMPARE(lvwph->m_clipItem->clip(), false);
371+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
372+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
373+ QCOMPARE(lvwph->contentY(), 0.);
374+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
375+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
376+ }
377+
378+ void testIndex0GrowOffScreen()
379+ {
380+ changeContentY(375);
381+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
382+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
383+ verifyItem(0, -325., 190., true, "Agressive", true);
384+ verifyItem(1, -135, 240., false, "Regular", true);
385+ verifyItem(2, 105, 390., false, "Mild", false);
386+ verifyItem(3, 495, 390., false, "Bold", false);
387+ QCOMPARE(lvwph->m_minYExtent, 0.);
388+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
389+ QCOMPARE(lvwph->m_clipItem->clip(), false);
390+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
391+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
392+ QCOMPARE(lvwph->contentY(), 375.);
393+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
394+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
395+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
396+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
397+
398+ model->setProperty(0, "size", 400);
399+
400+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
401+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
402+ verifyItem(0, -575., 440., true, "Agressive", true);
403+ verifyItem(1, -135, 240., false, "Regular", true);
404+ verifyItem(2, 105, 390., false, "Mild", false);
405+ verifyItem(3, 495, 390., false, "Bold", false);
406+ QCOMPARE(lvwph->m_minYExtent, 250.);
407+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
408+ QCOMPARE(lvwph->m_clipItem->clip(), false);
409+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
410+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
411+ QCOMPARE(lvwph->contentY(), 375.);
412+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
413+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
414+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
415+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
416+
417+ scrollToTop();
418+
419+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
420+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
421+ verifyItem(0, 50., 440., false, "Agressive", false);
422+ verifyItem(1, 490, 240., false, "Regular", false);
423+ verifyItem(2, 730, 390., true, "Mild", true);
424+ QCOMPARE(lvwph->m_minYExtent, 250.);
425+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
426+ QCOMPARE(lvwph->m_clipItem->clip(), false);
427+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
428+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
429+ QCOMPARE(lvwph->contentY(), -250.);
430+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
431+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
432+
433+ changeContentY(30);
434+
435+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
436+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
437+ verifyItem(0, 20., 440., false, "Agressive", false);
438+ verifyItem(1, 460, 240., false, "Regular", false);
439+ verifyItem(2, 700, 390., true, "Mild", true);
440+ QCOMPARE(lvwph->m_minYExtent, 250.);
441+ QCOMPARE(lvwph->m_clipItem->y(), -220.);
442+ QCOMPARE(lvwph->m_clipItem->clip(), false);
443+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
444+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
445+ QCOMPARE(lvwph->contentY(), -220.);
446+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
447+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
448+ }
449+
450+ void testIndex0GrowNotCreated()
451+ {
452+ changeContentY(520);
453+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
454+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
455+ verifyItem(0, -280., 240., true, "Regular", true);
456+ verifyItem(1, -40, 390., false, "Mild", true);
457+ verifyItem(2, 350, 390., false, "Bold", false);
458+ verifyItem(3, 740, 350., true, QString(), true);
459+ QCOMPARE(lvwph->m_minYExtent, 152.5);
460+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
461+ QCOMPARE(lvwph->m_clipItem->clip(), false);
462+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
463+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
464+ QCOMPARE(lvwph->contentY(), 520.);
465+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
466+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
467+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
468+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
469+
470+ model->setProperty(0, "size", 400);
471+
472+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
473+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
474+ verifyItem(0, -280., 240., true, "Regular", true);
475+ verifyItem(1, -40, 390., false, "Mild", true);
476+ verifyItem(2, 350, 390., false, "Bold", false);
477+ verifyItem(3, 740, 350., true, QString(), true);
478+ QCOMPARE(lvwph->m_minYExtent, 152.5);
479+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
480+ QCOMPARE(lvwph->m_clipItem->clip(), false);
481+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
482+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
483+ QCOMPARE(lvwph->contentY(), 520.);
484+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
485+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
486+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
487+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
488+
489+ scrollToTop();
490+
491+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
492+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
493+ verifyItem(0, 50., 440., false, "Agressive", false);
494+ verifyItem(1, 490, 240., false, "Regular", false);
495+ verifyItem(2, 730, 390., true, "Mild", true);
496+ QCOMPARE(lvwph->m_minYExtent, 250.);
497+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
498+ QCOMPARE(lvwph->m_clipItem->clip(), false);
499+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
500+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
501+ QCOMPARE(lvwph->contentY(), -250.);
502+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
503+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
504+ }
505+
506+ void testShowHideShowHeaderAtBottom()
507+ {
508+ scrollToBottom();
509+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
510+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
511+ verifyItem(0, -588., 390., true, "Bold", true);
512+ verifyItem(1, -198, 350., false, QString(), true);
513+ verifyItem(2, 152, 390., false, "Lazy", false);
514+ QCOMPARE(lvwph->m_minYExtent, 310.);
515+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
516+ QCOMPARE(lvwph->m_clipItem->clip(), false);
517+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
518+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
519+ QCOMPARE(lvwph->contentY(), 1458.);
520+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
521+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
522+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
523+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
524+
525+ changeContentY(-30);
526+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
527+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
528+ verifyItem(0, -588., 390., true, "Bold", true);
529+ verifyItem(1, -198, 350., false, QString(), true);
530+ verifyItem(2, 152, 390., false, "Lazy", false);
531+ QCOMPARE(lvwph->m_minYExtent, 310.);
532+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
533+ QCOMPARE(lvwph->m_clipItem->clip(), true);
534+ QCOMPARE(lvwph->m_headerItem->y(), 1408.);
535+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
536+ QCOMPARE(lvwph->contentY(), 1428.);
537+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
538+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
539+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
540+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
541+
542+ changeContentY(30);
543+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
544+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
545+ verifyItem(0, -588., 390., true, "Bold", true);
546+ verifyItem(1, -198, 350., false, QString(), true);
547+ verifyItem(2, 152, 390., false, "Lazy", false);
548+ QCOMPARE(lvwph->m_minYExtent, 310.);
549+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
550+ QCOMPARE(lvwph->m_clipItem->clip(), false);
551+ QCOMPARE(lvwph->m_headerItem->y(), -310.);
552+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
553+ QCOMPARE(lvwph->contentY(), 1458.);
554+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
555+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
556+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
557+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
558+
559+ changeContentY(-30);
560+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
561+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
562+ verifyItem(0, -588., 390., true, "Bold", true);
563+ verifyItem(1, -198, 350., false, QString(), true);
564+ verifyItem(2, 152, 390., false, "Lazy", false);
565+ QCOMPARE(lvwph->m_minYExtent, 310.);
566+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
567+ QCOMPARE(lvwph->m_clipItem->clip(), true);
568+ QCOMPARE(lvwph->m_headerItem->y(), 1408.);
569+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
570+ QCOMPARE(lvwph->contentY(), 1428.);
571+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
572+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
573+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
574+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
575+ }
576+
577+ void testChangeDelegateAtBottom()
578+ {
579+ scrollToBottom();
580+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
581+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
582+ verifyItem(0, -588., 390., true, "Bold", true);
583+ verifyItem(1, -198, 350., false, QString(), true);
584+ verifyItem(2, 152, 390., false, "Lazy", false);
585+ QCOMPARE(lvwph->m_minYExtent, 310.);
586+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
587+ QCOMPARE(lvwph->m_clipItem->clip(), false);
588+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
589+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
590+ QCOMPARE(lvwph->contentY(), 1458.);
591+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
592+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
593+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
594+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
595+
596+ lvwph->setDelegate(otherDelegate);
597+
598+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
599+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
600+ verifyItem(0, 50., 75., false, "Agressive", false);
601+ verifyItem(1, 125, 75., false, "Regular", false);
602+ verifyItem(2, 200, 75., false, "Mild", false);
603+ verifyItem(3, 275, 75., false, "Bold", false);
604+ verifyItem(4, 350, 35., false, QString(), true);
605+ verifyItem(5, 385, 75., false, "Lazy", false);
606+ QCOMPARE(lvwph->m_minYExtent, 0.);
607+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
608+ QCOMPARE(lvwph->m_clipItem->clip(), false);
609+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
610+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
611+ QCOMPARE(lvwph->contentY(), 0.);
612+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
613+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
614+ }
615+
616+ void testSetEmptyHeaderAtTop()
617+ {
618+ lvwph->setHeader(nullptr);
619+
620+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
621+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
622+ verifyItem(0, 0., 190., false, "Agressive", false);
623+ verifyItem(1, 190., 240., false, "Regular", false);
624+ verifyItem(2, 430., 390., false, "Mild", false);
625+ QCOMPARE(lvwph->m_minYExtent, 0.);
626+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
627+ QCOMPARE(lvwph->m_clipItem->clip(), false);
628+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
629+ QCOMPARE(lvwph->contentY(), 0.);
630+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
631+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
632+ }
633+
634+ void testSetEmptyHeaderAtBottom()
635+ {
636+ scrollToBottom();
637+ lvwph->setHeader(nullptr);
638+
639+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
640+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
641+ verifyItem(0, -588., 390., true, "Bold", true);
642+ verifyItem(1, -198, 350., false, QString(), true);
643+ verifyItem(2, 152, 390., false, "Lazy", false);
644+ QCOMPARE(lvwph->m_minYExtent, 260.);
645+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
646+ QCOMPARE(lvwph->m_clipItem->clip(), false);
647+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
648+ QCOMPARE(lvwph->contentY(), 1458.);
649+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
650+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
651+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
652+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
653+
654+ scrollToTop();
655+
656+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
657+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
658+ verifyItem(0, 0., 190., false, "Agressive", false);
659+ verifyItem(1, 190., 240., false, "Regular", false);
660+ verifyItem(2, 430., 390., false, "Mild", false);
661+ QCOMPARE(lvwph->m_minYExtent, -50.);
662+ QCOMPARE(lvwph->m_clipItem->y(), 50.);
663+ QCOMPARE(lvwph->m_clipItem->clip(), false);
664+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
665+ QCOMPARE(lvwph->contentY(), 50.);
666+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
667+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
668+ }
669+
670+ void testSetEmptyHeaderWhenPartlyShownClipped()
671+ {
672+ scrollToBottom();
673+ changeContentY(-30);
674+ lvwph->setHeader(nullptr);
675+
676+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
677+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
678+ verifyItem(0, -588., 390., true, "Bold", true);
679+ verifyItem(1, -198, 350., false, QString(), true);
680+ verifyItem(2, 152, 390., false, "Lazy", false);
681+ QCOMPARE(lvwph->m_minYExtent, 290.);
682+ QCOMPARE(lvwph->m_clipItem->y(), 1428.);
683+ QCOMPARE(lvwph->m_clipItem->clip(), false);
684+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
685+ QCOMPARE(lvwph->contentY(), 1428.);
686+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
687+ QTRY_VERIFY(lvwph->isAtYEnd());
688+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
689+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
690+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
691+
692+ scrollToTop();
693+
694+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
695+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
696+ verifyItem(0, 0., 190., false, "Agressive", false);
697+ verifyItem(1, 190., 240., false, "Regular", false);
698+ verifyItem(2, 430., 390., false, "Mild", false);
699+ QCOMPARE(lvwph->m_minYExtent, -20.);
700+ QCOMPARE(lvwph->m_clipItem->y(), 20.);
701+ QCOMPARE(lvwph->m_clipItem->clip(), false);
702+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
703+ QCOMPARE(lvwph->contentY(), 20.);
704+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
705+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
706+ }
707+
708+ void testSetEmptyHeaderWhenPartlyShownNotClipped()
709+ {
710+ changeContentY(30);
711+ lvwph->setHeader(nullptr);
712+
713+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
714+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
715+ verifyItem(0, -30., 190., false, "Agressive", true);
716+ verifyItem(1, 160., 240., false, "Regular", false);
717+ verifyItem(2, 400., 390., false, "Mild", false);
718+ QCOMPARE(lvwph->m_minYExtent, 0.);
719+ QCOMPARE(lvwph->m_clipItem->y(), 30.);
720+ QCOMPARE(lvwph->m_clipItem->clip(), false);
721+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
722+ QCOMPARE(lvwph->contentY(), 30.);
723+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
724+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
725+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
726+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
727+ }
728+
729+ void testSetNullDelegate()
730+ {
731+ lvwph->setDelegate(nullptr);
732+
733+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
734+ QCOMPARE(lvwph->m_firstVisibleIndex, -1);
735+ QCOMPARE(lvwph->m_minYExtent, 0.);
736+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
737+ QCOMPARE(lvwph->m_clipItem->clip(), false);
738+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
739+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
740+ QCOMPARE(lvwph->contentY(), 0.);
741+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
742+ QTRY_COMPARE(lvwph->contentHeight(), 50.);
743+ QVERIFY(lvwph->isAtYBeginning());
744+ QVERIFY(lvwph->isAtYEnd());
745+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
746+ }
747+
748+ void testInsertItems()
749+ {
750+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
751+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
752+
753+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
754+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
755+ verifyItem(0, 50., 190., false, "Agressive", false);
756+ verifyItem(1, 240., 165., false, "Regular", false);
757+ verifyItem(2, 405., 140., false, "Agressive", false);
758+ verifyItem(3, 545., 240., true, "Regular", true);
759+ verifyItem(4, 785., 390., true, "Mild", true);
760+ QCOMPARE(lvwph->m_minYExtent, 0.);
761+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
762+ QCOMPARE(lvwph->m_clipItem->clip(), false);
763+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
764+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
765+ QCOMPARE(lvwph->contentY(), 0.);
766+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
767+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
768+ }
769+
770+ void testInsertItemsStealSectionItem()
771+ {
772+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
773+
774+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
775+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
776+ verifyItem(0, 50., 190., false, "Agressive", false);
777+ verifyItem(1, 240., 165., false, "Regular", false);
778+ verifyItem(2, 405., 200., false, QString(), true);
779+ verifyItem(3, 605., 390., true, "Mild", true);
780+ QCOMPARE(lvwph->m_minYExtent, 0.);
781+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
782+ QCOMPARE(lvwph->m_clipItem->clip(), false);
783+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
784+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
785+ QCOMPARE(lvwph->contentY(), 0.);
786+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
787+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
788+ }
789+
790+
791+ void testInsertItemsOnNotShownPosition()
792+ {
793+ changeContentY(800);
794+
795+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
796+ QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
797+ verifyItem(0, -320., 390., false, "Mild", true);
798+ verifyItem(1, 70, 390., false, "Bold", false);
799+ verifyItem(2, 460, 350., false, QString(), false);
800+ verifyItem(3, 810, 390., true, "Lazy", true);
801+ QCOMPARE(lvwph->m_minYExtent, 330.);
802+ QCOMPARE(lvwph->m_clipItem->y(), 800.);
803+ QCOMPARE(lvwph->m_clipItem->clip(), false);
804+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
805+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
806+ QCOMPARE(lvwph->contentY(), 800.);
807+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
808+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
809+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
810+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
811+
812+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
813+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
814+
815+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
816+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
817+ verifyItem(0, -320., 390., false, "Mild", true);
818+ verifyItem(1, 70, 390., false, "Bold", false);
819+ verifyItem(2, 460, 350., false, QString(), false);
820+ verifyItem(3, 810, 390., true, "Lazy", true);
821+ QCOMPARE(lvwph->m_minYExtent, 1090.);
822+ QCOMPARE(lvwph->m_clipItem->y(), 800.);
823+ QCOMPARE(lvwph->m_clipItem->clip(), false);
824+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
825+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
826+ QCOMPARE(lvwph->contentY(), 800.);
827+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
828+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
829+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
830+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
831+
832+ scrollToTop();
833+
834+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
835+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
836+ verifyItem(0, 50., 190., false, "Agressive", false);
837+ verifyItem(1, 240., 165., false, "Regular", false);
838+ verifyItem(2, 405., 140., false, "Agressive", false);
839+ verifyItem(3, 545., 240., true, "Regular", true);
840+ verifyItem(4, 785., 390., true, "Mild", true);
841+ QCOMPARE(lvwph->m_minYExtent, 305.);
842+ QCOMPARE(lvwph->m_clipItem->y(), -305.);
843+ QCOMPARE(lvwph->m_clipItem->clip(), false);
844+ QCOMPARE(lvwph->m_headerItem->y(), -305.);
845+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
846+ QCOMPARE(lvwph->contentY(), -305.);
847+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
848+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
849+ }
850+
851+ void testInsertItemsAtEndOfViewport()
852+ {
853+ changeContentY(60);
854+
855+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
856+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
857+ verifyItem(0, -10., 190., false, "Agressive", true);
858+ verifyItem(1, 180., 240., false, "Regular", false);
859+ verifyItem(2, 420., 390., false, "Mild", false);
860+ verifyItem(3, 810., 390., true, "Bold", true);
861+ QCOMPARE(lvwph->m_minYExtent, 0.);
862+ QCOMPARE(lvwph->m_clipItem->y(), 60.);
863+ QCOMPARE(lvwph->m_clipItem->clip(), false);
864+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
865+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
866+ QCOMPARE(lvwph->contentY(), 60.);
867+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
868+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
869+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
870+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
871+
872+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
873+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
874+
875+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
876+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
877+ verifyItem(0, -10., 190., false, "Agressive", true);
878+ verifyItem(1, 180., 240., false, "Regular", false);
879+ verifyItem(2, 420., 390., false, "Mild", false);
880+ verifyItem(3, 810., 165., true, "Regular", true);
881+ QCOMPARE(lvwph->m_minYExtent, 0.);
882+ QCOMPARE(lvwph->m_clipItem->y(), 60.);
883+ QCOMPARE(lvwph->m_clipItem->clip(), false);
884+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
885+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
886+ QCOMPARE(lvwph->contentY(), 60.);
887+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
888+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
889+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
890+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
891+ }
892+
893+ void testInsertItemsBeforeValidIndex()
894+ {
895+ changeContentY(520);
896+
897+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
898+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
899+ verifyItem(0, -280., 240., true, "Regular", true);
900+ verifyItem(1, -40, 390., false, "Mild", true);
901+ verifyItem(2, 350, 390., false, "Bold", false);
902+ verifyItem(3, 740, 350., true, QString(), true);
903+ QCOMPARE(lvwph->m_minYExtent, 152.5);
904+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
905+ QCOMPARE(lvwph->m_clipItem->clip(), false);
906+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
907+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
908+ QCOMPARE(lvwph->contentY(), 520.);
909+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
910+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
911+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
912+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
913+
914+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
915+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
916+
917+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
918+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
919+ verifyItem(0, -280., 240., true, "Regular", true);
920+ verifyItem(1, -40, 390., false, "Mild", true);
921+ verifyItem(2, 350, 390., false, "Bold", false);
922+ verifyItem(3, 740, 350., true, QString(), true);
923+ QCOMPARE(lvwph->m_minYExtent, 837.5);
924+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
925+ QCOMPARE(lvwph->m_clipItem->clip(), false);
926+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
927+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
928+ QCOMPARE(lvwph->contentY(), 520.);
929+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
930+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
931+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
932+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
933+ }
934+
935+ void testInsertItemsBeforeViewport()
936+ {
937+ changeContentY(375);
938+
939+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
940+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
941+ verifyItem(0, -325., 190., true, "Agressive", true);
942+ verifyItem(1, -135, 240., false, "Regular", true);
943+ verifyItem(2, 105, 390., false, "Mild", false);
944+ verifyItem(3, 495, 390., false, "Bold", false);
945+ QCOMPARE(lvwph->m_minYExtent, 0.);
946+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
947+ QCOMPARE(lvwph->m_clipItem->clip(), false);
948+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
949+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
950+ QCOMPARE(lvwph->contentY(), 375.);
951+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
952+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
953+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
954+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
955+
956+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
957+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
958+
959+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
960+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
961+ verifyItem(0, -275., 140., true, "Agressive", true);
962+ verifyItem(1, -135., 240., false, "Regular", true);
963+ verifyItem(2, 105, 390., false, "Mild", false);
964+ verifyItem(3, 495, 390., false, "Bold", false);
965+ QCOMPARE(lvwph->m_minYExtent, 530.);
966+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
967+ QCOMPARE(lvwph->m_clipItem->clip(), false);
968+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
969+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
970+ QCOMPARE(lvwph->contentY(), 375.);
971+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
972+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
973+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
974+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
975+
976+ scrollToTop();
977+
978+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
979+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
980+ verifyItem(0, 50., 190., false, "Agressive", false);
981+ verifyItem(1, 240., 165., false, "Regular", false);
982+ verifyItem(2, 405., 140., false, "Agressive", false);
983+ verifyItem(3, 545., 240., true, "Regular", true);
984+ verifyItem(4, 785., 390., true, "Mild", true);
985+ QCOMPARE(lvwph->m_minYExtent, 305.);
986+ QCOMPARE(lvwph->m_clipItem->y(), -305.);
987+ QCOMPARE(lvwph->m_clipItem->clip(), false);
988+ QCOMPARE(lvwph->m_headerItem->y(), -305.);
989+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
990+ QCOMPARE(lvwph->contentY(), -305.);
991+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
992+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
993+ }
994+
995+ void testInsertItemsAtBottom()
996+ {
997+ scrollToBottom();
998+
999+ QVERIFY(lvwph->isAtYEnd());
1000+
1001+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
1002+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
1003+
1004+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1005+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1006+ verifyItem(0, -588., 390., true, "Bold", true);
1007+ verifyItem(1, -198, 350., false, QString(), true);
1008+ verifyItem(2, 152, 390., false, "Lazy", false);
1009+ verifyItem(3, 542, 165., true, "Regular", true);
1010+ verifyItem(4, 707, 140., true, "Agressive", true);
1011+ QCOMPARE(lvwph->m_minYExtent, 41.);
1012+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1013+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1014+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1015+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1016+ QCOMPARE(lvwph->contentY(), 1458.);
1017+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1018+ QVERIFY(!lvwph->isAtYEnd());
1019+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1020+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1021+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1022+
1023+ scrollToBottom();
1024+
1025+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1026+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
1027+ verifyItem(0, -503., 350., true, QString(), true);
1028+ verifyItem(1, -153, 390., false, "Lazy", true);
1029+ verifyItem(2, 237, 165., false, "Regular", false);
1030+ verifyItem(3, 402, 140., false, "Agressive", false);
1031+ QCOMPARE(lvwph->m_minYExtent, -165.);
1032+ QCOMPARE(lvwph->m_clipItem->y(), 1763.);
1033+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1034+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1035+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1036+ QCOMPARE(lvwph->contentY(), 1763.);
1037+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1038+ QVERIFY(lvwph->isAtYEnd());
1039+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1040+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Lazy"));
1041+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1042+ }
1043+
1044+ void testInsertItemAtTop()
1045+ {
1046+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1047+
1048+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1049+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1050+ verifyItem(0, 50., 115., false, "Agressive", false);
1051+ verifyItem(1, 165., 150., false, QString(), false);
1052+ verifyItem(2, 315., 240., false, "Regular", false);
1053+ verifyItem(3, 555., 390., true, "Mild", true);
1054+ QCOMPARE(lvwph->m_minYExtent, 0.);
1055+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1056+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1057+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1058+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1059+ QCOMPARE(lvwph->contentY(), 0.);
1060+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1061+ QVERIFY(lvwph->isAtYBeginning());
1062+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1063+ }
1064+
1065+ void testInsertItem10SmallItemsAtTopWhenAtBottom()
1066+ {
1067+ scrollToBottom();
1068+
1069+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1070+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1071+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1072+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1073+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1074+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1075+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1076+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1077+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1078+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1079+
1080+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1081+ QCOMPARE(lvwph->m_firstVisibleIndex, 13);
1082+ verifyItem(0, -588., 390., true, "Bold", true);
1083+ verifyItem(1, -198, 350., false, QString(), true);
1084+ verifyItem(2, 152, 390., false, "Lazy", false);
1085+ QCOMPARE(lvwph->m_minYExtent, 12230./3.);
1086+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1087+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1088+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1089+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1090+ QCOMPARE(lvwph->contentY(), 1458.);
1091+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1092+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1093+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1094+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1095+
1096+ changeContentY(-1700);
1097+
1098+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 10);
1099+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1100+ verifyItem(0, -323., 75., true, QString(), true);
1101+ verifyItem(1, -248., 115., true, "Regular", true);
1102+ verifyItem(2, -133., 75., true, QString(), true);
1103+ verifyItem(3, -58., 75., false, QString(), true);
1104+ verifyItem(4, 17., 115., false, "Agressive", false);
1105+ verifyItem(5, 132., 75., false, QString(), true);
1106+ verifyItem(6, 207., 75., false, QString(), true);
1107+ verifyItem(7, 282., 150., false, QString(), true);
1108+ verifyItem(8, 432., 240., false, "Regular", false);
1109+ QCOMPARE(lvwph->m_minYExtent, 980.5);
1110+ QCOMPARE(lvwph->m_clipItem->y(), -192.);
1111+ QCOMPARE(lvwph->m_clipItem->clip(), true);
1112+ QCOMPARE(lvwph->m_headerItem->y(), -242.);
1113+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1114+ QCOMPARE(lvwph->contentY(), -242.);
1115+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
1116+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1117+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1118+ QCOMPARE(lvwph->m_topSectionItem->y(), -23.);
1119+ }
1120+
1121+ void testRemoveItemsAtTop()
1122+ {
1123+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1124+
1125+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 2);
1126+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1127+ verifyItem(0, 50., 390., false, "Mild", false);
1128+ verifyItem(1, 440., 390., false, "Bold", false);
1129+ QCOMPARE(lvwph->m_minYExtent, 0.);
1130+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1131+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1132+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1133+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1134+ QCOMPARE(lvwph->contentY(), 0.);
1135+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1136+ QVERIFY(lvwph->isAtYBeginning());
1137+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1138+ }
1139+
1140+ void testRemoveNonCreatedItemsAtTopWhenAtBottom()
1141+ {
1142+ scrollToBottom();
1143+
1144+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1145+
1146+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1147+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1148+ verifyItem(0, -588., 390., true, "Bold", true);
1149+ verifyItem(1, -198, 350., false, QString(), true);
1150+ verifyItem(2, 152, 390., false, "Lazy", false);
1151+ QCOMPARE(lvwph->m_minYExtent, -1330./3.);
1152+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1153+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1154+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1155+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1156+ QCOMPARE(lvwph->contentY(), 1458.);
1157+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1158+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1159+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1160+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1161+ }
1162+
1163+ void testRemoveLastItemsAtBottom()
1164+ {
1165+ scrollToBottom();
1166+
1167+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
1168+
1169+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1170+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1171+ verifyItem(0, -478., 240., true, "Regular", true);
1172+ verifyItem(1, -238, 390., false, "Mild", true);
1173+ verifyItem(2, 152, 390., false, "Bold", false);
1174+ QCOMPARE(lvwph->m_minYExtent, 150.);
1175+ QCOMPARE(lvwph->m_clipItem->y(), 718.);
1176+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1177+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1178+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1179+ QCOMPARE(lvwph->contentY(), 718.);
1180+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1181+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1182+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1183+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1184+ }
1185+
1186+ void testRemoveItemOutOfViewport()
1187+ {
1188+ changeContentY(520);
1189+
1190+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1191+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1192+ verifyItem(0, -280., 240., true, "Regular", true);
1193+ verifyItem(1, -40, 390., false, "Mild", true);
1194+ verifyItem(2, 350, 390., false, "Bold", false);
1195+ verifyItem(3, 740, 350., true, QString(), true);
1196+ QCOMPARE(lvwph->m_minYExtent, 152.5);
1197+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1198+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1199+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1200+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1201+ QCOMPARE(lvwph->contentY(), 520.);
1202+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1203+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1204+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1205+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1206+
1207+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1208+
1209+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1210+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1211+ verifyItem(0, -230., 190., true, "Agressive", true);
1212+ verifyItem(1, -40, 390., false, "Mild", true);
1213+ verifyItem(2, 350, 390., false, "Bold", false);
1214+ verifyItem(3, 740, 350., true, QString(), true);
1215+ QCOMPARE(lvwph->m_minYExtent, -240.);
1216+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1217+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1218+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1219+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1220+ QCOMPARE(lvwph->contentY(), 520.);
1221+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1222+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1223+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1224+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1225+ }
1226+
1227+ void testRemoveFirstOfCategory()
1228+ {
1229+ changeContentY(520);
1230+
1231+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1232+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1233+ verifyItem(0, -280., 240., true, "Regular", true);
1234+ verifyItem(1, -40, 390., false, "Mild", true);
1235+ verifyItem(2, 350, 390., false, "Bold", false);
1236+ verifyItem(3, 740, 350., true, QString(), true);
1237+ QCOMPARE(lvwph->m_minYExtent, 152.5);
1238+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1239+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1240+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1241+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1242+ QCOMPARE(lvwph->contentY(), 520.);
1243+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1244+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1245+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1246+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1247+
1248+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 3), Q_ARG(QVariant, 1));
1249+
1250+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1251+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1252+ verifyItem(0, -280., 240., true, "Regular", true);
1253+ verifyItem(1, -40, 390., false, "Mild", true);
1254+ verifyItem(2, 350, 390., false, "Bold", false);
1255+ verifyItem(3, 740, 390., true, "Lazy", true);
1256+ QCOMPARE(lvwph->m_minYExtent, 162.5);
1257+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
1258+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1259+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1260+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1261+ QCOMPARE(lvwph->contentY(), 520.);
1262+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1263+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1264+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1265+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1266+ }
1267+
1268+ void testAddSecondToCulledCategoryOfTwo()
1269+ {
1270+ // Do some setup
1271+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1272+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1273+
1274+ changeContentY(200);
1275+
1276+ // Very the items are culled
1277+ verifyItem(0, -150., 65., true, "Agressive", true);
1278+ verifyItem(1, -85, 25., true, QString(), true);
1279+
1280+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1281+
1282+ // Very the new item is there correctly
1283+ verifyItem(0, -175., 65., true, "Agressive", true);
1284+ verifyItem(1, -110, 25., true, QString(), true);
1285+ verifyItem(2, -85, 25., true, QString(), true);
1286+ }
1287+
1288+ void testMoveFirstItems()
1289+ {
1290+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1291+
1292+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1293+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1294+ verifyItem(0, 50., 240., false, "Regular", false);
1295+ verifyItem(1, 290., 190., false, "Agressive", false);
1296+ verifyItem(2, 480., 390., false, "Mild", false);
1297+ QCOMPARE(lvwph->m_minYExtent, 0.);
1298+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1299+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1300+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1301+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1302+ QCOMPARE(lvwph->contentY(), 0.);
1303+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1304+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1305+ }
1306+
1307+ void testMoveFirstOutOfVisibleItems()
1308+ {
1309+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
1310+
1311+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1312+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1313+ verifyItem(0, 50., 240., false, "Regular", false);
1314+ verifyItem(1, 290., 390., false, "Mild", false);
1315+ verifyItem(2, 680., 390., true, "Bold", true);
1316+ QCOMPARE(lvwph->m_minYExtent, 0.);
1317+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1318+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1319+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1320+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1321+ QCOMPARE(lvwph->contentY(), 0.);
1322+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1323+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1324+ }
1325+
1326+ void testMoveFirstToLastAtBottom()
1327+ {
1328+ scrollToBottom();
1329+
1330+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1331+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1332+ verifyItem(0, -588., 390., true, "Bold", true);
1333+ verifyItem(1, -198, 350., false, QString(), true);
1334+ verifyItem(2, 152, 390., false, "Lazy", false);
1335+ QCOMPARE(lvwph->m_minYExtent, 310.);
1336+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1337+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1338+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1339+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1340+ QCOMPARE(lvwph->contentY(), 1458.);
1341+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1342+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1343+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1344+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1345+
1346+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
1347+
1348+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1349+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1350+ verifyItem(0, -588., 390., true, "Bold", true);
1351+ verifyItem(1, -198, 350., false, QString(), true);
1352+ verifyItem(2, 152, 390., false, "Lazy", false);
1353+ verifyItem(3, 542, 190., true, "Agressive", true);
1354+ QCOMPARE(lvwph->m_minYExtent, -160.);
1355+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1356+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1357+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1358+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1359+ QCOMPARE(lvwph->contentY(), 1458.);
1360+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1361+ QVERIFY(!lvwph->isAtYEnd());
1362+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1363+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1364+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1365+ }
1366+
1367+ void testChangeSizeVisibleItemNotOnViewport()
1368+ {
1369+ changeContentY(490);
1370+
1371+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1372+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1373+ verifyItem(0, -440., 190., true, "Agressive", true);
1374+ verifyItem(1, -250., 240., true, "Regular", true);
1375+ verifyItem(2, -10, 390., false, "Mild", true);
1376+ verifyItem(3, 380, 390., false, "Bold", false);
1377+ verifyItem(4, 770, 350., true, QString(), true);
1378+ QCOMPARE(lvwph->m_minYExtent, 0.);
1379+ QCOMPARE(lvwph->m_clipItem->y(), 490.);
1380+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1381+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1382+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1383+ QCOMPARE(lvwph->contentY(), 490.);
1384+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1385+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1386+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1387+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1388+
1389+ model->setProperty(1, "size", 100);
1390+
1391+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1392+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1393+ verifyItem(0, -340., 190., true, "Agressive", true);
1394+ verifyItem(1, -150., 140., true, "Regular", true);
1395+ verifyItem(2, -10, 390., false, "Mild", true);
1396+ verifyItem(3, 380, 390., false, "Bold", false);
1397+ verifyItem(4, 770, 350., true, QString(), true);
1398+ QCOMPARE(lvwph->m_minYExtent, -100.);
1399+ QCOMPARE(lvwph->m_clipItem->y(), 490.);
1400+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1401+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1402+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1403+ QCOMPARE(lvwph->contentY(), 490.);
1404+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1405+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1406+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1407+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1408+ }
1409+
1410+ void testShowHeaderCloseToTheTop()
1411+ {
1412+ changeContentY(375);
1413+
1414+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1415+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1416+ verifyItem(0, -325., 190., true, "Agressive", true);
1417+ verifyItem(1, -135, 240., false, "Regular", true);
1418+ verifyItem(2, 105, 390., false, "Mild", false);
1419+ verifyItem(3, 495, 390., false, "Bold", false);
1420+ QCOMPARE(lvwph->m_minYExtent, 0.);
1421+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
1422+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1423+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1424+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1425+ QCOMPARE(lvwph->contentY(), 375.);
1426+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
1427+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1428+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1429+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1430+
1431+ lvwph->showHeader();
1432+
1433+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1434+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1435+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1436+ verifyItem(0, -375., 190., true, "Agressive", true);
1437+ verifyItem(1, -185, 240., false, "Regular", true);
1438+ verifyItem(2, 55, 390., false, "Mild", false);
1439+ verifyItem(3, 445, 390., false, "Bold", false);
1440+ QCOMPARE(lvwph->m_minYExtent, 50.);
1441+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
1442+ QCOMPARE(lvwph->m_clipItem->clip(), true);
1443+ QCOMPARE(lvwph->m_headerItem->y(), 325.);
1444+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1445+ QCOMPARE(lvwph->contentY(), 325.);
1446+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
1447+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1448+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1449+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1450+
1451+ scrollToTop();
1452+
1453+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1454+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1455+ verifyItem(0, 50., 190., false, "Agressive", false);
1456+ verifyItem(1, 240., 240., false, "Regular", false);
1457+ verifyItem(2, 480., 390., false, "Mild", false);
1458+ QCOMPARE(lvwph->m_minYExtent, 50.);
1459+ QCOMPARE(lvwph->m_clipItem->y(), -50.);
1460+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1461+ QCOMPARE(lvwph->m_headerItem->y(), -50.);
1462+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1463+ QCOMPARE(lvwph->contentY(), -50.);
1464+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1465+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1466+ }
1467+
1468+ void testShowHeaderAtBottom()
1469+ {
1470+ scrollToBottom();
1471+
1472+ lvwph->showHeader();
1473+
1474+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1475+ QTRY_VERIFY (lvwph->isAtYEnd());
1476+ }
1477+
1478+ void growWindow()
1479+ {
1480+ view->rootObject()->setHeight(850);
1481+
1482+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1483+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1484+ verifyItem(0, 50., 190., false, "Agressive", false);
1485+ verifyItem(1, 240., 240., false, "Regular", false);
1486+ verifyItem(2, 480., 390., false, "Mild", false);
1487+ verifyItem(3, 870., 390., true, "Bold", true);
1488+ verifyItem(4, 1260., 350., true, QString(), true);
1489+ QCOMPARE(lvwph->m_minYExtent, 0.);
1490+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1491+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1492+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1493+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1494+ QCOMPARE(lvwph->contentY(), 0.);
1495+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1496+ }
1497+
1498+ void growWindowAtBottom()
1499+ {
1500+ // Need a bunch small items at the bottom to trigger the problem
1501+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1502+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1503+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1504+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1505+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1506+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1507+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1508+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1509+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1510+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1511+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1512+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1513+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1514+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1515+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1516+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1517+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1518+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1519+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1520+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1521+
1522+ scrollToBottom();
1523+
1524+ view->rootObject()->setHeight(850);
1525+
1526+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 21);
1527+ QCOMPARE(lvwph->m_firstVisibleIndex, 5);
1528+ verifyItem(0, -660., 390., true, "Lazy", true);
1529+ verifyItem(1, -270., 90., true, "Agressive", true);
1530+ verifyItem(2, -180, 50., true, QString(), true);
1531+ verifyItem(3, -130, 50., true, QString(), true);
1532+ verifyItem(4, -80, 50., true, QString(), true);
1533+ verifyItem(5, -30, 50., false, QString(), true);
1534+ verifyItem(6, 20, 50., false, QString(), true);
1535+ verifyItem(7, 70, 50., false, QString(), true);
1536+ verifyItem(8, 120, 50., false, QString(), true);
1537+ verifyItem(9, 170, 90., false, "Regular", false);
1538+ verifyItem(10, 260, 50., false, QString(), true);
1539+ verifyItem(11, 310, 50., false, QString(), true);
1540+ verifyItem(12, 360, 50., false, QString(), true);
1541+ verifyItem(13, 410, 50., false, QString(), true);
1542+ verifyItem(14, 460, 50., false, QString(), true);
1543+ verifyItem(15, 510, 90., false, "Agressive", false);
1544+ verifyItem(16, 600, 50., false, QString(), true);
1545+ verifyItem(17, 650, 50., false, QString(), true);
1546+ verifyItem(18, 700, 50., false, QString(), true);
1547+ verifyItem(19, 750, 50., false, QString(), true);
1548+ verifyItem(20, 800, 50., false, QString(), true);
1549+ QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - 1970 + 50);
1550+ QCOMPARE(lvwph->m_clipItem->y(), 1970.);
1551+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1552+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1553+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1554+ QCOMPARE(lvwph->contentY(), 1970.);
1555+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1556+ }
1557+
1558+private:
1559+ QQuickView *view;
1560+ ListViewWithPageHeader *lvwph;
1561+ QQuickListModel *model;
1562+ QQmlComponent *otherDelegate;
1563+};
1564+
1565+QTEST_MAIN(ListViewWithPageHeaderTestSection)
1566+
1567+#include "listviewwithpageheadertestsection.moc"
tests/plugins/ListViewWithPageHeader/test.qmlnew file+96
@@ -0,0 +1,96 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import ListViewWithPageHeader 0.1
19+
20+Rectangle {
21+ width: 300
22+ height: 542
23+ color: "lightblue"
24+
25+ ListModel {
26+ id: model
27+
28+ function insertItem(index, size) {
29+ insert(index, { size: size });
30+ }
31+
32+ function removeItems(index, count) {
33+ remove(index, count);
34+ }
35+
36+ function moveItems(indexFrom, indexTo, count) {
37+ move(indexFrom, indexTo, count);
38+ }
39+
40+ ListElement { size: 150 }
41+ ListElement { size: 200 }
42+ ListElement { size: 350 }
43+ ListElement { size: 350 }
44+ ListElement { size: 350 }
45+ ListElement { size: 350 }
46+ }
47+
48+ Component {
49+ id: otherRect
50+ Rectangle {
51+ height: 35
52+ width: parent.width
53+ color: index % 2 == 0 ? "yellow" : "purple"
54+ }
55+ }
56+
57+ ListViewWithPageHeader {
58+ id: listView
59+ width: parent.width
60+ anchors.top: parent.top
61+ anchors.bottom: parent.bottom
62+ model: model
63+ delegate: Rectangle {
64+ property bool timerDone: false
65+ width: parent.width - 20
66+ x: 10
67+ color: index % 2 == 0 ? "red" : "blue"
68+ height: timerDone ? size : 350
69+ Text {
70+ text: index
71+ }
72+ Timer {
73+ id: sizeTimer
74+ interval: 10;
75+ onTriggered: {
76+ timerDone = true
77+ }
78+ }
79+ Component.onCompleted: {
80+ sizeTimer.start()
81+ }
82+ }
83+
84+ pageHeader: Rectangle {
85+ color: "transparent"
86+ width: parent.width
87+ height: 50
88+ implicitHeight: 50
89+ Text {
90+ anchors.fill: parent
91+ text: "APPS"
92+ font.pixelSize: 40
93+ }
94+ }
95+ }
96+}
tests/plugins/ListViewWithPageHeader/test_section.qmlnew file+106
@@ -0,0 +1,106 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import ListViewWithPageHeader 0.1
19+
20+Rectangle {
21+ width: 300
22+ height: 542
23+ color: "lightblue"
24+
25+ ListModel {
26+ id: model
27+
28+ function insertItem(index, size, type) {
29+ insert(index, { size: size, type: type });
30+ }
31+
32+ function removeItems(index, count) {
33+ remove(index, count);
34+ }
35+
36+ function moveItems(indexFrom, indexTo, count) {
37+ move(indexFrom, indexTo, count);
38+ }
39+
40+ ListElement { type: "Agressive"; size: 150 }
41+ ListElement { type: "Regular"; size: 200 }
42+ ListElement { type: "Mild"; size: 350 }
43+ ListElement { type: "Bold"; size: 350 }
44+ ListElement { type: "Bold"; size: 350 }
45+ ListElement { type: "Lazy"; size: 350 }
46+ }
47+
48+ Component {
49+ id: otherRect
50+ Rectangle {
51+ height: 35
52+ width: parent.width
53+ color: index % 2 == 0 ? "yellow" : "purple"
54+ }
55+ }
56+
57+ ListViewWithPageHeader {
58+ id: listView
59+ width: parent.width
60+ anchors.top: parent.top
61+ anchors.bottom: parent.bottom
62+ model: model
63+ delegate: Rectangle {
64+ property bool timerDone: false
65+ width: parent.width - 20
66+ x: 10
67+ color: index % 2 == 0 ? "red" : "blue"
68+ height: timerDone ? size : 350
69+ Text {
70+ text: index
71+ }
72+ Timer {
73+ id: sizeTimer
74+ interval: 10;
75+ onTriggered: {
76+ timerDone = true
77+ }
78+ }
79+ Component.onCompleted: {
80+ sizeTimer.start()
81+ }
82+ }
83+
84+ pageHeader: Rectangle {
85+ color: "transparent"
86+ width: parent.width
87+ height: 50
88+ implicitHeight: 50
89+ Text {
90+ anchors.fill: parent
91+ text: "APPS"
92+ font.pixelSize: 40
93+ }
94+ }
95+
96+ sectionProperty: "type"
97+ sectionDelegate: Component {
98+ Rectangle {
99+ color: "green"
100+ height: 40
101+ Text { text: section; font.pixelSize: 34 }
102+ anchors { left: parent.left; right: parent.right }
103+ }
104+ }
105+ }
106+}
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+92
@@ -65,6 +65,9 @@ private Q_SLOTS:
65 void minSpeed_data();
65 void minSpeed_data();
66 void recognitionTimerUsage();
66 void recognitionTimerUsage();
67 void maxSilenceTime();
67 void maxSilenceTime();
68+ void sceneXAndX();
69+ void sceneYAndY();
70+ void emitDraggingChangedOnDirectRecognition();
68
71
69private:
72private:
70 QQuickView *createView();
73 QQuickView *createView();
@@ -454,6 +457,95 @@ void tst_DirectionalDragArea::maxSilenceTime()
454 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
457 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
455}
458}
456
459
460+/*
461+ Checks that it informs the X coordinate of the touch point in local and scene coordinates
462+ correctly.
463+ */
464+void tst_DirectionalDragArea::sceneXAndX()
465+{
466+ DirectionalDragArea *edgeDragArea =
467+ view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
468+ QVERIFY(edgeDragArea != 0);
469+ edgeDragArea->setRecognitionTimer(fakeTimer);
470+ edgeDragArea->setTimeSource(fakeTimeSource);
471+
472+ QPointF touchScenePos(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
473+
474+ fakeTimeSource->m_msecsSinceReference = 0;
475+ QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
476+
477+ QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
478+ QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
479+
480+ touchScenePos.rx() = view->width() / 2;
481+ QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
482+
483+ QCOMPARE(touchXSpy.count(), 1);
484+ QCOMPARE(touchSceneXSpy.count(), 1);
485+ QCOMPARE(edgeDragArea->touchX(), touchScenePos.x() - edgeDragArea->x());
486+ QCOMPARE(edgeDragArea->touchSceneX(), touchScenePos.x());
487+}
488+
489+/*
490+ Checks that it informs the Y coordinate of the touch point in local and scene coordinates
491+ correctly.
492+ */
493+void tst_DirectionalDragArea::sceneYAndY()
494+{
495+ DirectionalDragArea *edgeDragArea =
496+ view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
497+ QVERIFY(edgeDragArea != 0);
498+ edgeDragArea->setRecognitionTimer(fakeTimer);
499+ edgeDragArea->setTimeSource(fakeTimeSource);
500+
501+ QPointF touchScenePos(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
502+
503+ fakeTimeSource->m_msecsSinceReference = 0;
504+ QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
505+
506+ QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
507+ QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
508+
509+ touchScenePos.ry() = view->height() / 2;
510+ QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
511+
512+ QCOMPARE(touchYSpy.count(), 1);
513+ QCOMPARE(touchSceneYSpy.count(), 1);
514+ QCOMPARE(edgeDragArea->touchY(), touchScenePos.y() - edgeDragArea->y());
515+ QCOMPARE(edgeDragArea->touchSceneY(), touchScenePos.y());
516+}
517+
518+/*
519+ Emit draggingChanged() when status change from WaitingForTouch directly to Recognized.
520+ This happens when the gesture recognition is effectively disabled (e.g. by setting a
521+ distanceThreshold of zero)
522+ */
523+void tst_DirectionalDragArea::emitDraggingChangedOnDirectRecognition()
524+{
525+ DirectionalDragArea *edgeDragArea =
526+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
527+ QVERIFY(edgeDragArea != 0);
528+ edgeDragArea->setRecognitionTimer(fakeTimer);
529+ edgeDragArea->setTimeSource(fakeTimeSource);
530+
531+ QPointF touchPos = calculateInitialTouchPos(edgeDragArea, view);
532+
533+ QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
534+
535+ // That will make it completetly skip the recognition phase ("Undecided" status).
536+ edgeDragArea->setDistanceThreshold(0.0);
537+
538+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
539+ QCOMPARE(edgeDragArea->dragging(), false);
540+
541+ fakeTimeSource->m_msecsSinceReference = 0;
542+ QTest::touchEvent(view, device).press(0, touchPos.toPoint());
543+
544+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
545+ QCOMPARE(draggingSpy.count(), 1);
546+ QCOMPARE(edgeDragArea->dragging(), true);
547+}
548+
457QTEST_MAIN(tst_DirectionalDragArea)
549QTEST_MAIN(tst_DirectionalDragArea)
458
550
459#include "tst_DirectionalDragArea.moc"
551#include "tst_DirectionalDragArea.moc"
tests/qmltests/CMakeLists.txt−1
@@ -28,7 +28,6 @@ set(qmltest_DEFAULT_PROPERTIES "")
28add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
28add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
29add_qml_test(Components DraggingArea)
29add_qml_test(Components DraggingArea)
30add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
30add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
31−add_qml_test(Components ListViewWithPageHeader)
32add_qml_test(Components ResponsiveFlowView)
31add_qml_test(Components ResponsiveFlowView)
33add_qml_test(Components ResponsiveGridView)
32add_qml_test(Components ResponsiveGridView)
34add_qml_test(Components Revealer)
33add_qml_test(Components Revealer)
tests/qmltests/Components/tst_DragHandle.cpp+100
@@ -59,6 +59,9 @@ private Q_SLOTS:
59
59
60 void dragThreshold_horizontal();
60 void dragThreshold_horizontal();
61 void dragThreshold_vertical();
61 void dragThreshold_vertical();
62+ void stretch_horizontal();
63+ void stretch_vertical();
64+ void hintingAnimation();
62
65
63private:
66private:
64 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
67 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
@@ -290,6 +293,103 @@ void tst_DragHandle::dragThreshold_vertical()
290 QCOMPARE(parentItem->property("shown").toBool(), false);
293 QCOMPARE(parentItem->property("shown").toBool(), false);
291}
294}
292
295
296+/*
297+ Checks that when the stretch property is true, dragging the DragHandle increases
298+ the width or height (depending on its direction) of its parent Showable
299+ */
300+void tst_DragHandle::stretch_horizontal()
301+{
302+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
303+ qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
304+ QQuickItem *parentItem = dragHandle->parentItem();
305+
306+ // enable strech mode
307+ m_view->rootObject()->setProperty("stretch", QVariant(true));
308+
309+ QCOMPARE(parentItem->width(), 0.0);
310+
311+ // flick all the way
312+ flickAndHold(dragHandle, totalDragDistance);
313+
314+ // should keep going until completion
315+ // Parent item should now have its full height
316+ tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
317+ QCOMPARE(parentItem->property("shown").toBool(), true);
318+
319+ dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
320+
321+ // flick all the way
322+ flickAndHold(dragHandle, totalDragDistance);
323+
324+ // should keep going until completion
325+ // Parent item should now have its full height
326+ tryCompare([&](){ return parentItem->width(); }, 0.0);
327+ QCOMPARE(parentItem->property("shown").toBool(), false);
328+}
329+
330+void tst_DragHandle::stretch_vertical()
331+{
332+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
333+ qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
334+ QQuickItem *parentItem = dragHandle->parentItem();
335+
336+ // enable strech mode
337+ m_view->rootObject()->setProperty("stretch", QVariant(true));
338+
339+ QCOMPARE(parentItem->height(), 0.0);
340+
341+ // flick all the way
342+ flickAndHold(dragHandle, totalDragDistance);
343+
344+ // should keep going until completion
345+ // Parent item should now have its full height
346+ tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
347+ QCOMPARE(parentItem->property("shown").toBool(), true);
348+
349+ dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
350+
351+ // flick all the way
352+ flickAndHold(dragHandle, totalDragDistance);
353+
354+ // should keep going until completion
355+ // Parent item should now have its full height
356+ tryCompare([&](){ return parentItem->height(); }, 0.0);
357+ QCOMPARE(parentItem->property("shown").toBool(), false);
358+}
359+
360+/*
361+ Set DragHandle.hintDisplacement to a value bigger than zero.
362+ Then lay a finger on the DragHandle.
363+ The expected behavior is that it will move or strech its parent Showable
364+ by hintDisplacement pixels.
365+ */
366+void tst_DragHandle::hintingAnimation()
367+{
368+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
369+ QQuickItem *parentItem = dragHandle->parentItem();
370+ qreal hintDisplacement = 100.0;
371+
372+ // enable hinting animations and stretch mode
373+ m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
374+ m_view->rootObject()->setProperty("stretch", QVariant(true));
375+
376+ QCOMPARE(parentItem->height(), 0.0);
377+
378+ QPointF initialTouchPos = dragHandle->mapToScene(
379+ QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
380+ QPointF touchPoint = initialTouchPos;
381+
382+ // Pressing causes the Showable to be stretched by hintDisplacement pixels
383+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
384+ tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
385+
386+ // Releasing causes the Showable to shrink back to 0 pixels.
387+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
388+ tryCompare([&](){ return parentItem->height(); }, 0.0);
389+
390+ QCOMPARE(parentItem->property("shown").toBool(), false);
391+}
392+
293QTEST_MAIN(tst_DragHandle)
393QTEST_MAIN(tst_DragHandle)
294
394
295#include "tst_DragHandle.moc"
395#include "tst_DragHandle.moc"
tests/qmltests/Components/tst_DragHandle.qml+32
@@ -34,12 +34,19 @@ Item {
34
34
35 property var dragHandle
35 property var dragHandle
36
36
37+ property bool stretch: false
38+ property real hintDisplacement: 0
39+
37 VerticalShowable {
40 VerticalShowable {
38 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
41 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
42+ stretch: root.stretch
43+ hintDisplacement: root.hintDisplacement
39 }
44 }
40
45
41 HorizontalShowable {
46 HorizontalShowable {
42 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
47 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
48+ stretch: root.stretch
49+ hintDisplacement: root.hintDisplacement
43 }
50 }
44
51
45 // Visually mark drag threshold
52 // Visually mark drag threshold
@@ -111,4 +118,29 @@ Item {
111 }
118 }
112 }
119 }
113 }
120 }
121+
122+ TestButton {
123+ id: stretchButton
124+ anchors.bottom: parent.bottom
125+ anchors.left: parent.left
126+ anchors.margins: units.gu(1)
127+
128+ text: root.stretch ? "stretch" : "move"
129+ onClicked: { root.stretch = !root.stretch; }
130+ }
131+
132+ TestButton {
133+ anchors.bottom: parent.bottom
134+ anchors.left: stretchButton.right
135+ anchors.margins: units.gu(1)
136+
137+ text: root.hintDisplacement > 0 ? "hint" : "no hint"
138+ onClicked: {
139+ if (root.hintDisplacement > 0) {
140+ root.hintDisplacement = 0;
141+ } else {
142+ root.hintDisplacement = units.gu(6);
143+ }
144+ }
145+ }
114}
146}
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+28−5
@@ -21,18 +21,35 @@ import Ubuntu.Gestures 0.1
21
21
22Showable {
22Showable {
23 id: showable
23 id: showable
24− x: -width
24+ x: stretch ? 0 : -width
25− width: parent.width
25+ width: stretch ? 0 : parent.width
26 height: parent.height
26 height: parent.height
27
27
28+ property bool stretch
29+ property real hintDisplacement
30+
31+ onStretchChanged: {
32+ if (stretch) {
33+ x = 0;
34+ width = 0;
35+ } else {
36+ x = -parent.width;
37+ width = parent.width;
38+ }
39+ }
40+
28 shown: false
41 shown: false
29
42
30 signal dragHandleRecognizedGesture(var dragHandle)
43 signal dragHandleRecognizedGesture(var dragHandle)
31
44
45+ property string animatedProp: stretch ? "width" : "x"
46+ property real propValueWhenShown: stretch ? parent.width : 0
47+ property real propValueWhenHidden: stretch ? 0 : -width
48+
32− showAnimation: StandardAnimation { property: "x"; to: 0 }
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
33− hideAnimation: StandardAnimation { property: "x"; to: -width }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
34
51
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
36
53
37 DragHandle {
54 DragHandle {
38 objectName: "leftwardsDragHandle"
55 objectName: "leftwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
44 width: units.gu(2)
61 width: units.gu(2)
45
62
46 direction: Direction.Leftwards
63 direction: Direction.Leftwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.width
66+ hintDisplacement: showable.hintDisplacement
47
67
48 onStatusChanged: {
68 onStatusChanged: {
49 if (status === DirectionalDragArea.Recognized) {
69 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
64 width: units.gu(2)
84 width: units.gu(2)
65
85
66 direction: Direction.Rightwards
86 direction: Direction.Rightwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.width
89+ hintDisplacement: showable.hintDisplacement
67
90
68 onStatusChanged: {
91 onStatusChanged: {
69 if (status === DirectionalDragArea.Recognized) {
92 if (status === DirectionalDragArea.Recognized) {
tests/qmltests/Components/tst_DragHandle/TestButton.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+Rectangle {
21+ id: rect
22+ width: childrenRect.width
23+ height: childrenRect.height
24+
25+ property alias text: textComponent.text
26+
27+ signal clicked
28+
29+ color: "yellow"
30+ Text {
31+ id: textComponent
32+ font.pixelSize: units.gu(2)
33+ text: root.stretch ? "stretch" : "move"
34+ MouseArea {
35+ anchors.fill: parent
36+ onClicked: {
37+ rect.clicked()
38+ }
39+ }
40+ }
41+}
tests/qmltests/Components/tst_DragHandle/UnityLogo.pngnew file

Binary file not shown.

tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+28−5
@@ -21,18 +21,35 @@ import Ubuntu.Gestures 0.1
21
21
22Showable {
22Showable {
23 id: showable
23 id: showable
24− y: -height
24+ y: stretch ? 0 : -height
25 width: parent.width
25 width: parent.width
26− height: parent.height
26+ height: stretch ? 0 : parent.height
27+
28+ property bool stretch
29+ property real hintDisplacement
30+
31+ onStretchChanged: {
32+ if (stretch) {
33+ y = 0;
34+ height = 0;
35+ } else {
36+ y = -parent.height;
37+ height = parent.height;
38+ }
39+ }
27
40
28 shown: false
41 shown: false
29
42
30 signal dragHandleRecognizedGesture(var dragHandle)
43 signal dragHandleRecognizedGesture(var dragHandle)
31
44
45+ property string animatedProp: stretch ? "height" : "y"
46+ property real propValueWhenShown: stretch ? parent.height : 0
47+ property real propValueWhenHidden: stretch ? 0 : -height
48+
32− showAnimation: StandardAnimation { property: "y"; to: 0 }
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
33− hideAnimation: StandardAnimation { property: "y"; to: -height }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
34
51
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
36
53
37 DragHandle {
54 DragHandle {
38 objectName: "upwardsDragHandle"
55 objectName: "upwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
44 height: units.gu(2)
61 height: units.gu(2)
45
62
46 direction: Direction.Upwards
63 direction: Direction.Upwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.height
66+ hintDisplacement: showable.hintDisplacement
47
67
48 onStatusChanged: {
68 onStatusChanged: {
49 if (status === DirectionalDragArea.Recognized) {
69 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
64 height: units.gu(2)
84 height: units.gu(2)
65
85
66 direction: Direction.Downwards
86 direction: Direction.Downwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.height
89+ hintDisplacement: showable.hintDisplacement
67
90
68 onStatusChanged: {
91 onStatusChanged: {
69 if (status === DirectionalDragArea.Recognized) {
92 if (status === DirectionalDragArea.Recognized) {
tests/qmltests/Components/tst_ListViewWithPageHeader.qmldeleted−351
@@ -1,351 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import ".."
20−import "../../../Components"
21−import Ubuntu.Components.ListItems 0.1 as ListItem
22−import Ubuntu.Components 0.1
23−import Unity.Test 0.1 as UT
24−
25−Item {
26− id: root
27− width: units.gu(40)
28− height: units.gu(80)
29−
30− ListModel {
31− id: animalsModel
32− ListElement { name: "Parrot"; size: "Small" }
33− ListElement { name: "Guinea pig"; size: "Small" }
34− ListElement { name: "Mouse"; size: "Small" }
35− ListElement { name: "Sparrow"; size: "Small" }
36− ListElement { name: "Dog"; size: "Medium" }
37− ListElement { name: "Cat"; size: "Medium" }
38− ListElement { name: "Dolphin"; size: "Medium" }
39− ListElement { name: "Seal"; size: "Medium" }
40− ListElement { name: "Elephant"; size: "Large" }
41− ListElement { name: "Blue whale"; size: "Large" }
42− ListElement { name: "Rhino"; size: "Large" }
43− ListElement { name: "Ostrich"; size: "Large" }
44− ListElement { name: "Sperm whale"; size: "Large" }
45− ListElement { name: "Giraffe"; size: "Large" }
46− ListElement { name: "Parrot"; size: "Small" }
47− ListElement { name: "Guinea pig"; size: "Small" }
48− ListElement { name: "Mouse"; size: "Small" }
49− ListElement { name: "Sparrow"; size: "Small" }
50− ListElement { name: "Dog"; size: "Medium" }
51− ListElement { name: "Cat"; size: "Medium" }
52− ListElement { name: "Dolphin"; size: "Medium" }
53− ListElement { name: "Seal"; size: "Medium" }
54− ListElement { name: "Elephant"; size: "Large" }
55− ListElement { name: "Blue whale"; size: "Large" }
56− ListElement { name: "Rhino"; size: "Large" }
57− ListElement { name: "Ostrich"; size: "Large" }
58− ListElement { name: "Sperm whale"; size: "Large" }
59− ListElement { name: "Giraffe"; size: "Large" }
60− ListElement { name: "Parrot"; size: "Small" }
61− ListElement { name: "Guinea pig"; size: "Small" }
62− ListElement { name: "Mouse"; size: "Small" }
63− ListElement { name: "Sparrow"; size: "Small" }
64− ListElement { name: "Dog"; size: "Medium" }
65− ListElement { name: "Cat"; size: "Medium" }
66− ListElement { name: "Dolphin"; size: "Medium" }
67− ListElement { name: "Seal"; size: "Medium" }
68− ListElement { name: "Elephant"; size: "Large" }
69− ListElement { name: "Blue whale"; size: "Large" }
70− ListElement { name: "Rhino"; size: "Large" }
71− ListElement { name: "Ostrich"; size: "Large" }
72− ListElement { name: "Sperm whale"; size: "Large" }
73− ListElement { name: "Giraffe"; size: "Large" }
74− ListElement { name: "Parrot"; size: "Small" }
75− ListElement { name: "Guinea pig"; size: "Small" }
76− ListElement { name: "Mouse"; size: "Small" }
77− ListElement { name: "Sparrow"; size: "Small" }
78− ListElement { name: "Dog"; size: "Medium" }
79− ListElement { name: "Cat"; size: "Medium" }
80− ListElement { name: "Dolphin"; size: "Medium" }
81− ListElement { name: "Seal"; size: "Medium" }
82− ListElement { name: "Elephant"; size: "Large" }
83− ListElement { name: "Blue whale"; size: "Large" }
84− ListElement { name: "Rhino"; size: "Large" }
85− ListElement { name: "Ostrich"; size: "Large" }
86− ListElement { name: "Sperm whale"; size: "Large" }
87− ListElement { name: "Giraffe"; size: "Large" }
88− }
89−
90− ListViewWithPageHeader {
91− id: listView
92− anchors.fill: parent
93− model: animalsModel
94−
95− delegate: Item {
96− height: units.gu(6)
97− anchors {
98− left: parent.left
99− right: parent.right
100− }
101−
102− Label {
103− id: label
104− anchors.fill: parent
105− text: name
106− verticalAlignment: Text.AlignVCenter
107− }
108− }
109−
110− sectionProperty: "size"
111− sectionDelegate: Rectangle {
112− anchors {
113− left: (parent) ? parent.left : undefined
114− right: (parent) ? parent.right : undefined
115− }
116− height: units.gu(5)
117− color: "lightsteelblue"
118−
119− Label {
120− id: label
121− anchors.fill: parent
122− text: section
123− verticalAlignment: Text.AlignVCenter
124− }
125− }
126−
127− pageHeader: PageHeader {
128− id: pageHeader
129− anchors {
130− left: parent.left
131− right: parent.right
132− }
133− text: "Animals"
134− }
135− }
136−
137−
138−
139−
140− UT.UnityTestCase {
141− name: "ListViewWithPageHeader"
142− when: windowShown
143−
144− readonly property real xPos: listView.width / 2
145− readonly property real headerHeight: pageHeader.height
146−
147− /**************************** Helper functions ****************************/
148− function listViewFirstSectionHeaderYPosition() {
149− return Math.round(listView.pageHeader.mapToItem(listView).y); //round as using floats
150− }
151−
152− function firstSectionHeaderYPosition() {
153− return Math.round(listView.view.children[0].mapToItem(listView).y);
154− }
155−
156− function cleanup() {
157− listView.positionAtBeginning();
158− // wait for list position to reset
159− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
160− tryCompare(listView.view.contentY, 0);
161− }
162−
163− // these functions are hand-crafted to move the Flickable down/up one pixel
164− function swipeDown1Pixel(item) {
165− mousePress(item, xPos, 15);
166− mouseMove(item, xPos, 14, 100);
167− mouseMove(item, xPos, 10, 100);
168− mouseMove(item, xPos, 6, 100);
169− mouseMove(item, xPos, 3, 100);
170− mouseMove(item, xPos, 1, 100);
171− mouseMove(item, xPos, 0, 100);
172− mouseRelease(item, xPos, 0);
173− }
174−
175− function swipeUp1Pixel(item) {
176− mousePress(item, xPos, 0);
177− mouseMove(item, xPos, 1, 100);
178− mouseMove(item, xPos, 5, 100);
179− mouseMove(item, xPos, 9, 100);
180− mouseMove(item, xPos, 12, 100);
181− mouseMove(item, xPos, 14, 100);
182− mouseMove(item, xPos, 15, 100);
183− mouseRelease(item, xPos, 15);
184− }
185−
186− /******************************* Test cases *******************************/
187−
188− /* Check the initial positions of components are correct */
189− function test_initialState() {
190− compare(listViewFirstSectionHeaderYPosition(), 0);
191− compare(listView.view.contentY, 0);
192−
193− // Check that the section delegate is positioned underneath the header
194− // First section delegate is the first child of the view
195− tryCompareFunction(firstSectionHeaderYPosition, headerHeight);
196− }
197−
198− /* Check the header moves up one pixel when the view is moved up by one pixel */
199− function test_headerPositionAfterDownMoveByOnePixel() {
200− swipeDown1Pixel(listView)
201−
202− tryCompareFunction(listViewFirstSectionHeaderYPosition, -1);
203− tryCompareFunction(firstSectionHeaderYPosition, headerHeight - 1);
204− tryCompare(listView.view.contentY, -1);
205− }
206−
207− /* Check the header position is y=0 when view moved up and then down by one pixel */
208− function test_headerPositionAfterDownAndThenUpMoveByOne() {
209− swipeDown1Pixel(listView)
210−
211− tryCompareFunction(listViewFirstSectionHeaderYPosition, -1);
212− tryCompare(listView.view.contentY, -1);
213−
214− // these operations move the Flickabe up one pixel
215− swipeUp1Pixel(listView);
216−
217− tryCompare(listView.view.contentY, 0);
218− // tryCompareFunction(listViewFirstSectionHeaderYPosition, 0) //FIXME - this fails due to bug in LVWPH
219− tryCompareFunction(firstSectionHeaderYPosition, headerHeight)
220− }
221−
222− /* Check after a big flick the header is moved off-screen, with the header bottom
223− placed just above the view */
224− function test_headerPositionAfterDownMove() {
225− // move the Flickabe up to hide header
226− listView.flick(0, -10000);
227−
228− // wait for flick to finish
229− tryCompare(listView.moving, false);
230−
231− tryCompareFunction(listViewFirstSectionHeaderYPosition, -headerHeight);
232− }
233−
234− /* Check when header off-screen, moving down the view by one pixel moves the header
235− down by one pixel */
236− function test_hiddenHeaderPositionAfterUpMoveByOnePixel() {
237− // move the Flickabe up to hide header
238− listView.flick(0, -10000);
239−
240− // wait for flick to fully hide header
241− tryCompareFunction(listViewFirstSectionHeaderYPosition, -headerHeight);
242−
243− swipeUp1Pixel(listView);
244−
245− tryCompareFunction( function() {
246− return Math.floor( listViewFirstSectionHeaderYPosition() ); // need to round to make test more robust
247− }, -headerHeight + 1);
248− }
249−
250− /* Check if up swipe causes list to bounces at the bottom, header stays hidden */
251− function test_upSwipeCausingBounceKeepsHeaderHidden() {
252− // move list to the bottom (will bounce)
253− listView.flick(0, -1000000);
254−
255− // wait for bounce to complete
256− tryCompare(listView.moving, false);
257− tryCompare(listView.view.atYEnd, true);
258−
259− tryCompareFunction(listViewFirstSectionHeaderYPosition, -headerHeight);
260− }
261−
262− /* Check if up swipe causes list to bounces at the bottom, header stays hidden */
263− function test_downSwipeCausingBounceKeepsHeaderVisible() {
264− // move list to the bottom (will bounce)
265− listView.flick(0, -1000000);
266−
267− // wait for bounce to complete
268− tryCompare(listView.moving, false);
269− tryCompare(listView.view.atYBeginning, true);
270−
271− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
272− }
273−
274− /* Check if list at top is pulled down further, that the list contents move but the
275− header remains at the top of the view */
276− function test_topDragOverBoundsKeepsHeaderVisible() {
277− // drag list up - but don't release yet
278− mouseFlick(listView,
279− xPos, // from_x
280− 0, // from_y
281− xPos, // to_x
282− 100, true, false, 0.2);
283−
284− // wait for gesture to occur
285− tryCompareFunction(function() { return listView.view.contentY < 20; }, true);
286− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
287−
288− mouseRelease(listView, xPos, 400);
289−
290− // ensure list moving to recover from over-bound drag keeps header unchanged
291− tryCompare(listView.view.contentY, 0);
292− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
293− }
294−
295− /* Check if list at top is pulled down further, that the list contents move but the
296− header remains at the top of the view */
297− function test_bottomDragOverBoundsKeepsHeaderHidden() {
298− // move list to the bottom (will bounce)
299− listView.flick(0, -1000000);
300−
301− // wait for bounce to complete
302− tryCompare(listView.moving, false);
303− tryCompare(listView.view.atYEnd, true);
304−
305− // drag list up but don't release
306− mouseFlick(listView,
307− xPos, // from_x
308− 0, // from_y
309− xPos, // to_x
310− -listView.height, true, false);
311−
312− tryCompareFunction(listViewFirstSectionHeaderYPosition, -headerHeight);
313−
314− mouseRelease(listView, xPos, -listView.height);
315− // wait for list to reset position
316− tryCompare(listView.moving, false);
317− tryCompare(listView.view.atYEnd, true);
318−
319− // ensure list moving to recover from over-bound drag keeps header unchanged
320− tryCompareFunction(listViewFirstSectionHeaderYPosition, -headerHeight);
321− }
322−
323− /* Check positionAtBeginning() works resets list and header position */
324− function test_positionAtBeginning() {
325− // move the Flickabe up to hide header
326− listView.flick(0, -10000);
327−
328− // wait for gesture to complete
329− tryCompare(listView.moving, false);
330−
331− listView.positionAtBeginning();
332− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
333− tryCompare(listView.view.contentY, 0);
334− }
335−
336− /* Check showHeader forces header to appear but list position remains unchanged */
337− function test_showHeader() {
338− // move the Flickabe up to hide header
339− listView.flick(0, -10000);
340−
341− // wait for gesture to complete
342− tryCompare(listView.moving, false);
343−
344− var listContentY = listView.view.contentY;
345−
346− listView.showHeader();
347− tryCompareFunction(listViewFirstSectionHeaderYPosition, 0);
348− tryCompare(listView.view.contentY, listContentY);
349− }
350− }
351−}
tests/qmltests/Greeter/tst_Phone.qml+40−1
@@ -28,7 +28,17 @@ Item {
28
28
29 Greeter {
29 Greeter {
30 id: greeter
30 id: greeter
31− anchors.fill: parent
31+ width: parent.width
32+ height: parent.height
33+ x: 0; y: 0
34+
35+ property int minX: 0
36+
37+ onXChanged: {
38+ if (x < minX) {
39+ minX = x;
40+ }
41+ }
32 }
42 }
33
43
34 SignalSpy {
44 SignalSpy {
@@ -63,5 +73,34 @@ Item {
63 tryCompare(greeter, "leftTeaserPressed", false)
73 tryCompare(greeter, "leftTeaserPressed", false)
64 tryCompare(greeter, "rightTeaserPressed", false)
74 tryCompare(greeter, "rightTeaserPressed", false)
65 }
75 }
76+
77+ function test_teaseLockedUnlocked_data() {
78+ return [
79+ {tag: "unlocked", locked: false},
80+ {tag: "locked", locked: true}
81+ ];
82+ }
83+
84+ function test_teaseLockedUnlocked(data) {
85+ tryCompare(greeter, "rightTeaserPressed", false);
86+ tryCompare(greeter, "x", 0);
87+ greeter.locked = data.locked;
88+
89+ mouseClick(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
90+ greeter.minX = 0; // This is needed because the transition actually makes x jump once before animating
91+
92+ if (!data.locked) {
93+ // Check if it has been moved over by 2 GUs. Give it a 2 pixel grace area
94+ // because animation duration and teaseTimer are the same duration and
95+ // might cause slight offsets
96+ tryCompareFunction(function() { return greeter.minX <= -units.gu(2) + 2}, true);
97+ } else {
98+ // waiting 100ms to make sure nothing moves
99+ wait(100);
100+ compare(greeter.minX, 0, "Greeter moved even tho its locked");
101+ }
102+ // Wait until we're back to 0
103+ tryCompareFunction(function() { return greeter.x;}, 0);
104+ }
66 }
105 }
67}
106}
tests/qmltests/Launcher/tst_Launcher.qml+40
@@ -43,6 +43,18 @@ Item {
43 onDashItemSelected: {
43 onDashItemSelected: {
44 dashItemSelected_count++;
44 dashItemSelected_count++;
45 }
45 }
46+
47+ property int maxPanelX: 0
48+ }
49+
50+ Connections {
51+ target: testCase.findChild(launcher, "launcherPanel")
52+
53+ onXChanged: {
54+ if (target.x > launcher.maxPanelX) {
55+ launcher.maxPanelX = target.x;
56+ }
57+ }
46 }
58 }
47
59
48 UT.UnityTestCase {
60 UT.UnityTestCase {
@@ -126,5 +138,33 @@ Item {
126 var panel = findChild(launcher, "launcherPanel")
138 var panel = findChild(launcher, "launcherPanel")
127 tryCompare(panel, "x", -panel.width, 1000)
139 tryCompare(panel, "x", -panel.width, 1000)
128 }
140 }
141+
142+
143+ function test_teaseLauncher_data() {
144+ return [
145+ {tag: "available", available: true},
146+ {tag: "not available", available: false}
147+ ];
148+ }
149+
150+ function test_teaseLauncher(data) {
151+ launcher.available = data.available;
152+ launcher.maxPanelX = -launcher.panelWidth;
153+ launcher.tease();
154+
155+ if (data.available) {
156+ // Check if the launcher slides in for units.gu(2). However, as the animation is 200ms
157+ // and the teaseTimer's timeout too, give it a 2 pixels grace distance
158+ tryCompareFunction(
159+ function(){
160+ return launcher.maxPanelX >= -launcher.panelWidth + units.gu(2) - 2;
161+ },
162+ true)
163+ } else {
164+ wait(100)
165+ compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
166+ }
167+ waitUntilLauncherDisappears();
168+ }
129 }
169 }
130}
170}