on xenial help sign in
merged with trunk
commit826156513fc6a1d8c21600f58700403017f48b43 authorNick Dedekind <nicholas.dedekind@gmail.com> dateTue 2 Jul 2013, 09:16 (13 years ago) parentb1b5ac07e20e47
48 files changed+5240−693
.bazaar/plugins/makecheck_unity_phablet.py+15−6 .bzrignore+1 Bottombar/Bottombar.qml+1−8 Components/Carousel.qml+2 Components/DragHandle.qml+124−64 Components/EdgeDragArea.qml+36 Components/ListItems/Base.qml+19−21 Components/ListItems/Header.qml−6 Components/ListItems/ThinDivider.qml−4 Components/ListViewWithPageHeader.qml−189 Components/Stage.qml+1−5 Dash/DashApps.qml+2−3 Dash/DashHome.qml+1−2 Dash/DashMusic.qml+1−2 Dash/DashVideos.qml+2−3 Dash/GenericScopeView.qml+1−1 Dash/ScopeListView.qml+23 Greeter/Greeter.qml+2−2 Launcher/Launcher.qml+4−12 Shell.qml+6−1 debian/changelog+6 debian/control+2 debian/unity8.install+1 plugins/CMakeLists.txt+1 plugins/ListViewWithPageHeader/CMakeLists.txt+44 plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+1059 plugins/ListViewWithPageHeader/listviewwithpageheader.h+180 plugins/ListViewWithPageHeader/plugin.cpp+29 plugins/ListViewWithPageHeader/plugin.h+33 plugins/ListViewWithPageHeader/qmldir+2 po/is.po+220 run_on_device+1−1 tests/plugins/CMakeLists.txt+1 tests/plugins/ListViewWithPageHeader/CMakeLists.txt+31 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+1311 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp+1567 tests/plugins/ListViewWithPageHeader/test.qml+96 tests/plugins/ListViewWithPageHeader/test_section.qml+106 tests/qmltests/CMakeLists.txt−1 tests/qmltests/Components/tst_DragHandle.cpp+100 tests/qmltests/Components/tst_DragHandle.qml+32 tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+28−5 tests/qmltests/Components/tst_DragHandle/TestButton.qml+41 tests/qmltests/Components/tst_DragHandle/UnityLogo.png tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+28−5 tests/qmltests/Components/tst_ListViewWithPageHeader.qml−351 tests/qmltests/Greeter/tst_Phone.qml+40−1 tests/qmltests/Launcher/tst_Launcher.qml+40
.bazaar/plugins/makecheck_unity_phablet.py+15−6
@@ -23,12 +23,21 @@ def execute_makecheck(local_branch, master_branch, old_revision_number, old_revi
2323
2424 os.chdir(local_path_from_url(master_branch.base))
2525
26− print "Executing 'make check'.."
27− if (subprocess.call("make check", shell=True) != 0):
28− raise errors.BzrError("Unit tests failed, fix them before committing!")
26+ print "Executing 'make -C builddir test'.."
27+ os.environ['CTEST_OUTPUT_ON_FAILURE'] = "1"
28+ if (subprocess.call("make -C builddir test", shell=True) != 0):
2929
30− print "Executing 'make qmluitests'.."
31− if (subprocess.call("make qmluitests", shell=True) != 0):
32− raise errors.BzrError("QML UI tests failed, fix them before committing!")
30+ print("\n\n*** Warning ***\n\nBasic tests failed. This commit will not pass continuous integration.")
31+
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")
3342
3443branch.Branch.hooks.install_named_hook('pre_commit', execute_makecheck, 'make check pre-commit')
.bzrignore+1
@@ -7,3 +7,4 @@
77/.cproject
88/.settings
99/.pydevproject
10+message.txt
Bottombar/Bottombar.qml+1−8
@@ -64,7 +64,7 @@ Item {
6464 onShownChanged: bottomBarVisibilityCommunicatorShell.forceHidden = theHud.shown
6565 }
6666
67− DirectionalDragArea {
67+ EdgeDragArea {
6868 id: dragArea
6969 width: parent.width
7070 height: distanceThreshold
@@ -73,13 +73,6 @@ Item {
7373 enabled: !theHud.shown && bottombar.enabled && applicationIsOnForeground
7474 direction: Direction.Upwards
7575
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−
8376 property int previousStatus: -1
8477 property real touchStartX: -1
8578
Components/Carousel.qml+2
@@ -24,6 +24,8 @@ import "carousel.js" as CarouselJS
2424Item {
2525 id: carousel
2626
27+ clip: true // Don't leak horizontally to other dashes
28+
2729 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
2830 property Component itemComponent
2931 /// Model for the Carousel, which has to be a model usable by a ListView
Components/DragHandle.qml+124−64
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Ubuntu.Gestures 0.1
20+import "Math.js" as MathLocal
2021
2122/*
2223 Put a DragHandle inside a Showable to enable the user to drag it from that handle.
@@ -43,28 +44,124 @@ import Ubuntu.Gestures 0.1
4344 }
4445
4546 */
46−DirectionalDragArea {
47+EdgeDragArea {
4748 id: dragArea
49+ objectName: "dragHandle"
4850
49− // Once we are satisfied with those values, make them the default
50− // and remove them from here for the sake of consistency
51− maxDeviation: units.gu(1)
52− wideningAngle: 20
53− distanceThreshold: units.gu(3)
54− minSpeed: units.gu(5)
51+ property bool stretch: false
52+
53+ property alias autoCompleteDragThreshold: dragEvaluator.dragThreshold
5554
5655 // How far you can drag
57− property real maxTotalDragDistance: Direction.isHorizontal(direction) ? parent.width : parent.height
56+ property real maxTotalDragDistance: {
57+ if (stretch) {
58+ 0; // not enough context information to set a sensible default
59+ } else {
60+ Direction.isHorizontal(direction) ? parent.width : parent.height;
61+ }
62+ }
63+
64+ property real hintDisplacement: 0
65+ SmoothedAnimation {
66+ id: hintingAnimation
67+ target: hintingAnimation
68+ property: "targetValue"
69+ duration: 150
70+ velocity: -1
71+ to: Direction.isPositive(direction) ? d.startValue + hintDisplacement
72+ : d.startValue - hintDisplacement
73+ property real targetValue
74+ onTargetValueChanged: {
75+ if (!running) {
76+ return;
77+ }
78+
79+ if (Direction.isPositive(direction)) {
80+ if (parent[d.targetProp] < targetValue) {
81+ parent[d.targetProp] = targetValue;
82+ }
83+ } else {
84+ if (parent[d.targetProp] > targetValue) {
85+ parent[d.targetProp] = targetValue;
86+ }
87+ }
88+ }
89+ }
5890
59− property var __previousStatus
60− property real __totalDragDistance: 0
91+ // Private stuff
92+ QtObject {
93+ id: d
94+ property var previousStatus: undefined
95+ property real startValue
96+ property real minValue: Direction.isPositive(direction) ? startValue
97+ : startValue - maxTotalDragDistance
98+ property real maxValue: Direction.isPositive(direction) ? startValue + maxTotalDragDistance
99+ : startValue
100+
101+ property var dragParent: dragArea.parent
102+
103+ // The property of DragHandle's parent that will be modified
104+ property string targetProp: {
105+ if (stretch) {
106+ Direction.isHorizontal(direction) ? "width" : "height";
107+ } else {
108+ Direction.isHorizontal(direction) ? "x" : "y";
109+ }
110+ }
111+
112+ function limitMovement(step) {
113+ var targetValue = MathLocal.clamp(dragParent[targetProp] + step, minValue, maxValue);
114+ var step = targetValue - dragParent[targetProp];
115+
116+ if (hintDisplacement == 0) {
117+ return step;
118+ }
119+
120+ // we should not go behind hintingAnimation's current value
121+ if (Direction.isPositive(direction)) {
122+ if (dragParent[targetProp] + step < hintingAnimation.targetValue) {
123+ step = hintingAnimation.targetValue - dragParent[targetProp];
124+ }
125+ } else {
126+ if (dragParent[targetProp] + step > hintingAnimation.targetValue) {
127+ step = hintingAnimation.targetValue - dragParent[targetProp];
128+ }
129+ }
130+
131+ return step;
132+ }
133+
134+ function onFinishedRecognizedGesture() {
135+ if (dragEvaluator.shouldAutoComplete()) {
136+ completeDrag();
137+ } else {
138+ rollbackDrag();
139+ }
140+ }
141+
142+ function completeDrag() {
143+ if (dragParent.shown) {
144+ dragParent.hide();
145+ } else {
146+ dragParent.show();
147+ }
148+ }
149+
150+ function rollbackDrag() {
151+ if (dragParent.shown) {
152+ dragParent.show();
153+ } else {
154+ dragParent.hide();
155+ }
156+ }
157+ }
61158
62159 property alias edgeDragEvaluator: dragEvaluator
63160
64161 EdgeDragEvaluator {
65162 objectName: "edgeDragEvaluator"
66163 id: dragEvaluator
67− trackedPosition: Direction.isHorizontal(direction) ? parent.x + touchX : parent.y + touchY
164+ trackedPosition: sceneDistance
68165 maxDragDistance: maxTotalDragDistance
69166 direction: dragArea.direction
70167 }
@@ -74,75 +171,38 @@ DirectionalDragArea {
74171 // don't go the whole distance in order to smooth out the movement
75172 var step = distance * 0.3;
76173
77− step = __limitMovement(step);
78−
79− __totalDragDistance += step;
174+ step = d.limitMovement(step);
80175
81− if (Direction.isHorizontal(direction)) {
82− parent.x += step;
83− } else {
84− parent.y += step;
85− }
176+ parent[d.targetProp] += step;
86177 }
87178 }
88179
89−
90− function __limitMovement(step) {
91− if (Direction.isPositive(direction)) {
92− if (__totalDragDistance + step > maxTotalDragDistance) {
93− step = maxTotalDragDistance - __totalDragDistance;
94− } else if (__totalDragDistance + step < 0) {
95− step = 0 - __totalDragDistance;
180+ onDraggingChanged: {
181+ if (dragging) {
182+ if (hintDisplacement > 0) {
183+ hintingAnimation.targetValue = d.startValue;
184+ hintingAnimation.start();
96185 }
97186 } else {
98− if (__totalDragDistance + step < -maxTotalDragDistance) {
99− step = -maxTotalDragDistance - __totalDragDistance;
100− } else if (__totalDragDistance + step > 0) {
101− step = 0 - __totalDragDistance;
102− }
187+ hintingAnimation.stop();
103188 }
104−
105− return step;
106189 }
107190
108191 onStatusChanged: {
109192 if (status === DirectionalDragArea.WaitingForTouch) {
110− if (__previousStatus === DirectionalDragArea.Recognized) {
111− __onFinishedRecognizedGesture();
193+ if (d.previousStatus === DirectionalDragArea.Recognized) {
194+ d.onFinishedRecognizedGesture();
112195 }
113− __totalDragDistance = 0;
196+ d.startValue = parent[d.targetProp];
114197 }
115− else if (status === DirectionalDragArea.Undecided) {
198+
199+ if (d.previousStatus === DirectionalDragArea.WaitingForTouch ||
200+ d.previousStatus === undefined) {
116201 dragEvaluator.reset();
202+ d.startValue = parent[d.targetProp];
117203 }
118− else if (status === DirectionalDragArea.Recognized) {
119− if (__previousStatus === DirectionalDragArea.WaitingForTouch)
120− dragEvaluator.reset();
121− }
122− __previousStatus = status;
123− }
124204
125− function __onFinishedRecognizedGesture() {
126− if (dragEvaluator.shouldAutoComplete()) {
127− __completeDrag();
128− } else {
129− __rollbackDrag();
130− }
205+ d.previousStatus = status;
131206 }
132207
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− }
148208}
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/ListItems/Base.qml+19−21
@@ -54,35 +54,33 @@ AbstractButton {
5454 */
5555 function __showDivider() {
5656 // if we're not in ListView, always show a thin dividing line at the bottom
57− if (ListView.view !== null) {
58−
59− // if we're last item in ListView don't show divider
60− if (index === ListView.view.model.count - 1) return false;
57+ var model = null;
58+ if (typeof ListViewWithPageHeader !== 'undefined') {
59+ if (typeof ListViewWithPageHeader.model !== 'undefined') {
60+ model = ListViewWithPageHeader.model;
61+ }
62+ } else if (ListView.view !== null) {
63+ model = ListView.view.model;
6164 }
65+ // if we're last item in ListView don't show divider
66+ if (model && index === model.count - 1) return false;
67+
6268 return true;
6369 }
6470
65− property bool __clippingRequired: ListView.view !== null
66− && ListView.view.section.labelPositioning & ViewSection.CurrentLabelAtStart
67−
68− property real __yPositionRelativeToListView: ListView.view ? y - ListView.view.contentY : y
71+ /* Relevant really only for ListViewWithPageHeader case: specify how many pixels we can overlap with the section header */
72+ readonly property int allowedOverlap: units.dp(1)
6973
7074 property real __heightToClip: {
7175 // Check this is in position where clipping is needed
72− if (__clippingRequired && __yPositionRelativeToListView <= __sectionDelegateHeight
73− && __yPositionRelativeToListView > -height) {
74− return Math.min(__sectionDelegateHeight - __yPositionRelativeToListView, height);
75− } else {
76− return 0;
77− }
78− }
79−
80− property int __sectionDelegateHeight: {
81− if (__clippingRequired && ListView.view.hasOwnProperty("__sectionDelegateHeight")) {
82− return ListView.view.__sectionDelegateHeight;
83− } else {
84− return 0;
76+ if (typeof ListViewWithPageHeader !== 'undefined') {
77+ if (typeof heightToClip !== 'undefined') {
78+ if (heightToClip >= allowedOverlap) {
79+ return heightToClip - allowedOverlap;
80+ }
81+ }
8582 }
83+ return 0;
8684 }
8785
8886 /*!
Components/ListItems/Header.qml−6
@@ -25,12 +25,6 @@ Item {
2525 */
2626 property alias text: label.text
2727
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−
3428 height: units.gu(4.5)
3529
3630 Item {
Components/ListItems/ThinDivider.qml−4
@@ -23,9 +23,5 @@ Image {
2323 }
2424 height: (visible) ? units.dp(2) : 0
2525
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−
3026 source: "graphics/ListItemDividerHorizontal.png"
3127}
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/Stage.qml+1−5
@@ -423,7 +423,7 @@ Showable {
423423 maxDragDistance: stage.width
424424 }
425425
426− DirectionalDragArea {
426+ EdgeDragArea {
427427 id: rightEdgeDraggingArea
428428
429429 enabled: {
@@ -441,10 +441,6 @@ Showable {
441441 anchors.right: parent.right
442442
443443 direction: Direction.Leftwards
444− maxDeviation: units.gu(1)
445− wideningAngle: 20
446− distanceThreshold: units.gu(3)
447− minSpeed: units.gu(5)
448444
449445 onDistanceChanged: {
450446 if (status !== DirectionalDragArea.Recognized)
Dash/DashApps.qml+2−3
@@ -99,7 +99,7 @@ ScopeView {
9999 When using Loader to load external QML file in the list deelgate, the ListView has
100100 a bug where it can position the delegate content to overlap the section header
101101 of the ListView - a workaround is to use sourceComponent of Loader instead */
102− Component { id: runningApplicationsGrid; RunningApplicationsGrid {} }
102+ Component { id: runningApplicationsGrid; RunningApplicationsGrid { clip: true } }
103103 Component {
104104 id: applicationsFilterGrid;
105105 ApplicationsFilterGrid {
@@ -112,7 +112,7 @@ ScopeView {
112112 "ApplicationsFilterGrid": applicationsFilterGrid,
113113 }
114114
115− ListViewWithPageHeader {
115+ ScopeListView {
116116 id: categoryView
117117 anchors.fill: parent
118118 model: SortFilterProxyModel {
@@ -150,7 +150,6 @@ ScopeView {
150150 item.model = categoryModels[modelName]
151151 }
152152 }
153− asynchronous: true
154153 }
155154
156155 ListView.onRemove: SequentialAnimation {
Dash/DashHome.qml+1−2
@@ -130,7 +130,7 @@ ScopeView {
130130 "VideosGrid": videosGrid,
131131 }
132132
133− ListViewWithPageHeader {
133+ ScopeListView {
134134 id: listView
135135 anchors.fill: parent
136136 model: categoryListModel
@@ -161,7 +161,6 @@ ScopeView {
161161 item.clicked.connect(playVideo);
162162 }
163163 }
164− asynchronous: true
165164 }
166165 }
167166
Dash/DashMusic.qml+1−2
@@ -64,7 +64,7 @@ ScopeView {
6464 }
6565 }
6666
67− ListViewWithPageHeader {
67+ ScopeListView {
6868 id: categoryView
6969 anchors.fill: parent
7070 model: scopeView.categories
@@ -84,7 +84,6 @@ ScopeView {
8484 onLoaded: {
8585 item.model = results
8686 }
87− asynchronous: true
8887 }
8988 }
9089
Dash/DashVideos.qml+2−3
@@ -97,11 +97,11 @@ ScopeView {
9797 }
9898 }
9999
100− ListViewWithPageHeader {
100+ ScopeListView {
101101 id: categoryView
102102 anchors.fill: parent
103103 model: scopeView.categories
104− clipListView: !previewLoader.onScreen
104+ forceNoClip: previewLoader.onScreen
105105
106106 onAtYEndChanged: if (atYEnd) endReached()
107107 onMovingChanged: if (moving && atYEnd) endReached()
@@ -119,7 +119,6 @@ ScopeView {
119119 onLoaded: {
120120 item.model = results
121121 }
122− asynchronous: true
123122
124123 Connections {
125124 target: loader.item
Dash/GenericScopeView.qml+1−1
@@ -43,7 +43,7 @@ ScopeView {
4343 }
4444 }
4545
46− ListViewWithPageHeader {
46+ ScopeListView {
4747 id: categoryView
4848 anchors.fill: parent
4949 model: scopeView.categories
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 {
4848 signal unlocked(int uid)
4949
5050 onRightTeaserPressedChanged: {
51− if (rightTeaserPressed) {
51+ if (rightTeaserPressed && !locked) {
5252 teasingTimer.start();
5353 }
5454 }
@@ -81,7 +81,7 @@ Showable {
8181 states: [
8282 State {
8383 name: "teasing"
84− when: greeter.rightTeaserPressed || teasingTimer.running
84+ when: teasingTimer.running
8585 PropertyChanges {
8686 target: greeter
8787 x: -units.gu(2)
Launcher/Launcher.qml+4−12
@@ -25,7 +25,6 @@ Item {
2525
2626 property bool available: true // can be used to disable all interactions
2727
28− property bool teasing: false
2928 property int panelWidth: units.gu(8.5)
3029 property int dragAreaWidth: units.gu(1)
3130 property real progress: dragArea.dragging && dragArea.touchX > panel.width ? dragArea.touchX : 0
@@ -58,8 +57,8 @@ Item {
5857 animateTimer.start();
5958 }
6059
61− onTeasingChanged: {
62− if (teasing) {
60+ function tease() {
61+ if (available) {
6362 teaseTimer.start();
6463 }
6564 }
@@ -181,18 +180,11 @@ Item {
181180 }
182181 }
183182
184− DirectionalDragArea {
183+ EdgeDragArea {
185184 id: dragArea
186185
187186 direction: Direction.Rightwards
188187
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−
196188 enabled: root.available && root.state !== "visible"
197189 width: root.dragAreaWidth
198190 height: root.height
@@ -244,7 +236,7 @@ Item {
244236 },
245237 State {
246238 name: "teasing"
247− when: root.teasing || teaseTimer.running
239+ when: teaseTimer.running
248240 PropertyChanges {
249241 target: panel
250242 x: -root.panelWidth + units.gu(2)
Shell.qml+6−1
@@ -439,6 +439,12 @@ FocusScope {
439439 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
440440 shell.background = bgPath ? bgPath : default_background
441441 }
442+
443+ onLeftTeaserPressedChanged: {
444+ if (leftTeaserPressed) {
445+ launcher.tease();
446+ }
447+ }
442448 }
443449
444450 InputFilterArea {
@@ -532,7 +538,6 @@ FocusScope {
532538 width: parent.width
533539 dragAreaWidth: shell.edgeSize
534540 available: !greeter.locked
535− teasing: available && greeter.leftTeaserPressed
536541 onDashItemSelected: {
537542 greeter.hide()
538543 // Animate if moving between application and dash
debian/changelog+6
@@ -1,3 +1,9 @@
1+unity8 (7.81.3) UNRELEASED; urgency=low
2+
3+ * Choose more appropriate values for edge-drag gestures (LP: #1194150)
4+
5+ -- Daniel d'Andrada <daniel.dandrada@canonical.com> Fri, 28 Jun 2013 16:28:24 -0300
6+
17unity8 (7.81.2) saucy; urgency=low
28
39 * Translation updates.
debian/control+2
@@ -14,6 +14,7 @@ Build-Depends: cmake,
1414 libnm-util-dev,
1515 libpulse-dev,
1616 libqmenumodel-dev,
17+ libqt5v8-5-private-dev,
1718 libunity-core-6.0-dev (<< 7.80),
1819 libxcb1-dev,
1920 pkg-config,
@@ -23,6 +24,7 @@ Build-Depends: cmake,
2324 qtbase5-private-dev,
2425 qtdeclarative5-dev,
2526 qtdeclarative5-dev-tools,
27+ qtdeclarative5-private-dev,
2628 qtdeclarative5-qtquick2-plugin,
2729 qtdeclarative5-test-plugin,
2830 qtdeclarative5-ubuntu-ui-toolkit-plugin,
debian/unity8.install+1
@@ -16,6 +16,7 @@
1616/usr/share/unity8/modules/Unity/Indicators/*
1717/usr/share/unity8/plugins/HudClient/*
1818/usr/share/unity8/plugins/LightDM/*
19+/usr/share/unity8/plugins/ListViewWithPageHeader/*
1920/usr/share/unity8/plugins/Ubuntu/*
2021/usr/share/unity8/plugins/Unity/*
2122/usr/share/unity8/plugins/Utils/*
plugins/CMakeLists.txt+1
@@ -3,3 +3,4 @@ add_subdirectory(Utils)
33add_subdirectory(Unity)
44add_subdirectory(HudClient)
55add_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
po/is.ponew file+220
@@ -0,0 +1,220 @@
1+# Icelandic 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-07-01 02:39+0000\n"
12+"Last-Translator: Andri Thorlacius <Unknown>\n"
13+"Language-Team: Icelandic <is@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-07-02 07:01+0000\n"
18+"X-Generator: Launchpad (build 16692)\n"
19+
20+#: Applications/applications.js:23
21+msgid "Phone"
22+msgstr "Sími"
23+
24+#: Applications/applications.js:29
25+msgid "Camera"
26+msgstr "Myndavél"
27+
28+#: Applications/applications.js:35
29+msgid "Gallery"
30+msgstr "Myndasafn"
31+
32+#: Applications/applications.js:40
33+msgid "Facebook"
34+msgstr "Facebook"
35+
36+#: Applications/applications.js:46
37+msgid "Browser"
38+msgstr "Vafri"
39+
40+#: Applications/applications.js:51
41+msgid "Twitter"
42+msgstr "Twitter"
43+
44+#: Applications/applications.js:57
45+msgid "GMail"
46+msgstr "GMail"
47+
48+#: Applications/applications.js:62
49+msgid "Weather"
50+msgstr "Veður"
51+
52+#: Applications/applications.js:68
53+msgid "Notepad"
54+msgstr "Skrifblokk"
55+
56+#: Applications/applications.js:74
57+msgid "Calendar"
58+msgstr "Dagatal"
59+
60+#: Applications/applications.js:80
61+msgid "Media Player"
62+msgstr "Margmiðlunarspilari"
63+
64+#: Applications/applications.js:86
65+msgid "Evernote"
66+msgstr "Evernote"
67+
68+#: Applications/applications.js:91
69+msgid "Map"
70+msgstr "Kort"
71+
72+#: Applications/applications.js:96
73+msgid "Pinterest"
74+msgstr "Pinterest"
75+
76+#: Applications/applications.js:101
77+msgid "SoundCloud"
78+msgstr "SoundCloud"
79+
80+#: Applications/applications.js:106
81+msgid "Wikipedia"
82+msgstr "Wikipedia"
83+
84+#: Applications/applications.js:111
85+msgid "YouTube"
86+msgstr "YouTube"
87+
88+#: Components/PageHeader.qml:300
89+msgid "Recent searches"
90+msgstr "Nýlegar leitir"
91+
92+#: Dash/DashApps.qml:65
93+msgid "Running apps"
94+msgstr "Forrit í gangi"
95+
96+#: Dash/DashApps.qml:68
97+msgid "Frequently used"
98+msgstr "Oft notað"
99+
100+#: Dash/DashApps.qml:71
101+msgid "Installed"
102+msgstr "Uppsett"
103+
104+#: Dash/DashApps.qml:74
105+msgid "Available for download"
106+msgstr "Fáanlegt til niðurhals"
107+
108+#: Dash/DashApps.qml:178
109+msgid "Apps"
110+msgstr "Forrit"
111+
112+#: Dash/DashHome.qml:40
113+msgid "Frequent Apps"
114+msgstr "Margnotuð Forrit"
115+
116+#: Dash/DashHome.qml:43
117+msgid "Recent Music"
118+msgstr "Nýleg Tónlist"
119+
120+#: Dash/DashHome.qml:46
121+msgid "Videos Popular Online"
122+msgstr "Vinsæl Myndbönd á Netinu"
123+
124+#: Dash/DashHome.qml:175
125+msgid "Home"
126+msgstr "Heim"
127+
128+#: Dash/DashMusic.qml:27 Dash/DashVideos.qml:28
129+msgid "Featured"
130+msgstr "Efst á baugi"
131+
132+#: Dash/DashMusic.qml:28 Dash/DashVideos.qml:29
133+msgid "Recent"
134+msgstr "Nýlegt"
135+
136+#: Dash/DashMusic.qml:29 Dash/DashVideos.qml:30
137+msgid "New Releases"
138+msgstr "Nýjar Útgáfur"
139+
140+#: Dash/DashMusic.qml:30
141+msgid "Top Charting"
142+msgstr "Topp Listi"
143+
144+#: Dash/DashMusic.qml:99
145+msgid "Music"
146+msgstr "Tónlist"
147+
148+#: Dash/DashVideos.qml:31
149+msgid "Popular Online"
150+msgstr "Vinsælt á Netinu"
151+
152+#: Dash/DashVideos.qml:155
153+msgid "Videos"
154+msgstr "Myndbönd"
155+
156+#: Dash/Video/VideoPreview.qml:98
157+msgid "Directed by:"
158+msgstr "Leikstýrt af:"
159+
160+#: Dash/Video/VideoPreview.qml:121
161+msgid "Starring:"
162+msgstr "Meðal leikara eru:"
163+
164+#: Dash/Video/VideoPreview.qml:144
165+msgid "Author:"
166+msgstr "Höfundur:"
167+
168+#: Greeter/Lockscreen.qml:133
169+msgid "Emergency call"
170+msgstr "Neyðar símtal"
171+
172+#: Greeter/PassphraseLockscreen.qml:59
173+#, qt-format
174+msgid "Hello %1"
175+msgstr "Hæ %1"
176+
177+#: Greeter/PassphraseLockscreen.qml:59
178+msgid "Hello"
179+msgstr "Hæ"
180+
181+#: Hud/HudParametrizedActionsPage.qml:132
182+msgid "Confirm"
183+msgstr "Staðfesta"
184+
185+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
186+msgid "Type or say a command"
187+msgstr "Skrifaðu eða segðu eitthvað"
188+
189+#: Hud/Hud.qml:84
190+msgid "Loading. Please Wait..."
191+msgstr "Hleð. Vinsamlegast Bíðið..."
192+
193+#: Hud/Hud.qml:89
194+msgid "Speak Now..."
195+msgstr "Talaðu Núna..."
196+
197+#: Hud/Hud.qml:94
198+msgid "Speaking..."
199+msgstr "Tala..."
200+
201+#: Panel/MenuContent.qml:192
202+msgid "Device"
203+msgstr "Tæki"
204+
205+#: Panel/Menus/Overview/FlightModeWidget.qml:42
206+msgid "Flight mode"
207+msgstr "Flug hamur"
208+
209+#: Panel/SearchIndicator.qml:26
210+msgid "Search"
211+msgstr "Leita"
212+
213+#: plugins/Unity/scope.cpp:268
214+msgid "Sorry, there is nothing that matches your search."
215+msgstr "Því miður, það var ekkert sem passaði við leitarorðið þitt."
216+
217+#: Shell.qml:383
218+#, qt-format
219+msgid "Please enter %1"
220+msgstr "Vinsamlegast sláðu inn %1"
run_on_device+1−1
@@ -75,7 +75,7 @@ install_dependencies() {
7575sync_code() {
7676 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
7777 [ -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/
7979 rm -rf /tmp/$CODE_DIR
8080}
8181
tests/plugins/CMakeLists.txt+1
@@ -1,2 +1,3 @@
11add_subdirectory(Utils)
22add_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/qmltests/CMakeLists.txt−1
@@ -31,7 +31,6 @@ set(qmltest_DEFAULT_PROPERTIES "")
3131add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
3232add_qml_test(Components DraggingArea)
3333add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
34−add_qml_test(Components ListViewWithPageHeader)
3534add_qml_test(Components ResponsiveFlowView)
3635add_qml_test(Components ResponsiveGridView)
3736add_qml_test(Components Revealer)
tests/qmltests/Components/tst_DragHandle.cpp+100
@@ -59,6 +59,9 @@ private Q_SLOTS:
5959
6060 void dragThreshold_horizontal();
6161 void dragThreshold_vertical();
62+ void stretch_horizontal();
63+ void stretch_vertical();
64+ void hintingAnimation();
6265
6366private:
6467 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
@@ -290,6 +293,103 @@ void tst_DragHandle::dragThreshold_vertical()
290293 QCOMPARE(parentItem->property("shown").toBool(), false);
291294}
292295
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+
293393QTEST_MAIN(tst_DragHandle)
294394
295395#include "tst_DragHandle.moc"
tests/qmltests/Components/tst_DragHandle.qml+32
@@ -34,12 +34,19 @@ Item {
3434
3535 property var dragHandle
3636
37+ property bool stretch: false
38+ property real hintDisplacement: 0
39+
3740 VerticalShowable {
3841 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
42+ stretch: root.stretch
43+ hintDisplacement: root.hintDisplacement
3944 }
4045
4146 HorizontalShowable {
4247 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
48+ stretch: root.stretch
49+ hintDisplacement: root.hintDisplacement
4350 }
4451
4552 // Visually mark drag threshold
@@ -111,4 +118,29 @@ Item {
111118 }
112119 }
113120 }
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+ }
114146}
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+28−5
@@ -21,18 +21,35 @@ import Ubuntu.Gestures 0.1
2121
2222Showable {
2323 id: showable
24− x: -width
25− width: parent.width
24+ x: stretch ? 0 : -width
25+ width: stretch ? 0 : parent.width
2626 height: parent.height
2727
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+
2841 shown: false
2942
3043 signal dragHandleRecognizedGesture(var dragHandle)
3144
32− showAnimation: StandardAnimation { property: "x"; to: 0 }
33− hideAnimation: StandardAnimation { property: "x"; to: -width }
45+ property string animatedProp: stretch ? "width" : "x"
46+ property real propValueWhenShown: stretch ? parent.width : 0
47+ property real propValueWhenHidden: stretch ? 0 : -width
48+
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
3451
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
3653
3754 DragHandle {
3855 objectName: "leftwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
4461 width: units.gu(2)
4562
4663 direction: Direction.Leftwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.width
66+ hintDisplacement: showable.hintDisplacement
4767
4868 onStatusChanged: {
4969 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
6484 width: units.gu(2)
6585
6686 direction: Direction.Rightwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.width
89+ hintDisplacement: showable.hintDisplacement
6790
6891 onStatusChanged: {
6992 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
2121
2222Showable {
2323 id: showable
24− y: -height
24+ y: stretch ? 0 : -height
2525 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+ }
2740
2841 shown: false
2942
3043 signal dragHandleRecognizedGesture(var dragHandle)
3144
32− showAnimation: StandardAnimation { property: "y"; to: 0 }
33− hideAnimation: StandardAnimation { property: "y"; to: -height }
45+ property string animatedProp: stretch ? "height" : "y"
46+ property real propValueWhenShown: stretch ? parent.height : 0
47+ property real propValueWhenHidden: stretch ? 0 : -height
48+
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
3451
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
3653
3754 DragHandle {
3855 objectName: "upwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
4461 height: units.gu(2)
4562
4663 direction: Direction.Upwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.height
66+ hintDisplacement: showable.hintDisplacement
4767
4868 onStatusChanged: {
4969 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
6484 height: units.gu(2)
6585
6686 direction: Direction.Downwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.height
89+ hintDisplacement: showable.hintDisplacement
6790
6891 onStatusChanged: {
6992 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 {
2828
2929 Greeter {
3030 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+ }
3242 }
3343
3444 SignalSpy {
@@ -63,5 +73,34 @@ Item {
6373 tryCompare(greeter, "leftTeaserPressed", false)
6474 tryCompare(greeter, "rightTeaserPressed", false)
6575 }
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+ }
66105 }
67106}
tests/qmltests/Launcher/tst_Launcher.qml+40
@@ -43,6 +43,18 @@ Item {
4343 onDashItemSelected: {
4444 dashItemSelected_count++;
4545 }
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+ }
4658 }
4759
4860 UT.UnityTestCase {
@@ -126,5 +138,33 @@ Item {
126138 var panel = findChild(launcher, "launcherPanel")
127139 tryCompare(panel, "x", -panel.width, 1000)
128140 }
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+ }
129169 }
130170}