Merge lp:~aacid/unity/UseC++LVWPH into lp:unity/8.0

Proposed by Albert Astals Cid
Status: Merged
Approved by: Gerry Boland
Approved revision: no longer in the source branch.
Merged at revision: 63
Proposed branch: lp:~aacid/unity/UseC++LVWPH
Merge into: lp:unity/8.0
Diff against target: 5381 lines (+4516/-585)
29 files modified
Components/Carousel.qml (+2/-0)
Components/ListItems/Base.qml (+20/-22)
Components/ListItems/Header.qml (+0/-6)
Components/ListItems/ThinDivider.qml (+0/-4)
Components/ListViewWithPageHeader.qml (+0/-189)
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/-0)
debian/control (+2/-0)
debian/unity8.install (+1/-0)
plugins/CMakeLists.txt (+1/-0)
plugins/ListViewWithPageHeader/CMakeLists.txt (+44/-0)
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp (+1059/-0)
plugins/ListViewWithPageHeader/listviewwithpageheader.h (+180/-0)
plugins/ListViewWithPageHeader/plugin.cpp (+29/-0)
plugins/ListViewWithPageHeader/plugin.h (+33/-0)
plugins/ListViewWithPageHeader/qmldir (+2/-0)
run_on_device (+1/-1)
tests/plugins/CMakeLists.txt (+1/-0)
tests/plugins/ListViewWithPageHeader/CMakeLists.txt (+31/-0)
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp (+1311/-0)
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp (+1567/-0)
tests/plugins/ListViewWithPageHeader/test.qml (+96/-0)
tests/plugins/ListViewWithPageHeader/test_section.qml (+106/-0)
tests/qmltests/CMakeLists.txt (+0/-1)
tests/qmltests/Components/tst_ListViewWithPageHeader.qml (+0/-351)
To merge this branch: bzr merge lp:~aacid/unity/UseC++LVWPH
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Gerry Boland (community) Approve
Review via email: mp+168073@code.launchpad.net

Commit message

ListViewWithPageHeader implementation in C++

To post a comment you must log in.
Revision history for this message
Albert Astals Cid (aacid) wrote :

Setting to needs review because i want CI to give it a go, still work in progress (need to add a few more tests)

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

Hmm Jenkins hang, rebuild triggered.

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

FAILED: Continuous integration, rev:77
http://jenkins.qa.ubuntu.com/job/unity-8.0-ci/177/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-8.0-ci/177/rebuild

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

> Compared values are not the same Actual (lvwph->m_visibleItems.count()): 7 Expected (10): 10

Hm?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

http://pastebin.ubuntu.com/5798014/
Bug:
1. open this demo with qmlscene
2. Resize the window to make it bigger
Problem: ListView does not redraw upon being resized, only redraws after a viewPortMove. Would be an issue when including orientation support into Dash, as LV would then be resized.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

http://pastebin.ubuntu.com/5798014/
Crash Bug:
1. open this demo with qmlscene
2. scroll the list to the bottom
2. Resize the window to make it bigger
Crash!

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

I'm ever so slightly taking the mickey with this test case, but it does cause a crash.
http://pastebin.ubuntu.com/5801446/
Open this in qmlscene, and scroll up & down a bit. Can you repro?

Revision history for this message
Gerry Boland (gerboland) wrote :

Subtle visual bug in shell, see this screengrab:
http://ubuntuone.com/2RXlMfU9JnRs80n3Ywcb70

On left is this branch, on right is trunk.
Notice that on trunk, the carousel image is visible through the white pixels of the divider line at the bottom of the section delegate. This is deliberate as is wanted by design.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

http://pastebin.ubuntu.com/5801819/
Here I'm testing heightToClip. Unless I'm using it wrong, it appears to be only correct for the first delegate in each section. You see this by scrolling up slowly.

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

> I'm ever so slightly taking the mickey with this test case, but it does cause
> a crash.
> http://pastebin.ubuntu.com/5801446/
> Open this in qmlscene, and scroll up & down a bit. Can you repro?

Fixed in http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/85

Revision history for this message
Albert Astals Cid (aacid) wrote :

> http://pastebin.ubuntu.com/5801819/
> Here I'm testing heightToClip. Unless I'm using it wrong, it appears to be
> only correct for the first delegate in each section. You see this by scrolling
> up slowly.

Fixed in http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/86

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

> Subtle visual bug in shell, see this screengrab:
> http://ubuntuone.com/2RXlMfU9JnRs80n3Ywcb70
>
> On left is this branch, on right is trunk.
> Notice that on trunk, the carousel image is visible through the white pixels
> of the divider line at the bottom of the section delegate. This is deliberate
> as is wanted by design.

Fixed in http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/87

Revision history for this message
Gerry Boland (gerboland) wrote :

http://pastebin.ubuntu.com/5804245/
Load up that qml file, grab the list and drag cursor down so page header expands (i.e. list overshoots). Now release the mouse button/finger. PageHeader stays expanded for some time before eventually reseting

Revision history for this message
Gerry Boland (gerboland) wrote :

> http://pastebin.ubuntu.com/5804245/
> Load up that qml file, grab the list and drag cursor down so page header
> expands (i.e. list overshoots). Now release the mouse button/finger.
> PageHeader stays expanded for some time before eventually reseting

After discussion, this would be non-trivial to fix. As it is a non-crashing bug, and visually not too bad, I say this bug can be ignored for this MR.

Revision history for this message
Gerry Boland (gerboland) wrote :

+ clip: true // Don't leak horizontally to other dashes
This needed only now? Could we not enable clipping only while switching lens - would help prevent unnecessary clips.

+# FIXME There's no cmake var for this :-/
+ /usr/include/qt5/QtV8/5.0.2/QtV8
Could you get the version string (5.0.2) out of cmake somehow, to construct this path? For when we adopt 5.0.3 say.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> + clip: true // Don't leak horizontally to other dashes
> This needed only now? Could we not enable clipping only while switching lens -
> would help prevent unnecessary clips.

No, that's needed all the time (uncomment it and see what happens). Previously we did not need it because the whole LVPWH was clipping.

> +# FIXME There's no cmake var for this :-/
> + /usr/include/qt5/QtV8/5.0.2/QtV8
> Could you get the version string (5.0.2) out of cmake somehow, to construct
> this path? For when we adopt 5.0.3 say.

Should be doable, let me have a look

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

Visual bug on phone (not blocker):
1. Open Browser application
2. Go to Dash lens
3. Tap Search button in panel
4. Enter "t" - grid behind shows all results of search, "Running apps" disappeared
5. Tap backspace to clear search
See how first the "Running apps" section and preview appears underneath the grid, and then the grid is pushed down.

Since running apps will be going away, this is probably not important.

Revision history for this message
Gerry Boland (gerboland) wrote :

+ // This could be optimized by trying to reuse the interesection
+ // of items that may end up intersecting between the existing
+ // m_visibleItems and the items we are creating in the next loop
Mark as TODO?

+ const qreal buffer = height() / 2;
Maybe make the buffer easier to configure. A "const qreal bufferRatio = 0.5" somewhere.

Revision history for this message
Gerry Boland (gerboland) wrote :

+ setMaximumFlickVelocity(height() * 10);
+ setFlickDeceleration(height() * 2);
Why? Flickable defaults not ok? 10 and 2 come from where?

Q_FOREACH & foreach - you mix the two, please choose one

=== modified file 'run_on_device'
Why the adding --delete?

Revision history for this message
Albert Astals Cid (aacid) wrote :

> + // This could be optimized by trying to reuse the interesection
> + // of items that may end up intersecting between the existing
> + // m_visibleItems and the items we are creating in the next loop
> Mark as TODO?

http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/91

> + const qreal buffer = height() / 2;
> Maybe make the buffer easier to configure. A "const qreal bufferRatio = 0.5"
> somewhere.

http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/92

Revision history for this message
Albert Astals Cid (aacid) wrote :

> + setMaximumFlickVelocity(height() * 10);
> + setFlickDeceleration(height() * 2);
> Why? Flickable defaults not ok? 10 and 2 come from where?

They come from the old code

maximumFlickVelocity: height * 10
flickDeceleration: height * 2

> Q_FOREACH & foreach - you mix the two, please choose one

http://bazaar.launchpad.net/~aacid/unity/UseC++LVWPH/revision/93

> === modified file 'run_on_device'
> Why the adding --delete?

Because otherwise the ListViewWithPageHeader.qml is not deleted and the thing gets confused between a qml file that provides ListViewWithPageHeader and a plugin that does the same.

Revision history for this message
Gerry Boland (gerboland) wrote :

> > + clip: true // Don't leak horizontally to other dashes
> > This needed only now? Could we not enable clipping only while switching lens
> -
> > would help prevent unnecessary clips.
>
> No, that's needed all the time (uncomment it and see what happens). Previously
> we did not need it because the whole LVPWH was clipping.

Yep, you've not enough information to clip more intelligently. Best solution IMO would be to clip only when not current lens, or when lensView is moving. Since Carousel is a tiny bit slow on phone, it could help a bit. For laterz.

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

Another bug (Albert found):
1. Go to music lens
2. scroll down to bottom
3. Switch to home lens
4. Switch back to music lens
Header of Music lens goes a bit crazy. Some more lens switching completely breaks LVWPH

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

One final thing I've noticed: on the phone, on apps lens, try to flick down fast to bring list to the bottom. Often for me, with fast flicks, the list stops moving suddenly for no reason I can see - usually at the same place.

Unsure what's going on there tbh. Can you repro?

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

> One final thing I've noticed: on the phone, on apps lens, try to flick down
> fast to bring list to the bottom. Often for me, with fast flicks, the list
> stops moving suddenly for no reason I can see - usually at the same place.
>
> Unsure what's going on there tbh. Can you repro?

Yes, I have been able to reproduce that, my analisis shows that it is because of this:
 * We are scrollling
 * We create the last item of the list, it reports a very small size
 * We continue scrolling down and eventually we start going into overshoot mode, the list decides to go back to the stable position
 * At this moment the last item grows, but the list is already going back to the previous stable position

There's not much more we can do from the list, I'm going to try to see if we can make items give more accurate heights from the beginning.

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

Everything working perfectly, nice work Albert!

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote :

Just waiting for CI to approve, then will top-level-approce

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Components/Carousel.qml'
2--- Components/Carousel.qml 2013-06-06 07:57:05 +0000
3+++ Components/Carousel.qml 2013-07-01 11:23:32 +0000
4@@ -24,6 +24,8 @@
5 Item {
6 id: carousel
7
8+ clip: true // Don't leak horizontally to other dashes
9+
10 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
11 property Component itemComponent
12 /// Model for the Carousel, which has to be a model usable by a ListView
13
14=== modified file 'Components/ListItems/Base.qml'
15--- Components/ListItems/Base.qml 2013-06-05 22:03:08 +0000
16+++ Components/ListItems/Base.qml 2013-07-01 11:23:32 +0000
17@@ -54,35 +54,33 @@
18 */
19 function __showDivider() {
20 // if we're not in ListView, always show a thin dividing line at the bottom
21- if (ListView.view !== null) {
22-
23+ var model = null;
24+ if (typeof ListViewWithPageHeader !== 'undefined') {
25+ if (typeof ListViewWithPageHeader.model !== 'undefined') {
26+ model = ListViewWithPageHeader.model;
27+ }
28+ } else if (ListView.view !== null) {
29+ model = ListView.view.model;
30+ }
31 // if we're last item in ListView don't show divider
32- if (index === ListView.view.model.count - 1) return false;
33- }
34+ if (model && index === model.count - 1) return false;
35+
36 return true;
37 }
38
39- property bool __clippingRequired: ListView.view !== null
40- && ListView.view.section.labelPositioning & ViewSection.CurrentLabelAtStart
41-
42- property real __yPositionRelativeToListView: ListView.view ? y - ListView.view.contentY : y
43+ /* Relevant really only for ListViewWithPageHeader case: specify how many pixels we can overlap with the section header */
44+ readonly property int allowedOverlap: units.dp(1)
45
46 property real __heightToClip: {
47 // Check this is in position where clipping is needed
48- if (__clippingRequired && __yPositionRelativeToListView <= __sectionDelegateHeight
49- && __yPositionRelativeToListView > -height) {
50- return Math.min(__sectionDelegateHeight - __yPositionRelativeToListView, height);
51- } else {
52- return 0;
53- }
54- }
55-
56- property int __sectionDelegateHeight: {
57- if (__clippingRequired && ListView.view.hasOwnProperty("__sectionDelegateHeight")) {
58- return ListView.view.__sectionDelegateHeight;
59- } else {
60- return 0;
61- }
62+ if (typeof ListViewWithPageHeader !== 'undefined') {
63+ if (typeof heightToClip !== 'undefined') {
64+ if (heightToClip >= allowedOverlap) {
65+ return heightToClip - allowedOverlap;
66+ }
67+ }
68+ }
69+ return 0;
70 }
71
72 /*!
73
74=== modified file 'Components/ListItems/Header.qml'
75--- Components/ListItems/Header.qml 2013-06-05 22:03:08 +0000
76+++ Components/ListItems/Header.qml 2013-07-01 11:23:32 +0000
77@@ -25,12 +25,6 @@
78 */
79 property alias text: label.text
80
81- /* When this header is used in a ListViewWithPageHeader with CurrentLabelAtStart mode, to stop
82- the list's delegates contents being visible through this header, they clip themselves
83- depending on their position. However to allow delegates underlap the bottom X pixels of this
84- header (so delegate appears through ThinDivider say) we set an offset property here. */
85- readonly property int bottomBorderAllowedOverlap: bottomBorder.allowedBottomOverlap
86-
87 height: units.gu(4.5)
88
89 Item {
90
91=== modified file 'Components/ListItems/ThinDivider.qml'
92--- Components/ListItems/ThinDivider.qml 2013-06-05 22:03:08 +0000
93+++ Components/ListItems/ThinDivider.qml 2013-07-01 11:23:32 +0000
94@@ -23,9 +23,5 @@
95 }
96 height: (visible) ? units.dp(2) : 0
97
98- /* Relevant really only for ListViewWithPageHeader with Base delegates: should this image
99- be overlapping content, specify how many pixels from the bottom should be transparent */
100- readonly property int allowedBottomOverlap: units.dp(1)
101-
102 source: "graphics/ListItemDividerHorizontal.png"
103 }
104
105=== removed file 'Components/ListViewWithPageHeader.qml'
106--- Components/ListViewWithPageHeader.qml 2013-06-05 22:03:08 +0000
107+++ Components/ListViewWithPageHeader.qml 1970-01-01 00:00:00 +0000
108@@ -1,189 +0,0 @@
109-/*
110- * Copyright (C) 2013 Canonical, Ltd.
111- *
112- * This program is free software; you can redistribute it and/or modify
113- * it under the terms of the GNU General Public License as published by
114- * the Free Software Foundation; version 3.
115- *
116- * This program is distributed in the hope that it will be useful,
117- * but WITHOUT ANY WARRANTY; without even the implied warranty of
118- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
119- * GNU General Public License for more details.
120- *
121- * You should have received a copy of the GNU General Public License
122- * along with this program. If not, see <http://www.gnu.org/licenses/>.
123- */
124-
125-import QtQuick 2.0
126-import "Math.js" as MathLocal
127-
128-Item {
129- id: root
130- property Item pageHeader: null
131- property Component sectionDelegate: null
132- property string sectionProperty: ""
133- property alias model: listView.model
134- property alias delegate: listView.delegate
135- property ListView view: listView
136- property alias moving: flicker.moving
137- property alias atYEnd: flicker.atYEnd
138- property bool clipListView: true
139-
140- readonly property real __headerHeight: (pageHeader) ? pageHeader.implicitHeight : 0
141- property real __headerVisibleHeight: __headerHeight
142- readonly property real __overshootHeight: (flicker.contentY < 0) ? -flicker.contentY : 0
143-
144-
145- // TODO move to AnimationController
146- ParallelAnimation {
147- id: headerAnimation
148- property real targetContentY
149- NumberAnimation {
150- target: root
151- property: "__headerVisibleHeight"
152- to: root.__headerHeight
153- duration: 200
154- easing.type: Easing.OutQuad
155- }
156- NumberAnimation {
157- target: listView
158- property: "contentY"
159- to: headerAnimation.targetContentY
160- duration: 200
161- easing.type: Easing.OutQuad
162- }
163- }
164-
165- function positionAtBeginning() {
166- __headerVisibleHeight = __headerHeight
167- flicker.contentY = 0
168- }
169-
170- function showHeader() {
171- headerAnimation.targetContentY = listView.contentY + (__headerHeight - __headerVisibleHeight)
172- headerAnimation.start()
173- }
174-
175- function flick(xVelocity, yVelocity) {
176- flicker.flick(xVelocity, yVelocity);
177- }
178-
179- onPageHeaderChanged: {
180- pageHeader.parent = pageHeaderContainer;
181- pageHeader.anchors.fill = pageHeaderContainer;
182- }
183-
184- Item {
185- id: pageHeaderClipper
186- parent: flicker // parent to Flickable so mouse click events passed through to the header component
187- anchors {
188- top: parent.top
189- left: parent.left
190- right: parent.right
191- }
192- height: __headerVisibleHeight + __overshootHeight
193-
194- Item {
195- id: pageHeaderContainer
196- anchors {
197- left: parent.left
198- right: parent.right
199- bottom: parent.bottom
200- }
201- height: __headerHeight + __overshootHeight
202- }
203- }
204-
205- ListView {
206- id: listView
207- parent: flicker // parent to Flickable so mouse click events passed through to List delegates
208- anchors {
209- left: parent.left
210- right: parent.right
211- top: parent.top
212- topMargin: __headerVisibleHeight
213- }
214- height: root.height
215-
216- // FIXME scrolling workaround, see below
217- cacheBuffer: height*10
218-
219- section.property: sectionProperty
220- section.criteria: ViewSection.FullString
221- section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
222- section.delegate: sectionDelegate
223-
224- interactive: false
225- clip: root.clipListView
226-
227- property int __sectionDelegateHeight: __getHeight(section.delegate)
228- function __getHeight(component) {
229- // want height (minus allowed overlap) of the section delegate as is needed for clipping
230- if (component === null) return 0;
231- var object = component.createObject(); //FIXME: throws 'section' not defined error
232- var value = object.height - object.bottomBorderAllowedOverlap;
233- object.destroy();
234- return value;
235- }
236-
237- property real previousOriginY: 0
238- onOriginYChanged: {
239- var deltaOriginY = originY - previousOriginY;
240- previousOriginY = originY;
241- /* When originY changes, it causes the top of the flicker and the top of the list to fall
242- out of sync - and thus the contentY positioning will be broken. To correct for this we
243- manually change the flickable's contentY here */
244- flicker.contentY -= deltaOriginY;
245- }
246-
247- /* For case when list content greater than container height and list scrolled down so header
248- hidden. If content shrinks to be smaller than the container height, we want the header to
249- re-appear */
250- property real __previousContentHeight: 0
251- onContentHeightChanged: {
252- var deltaContentHeight = contentHeight - __previousContentHeight;
253- __previousContentHeight = contentHeight;
254- if (contentHeight < height && deltaContentHeight < 0 && __headerVisibleHeight < height - contentHeight) {
255- __headerVisibleHeight = Math.min(height - contentHeight, __headerHeight);
256- }
257- }
258- }
259-
260- Flickable {
261- id: flicker
262- anchors.fill: parent
263- contentHeight: listView.contentHeight + __headerHeight
264- maximumFlickVelocity: height * 10
265- flickDeceleration: height * 2
266- onContentYChanged: {
267- var deltaContentY = contentY - __previousContentY;
268- __previousContentY = contentY;
269-
270- // first decide if movement will prompt the page header to change height
271- if ((deltaContentY < 0 && __headerVisibleHeight >= 0) ||
272- (deltaContentY > 0 && __headerVisibleHeight <= __headerHeight)) {
273-
274- // calculate header height - but prevent bounce from changing it
275- if (contentY > 0 && contentY < contentHeight - height) {
276- __headerVisibleHeight = MathLocal.clamp(__headerVisibleHeight - deltaContentY, 0, __headerHeight);
277- }
278- }
279-
280- // now we move list position, taking into account page header height
281-
282- // BUG: With section headers enabled, the values of originY and contentY appear not
283- // correct at the exact point originY changes. originY changes when the ListView
284- // deletes/creates hidden delegates which are above the visible delegates.
285- // As a result of this bug, you experience jittering scrolling when rapidly moving
286- // around in large lists. See https://bugreports.qt-project.org/browse/QTBUG-27997
287- // A workaround is to use a large enough cacheBuffer to prevent deletions/creations
288- // so effectively originY is always zero.
289- var newContentY = flicker.contentY + listView.originY - __headerHeight + __headerVisibleHeight
290- if (newContentY < listView.contentHeight) {
291- listView.contentY = newContentY;
292- }
293- }
294-
295- property real __previousContentY: 0
296- }
297-}
298
299=== modified file 'Dash/DashApps.qml'
300--- Dash/DashApps.qml 2013-06-12 12:52:35 +0000
301+++ Dash/DashApps.qml 2013-07-01 11:23:32 +0000
302@@ -99,7 +99,7 @@
303 When using Loader to load external QML file in the list deelgate, the ListView has
304 a bug where it can position the delegate content to overlap the section header
305 of the ListView - a workaround is to use sourceComponent of Loader instead */
306- Component { id: runningApplicationsGrid; RunningApplicationsGrid {} }
307+ Component { id: runningApplicationsGrid; RunningApplicationsGrid { clip: true } }
308 Component {
309 id: applicationsFilterGrid;
310 ApplicationsFilterGrid {
311@@ -112,7 +112,7 @@
312 "ApplicationsFilterGrid": applicationsFilterGrid,
313 }
314
315- ListViewWithPageHeader {
316+ ScopeListView {
317 id: categoryView
318 anchors.fill: parent
319 model: SortFilterProxyModel {
320@@ -150,7 +150,6 @@
321 item.model = categoryModels[modelName]
322 }
323 }
324- asynchronous: true
325 }
326
327 ListView.onRemove: SequentialAnimation {
328
329=== modified file 'Dash/DashHome.qml'
330--- Dash/DashHome.qml 2013-06-14 13:54:08 +0000
331+++ Dash/DashHome.qml 2013-07-01 11:23:32 +0000
332@@ -130,7 +130,7 @@
333 "VideosGrid": videosGrid,
334 }
335
336- ListViewWithPageHeader {
337+ ScopeListView {
338 id: listView
339 anchors.fill: parent
340 model: categoryListModel
341@@ -161,7 +161,6 @@
342 item.clicked.connect(playVideo);
343 }
344 }
345- asynchronous: true
346 }
347 }
348
349
350=== modified file 'Dash/DashMusic.qml'
351--- Dash/DashMusic.qml 2013-06-14 20:57:40 +0000
352+++ Dash/DashMusic.qml 2013-07-01 11:23:32 +0000
353@@ -64,7 +64,7 @@
354 }
355 }
356
357- ListViewWithPageHeader {
358+ ScopeListView {
359 id: categoryView
360 anchors.fill: parent
361 model: scopeView.categories
362@@ -84,7 +84,6 @@
363 onLoaded: {
364 item.model = results
365 }
366- asynchronous: true
367 }
368 }
369
370
371=== modified file 'Dash/DashVideos.qml'
372--- Dash/DashVideos.qml 2013-06-14 20:57:40 +0000
373+++ Dash/DashVideos.qml 2013-07-01 11:23:32 +0000
374@@ -97,11 +97,11 @@
375 }
376 }
377
378- ListViewWithPageHeader {
379+ ScopeListView {
380 id: categoryView
381 anchors.fill: parent
382 model: scopeView.categories
383- clipListView: !previewLoader.onScreen
384+ forceNoClip: previewLoader.onScreen
385
386 onAtYEndChanged: if (atYEnd) endReached()
387 onMovingChanged: if (moving && atYEnd) endReached()
388@@ -119,7 +119,6 @@
389 onLoaded: {
390 item.model = results
391 }
392- asynchronous: true
393
394 Connections {
395 target: loader.item
396
397=== modified file 'Dash/GenericScopeView.qml'
398--- Dash/GenericScopeView.qml 2013-06-12 12:52:35 +0000
399+++ Dash/GenericScopeView.qml 2013-07-01 11:23:32 +0000
400@@ -43,7 +43,7 @@
401 }
402 }
403
404- ListViewWithPageHeader {
405+ ScopeListView {
406 id: categoryView
407 anchors.fill: parent
408 model: scopeView.categories
409
410=== added file 'Dash/ScopeListView.qml'
411--- Dash/ScopeListView.qml 1970-01-01 00:00:00 +0000
412+++ Dash/ScopeListView.qml 2013-07-01 11:23:32 +0000
413@@ -0,0 +1,23 @@
414+/*
415+ * Copyright (C) 2013 Canonical, Ltd.
416+ *
417+ * This program is free software; you can redistribute it and/or modify
418+ * it under the terms of the GNU General Public License as published by
419+ * the Free Software Foundation; version 3.
420+ *
421+ * This program is distributed in the hope that it will be useful,
422+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
423+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
424+ * GNU General Public License for more details.
425+ *
426+ * You should have received a copy of the GNU General Public License
427+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
428+ */
429+
430+import QtQuick 2.0
431+import ListViewWithPageHeader 0.1
432+
433+ListViewWithPageHeader {
434+ maximumFlickVelocity: height * 10
435+ flickDeceleration: height * 2
436+}
437
438=== modified file 'debian/control'
439--- debian/control 2013-06-20 00:01:16 +0000
440+++ debian/control 2013-07-01 11:23:32 +0000
441@@ -11,6 +11,7 @@
442 libgles2-mesa-dev[armhf],
443 libhud-client2-dev,
444 libpulse-dev,
445+ libqt5v8-5-private-dev,
446 libunity-core-6.0-dev (<< 7.80),
447 libxcb1-dev,
448 pkg-config,
449@@ -20,6 +21,7 @@
450 qtbase5-private-dev,
451 qtdeclarative5-dev,
452 qtdeclarative5-dev-tools,
453+ qtdeclarative5-private-dev,
454 qtdeclarative5-qtquick2-plugin,
455 qtdeclarative5-test-plugin,
456 qtdeclarative5-ubuntu-ui-toolkit-plugin,
457
458=== modified file 'debian/unity8.install'
459--- debian/unity8.install 2013-06-05 22:03:08 +0000
460+++ debian/unity8.install 2013-07-01 11:23:32 +0000
461@@ -15,6 +15,7 @@
462 /usr/share/unity8/graphics/*
463 /usr/share/unity8/plugins/HudClient/*
464 /usr/share/unity8/plugins/LightDM/*
465+/usr/share/unity8/plugins/ListViewWithPageHeader/*
466 /usr/share/unity8/plugins/Ubuntu/*
467 /usr/share/unity8/plugins/Unity/*
468 /usr/share/unity8/plugins/Utils/*
469
470=== modified file 'plugins/CMakeLists.txt'
471--- plugins/CMakeLists.txt 2013-06-05 22:03:08 +0000
472+++ plugins/CMakeLists.txt 2013-07-01 11:23:32 +0000
473@@ -3,3 +3,4 @@
474 add_subdirectory(Unity)
475 add_subdirectory(HudClient)
476 add_subdirectory(LightDM)
477+add_subdirectory(ListViewWithPageHeader)
478
479=== added directory 'plugins/ListViewWithPageHeader'
480=== added file 'plugins/ListViewWithPageHeader/CMakeLists.txt'
481--- plugins/ListViewWithPageHeader/CMakeLists.txt 1970-01-01 00:00:00 +0000
482+++ plugins/ListViewWithPageHeader/CMakeLists.txt 2013-07-01 11:23:32 +0000
483@@ -0,0 +1,44 @@
484+# There's no cmake var for v8 include path :-/
485+# so create one
486+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
487+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
488+
489+include_directories(
490+ ${CMAKE_CURRENT_SOURCE_DIR}
491+ ${CMAKE_CURRENT_BINARY_DIR}
492+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
493+ ${Qt5Quick_INCLUDE_DIRS}
494+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
495+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
496+)
497+
498+remove_definitions(-DQT_NO_KEYWORDS)
499+
500+set(QMLPLUGIN_SRC
501+ plugin.cpp
502+ listviewwithpageheader.cpp
503+ )
504+
505+add_library(ListViewWithPageHeader-qml MODULE
506+ ${QMLPLUGIN_SRC}
507+ )
508+
509+target_link_libraries(ListViewWithPageHeader-qml
510+ ${Qt5Gui_LIBRARIES}
511+ ${Qt5Quick_LIBRARIES}
512+ )
513+
514+qt5_use_modules(ListViewWithPageHeader-qml Qml Quick)
515+
516+# copy qmldir file into build directory for shadow builds
517+file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
518+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
519+ )
520+
521+install(TARGETS ListViewWithPageHeader-qml
522+ DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
523+ )
524+
525+install(FILES qmldir
526+ DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
527+ )
528
529=== added file 'plugins/ListViewWithPageHeader/listviewwithpageheader.cpp'
530--- plugins/ListViewWithPageHeader/listviewwithpageheader.cpp 1970-01-01 00:00:00 +0000
531+++ plugins/ListViewWithPageHeader/listviewwithpageheader.cpp 2013-07-01 11:23:32 +0000
532@@ -0,0 +1,1059 @@
533+/*
534+ * Copyright (C) 2013 Canonical, Ltd.
535+ *
536+ * This program is free software; you can redistribute it and/or modify
537+ * it under the terms of the GNU General Public License as published by
538+ * the Free Software Foundation; version 3.
539+ *
540+ * This program is distributed in the hope that it will be useful,
541+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
542+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
543+ * GNU General Public License for more details.
544+ *
545+ * You should have received a copy of the GNU General Public License
546+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
547+ */
548+
549+/*
550+ * Some documentation on how this thing works:
551+ *
552+ * A flickable has two very important concepts that define the top and
553+ * height of the flickable area.
554+ * The top is returned in minYExtent()
555+ * The height is set using setContentHeight()
556+ * By changing those two values we can make the list grow up or down
557+ * as needed. e.g. if we are in the middle of the list
558+ * and something that is above the viewport grows, since we do not
559+ * want to change the viewport because of that we just adjust the
560+ * minYExtent so that the list grows up.
561+ *
562+ * The implementation on the list relies on the delegateModel doing
563+ * most of the instantiation work. You call createItem() when you
564+ * need to create an item asking for it async or not. If returns null
565+ * it means the item will be created async and the model will call the
566+ * itemCreated slot with the item.
567+ *
568+ * updatePolish is the central point of dispatch for the work of the
569+ * class. It is called by the scene graph just before drawing the class.
570+ * In it we:
571+ * * Make sure all items are positioned correctly
572+ * * Add/Remove items if needed
573+ * * Update the content height if it was dirty
574+ *
575+ * m_visibleItems contains all the items we have created at the moment.
576+ * Actually not all of them are visible since it includes the ones
577+ * in the cache area we create asynchronously to help performance.
578+ * The first item in m_visibleItems has the m_firstVisibleIndex in
579+ * the model. If you actually want to know what is the first
580+ * item in the viewport you have to find the first non culled element
581+ * in m_visibleItems
582+ *
583+ * All the items (except the header) are childs of m_clipItem which
584+ * is a child of the contentItem() of the flickable (The contentItem()
585+ * is what actually 'moves' in a a flickable). This way
586+ * we can implement the clipping needed so we can have the header
587+ * shown in the middle of the list over the items without the items
588+ * leaking under the header in case it is transparent.
589+ *
590+ * The first item of m_visibleItems is the one that defines the
591+ * positions of all the rest of items (see updatePolish()) and
592+ * this is why sometimes we move it even if it's not the item
593+ * that has triggered the function (i.e. in itemGeometryChanged())
594+ *
595+ * m_visibleItems is a list of ListItem. Each ListItem
596+ * will contain a item and potentially a sectionItem. The sectionItem
597+ * is only there when the list is using sectionDelegate+sectionProperty
598+ * and this is the first item of the section. Each ListItem is vertically
599+ * layouted with the sectionItem first and then the item.
600+ *
601+ * For sectioning we also have a section item alone (m_topSectionItem)
602+ * that is used for the cases we need to show the sticky section item at
603+ * the top of the view.
604+ *
605+ * Each delegate item has a context property called heightToClip that is
606+ * used to communicate to the delegate implementation in case it has to
607+ * clip itself because of overlapping with the top sticky section item.
608+ * This is an implementation decision since it has been agreed it
609+ * is easier to implement the clipping in QML with this info than to
610+ * do it at the C++ level.
611+ *
612+ * Note that minYExtent and height are not always totally accurate, since
613+ * we don't have the items created we can't guess their heights
614+ * so we can only guarantee the values are correct when the first/last
615+ * items of the list are visible, otherwise we just live with good enough
616+ * values that make the list scrollable
617+ *
618+ * There are a few things that are not really implemented or tested properly
619+ * which we don't use at the moment like changing the model, changing
620+ * the section delegate, having a section delegate that changes its size, etc.
621+ * The known missing features are marked with TODOs along the code.
622+ */
623+
624+#include "listviewwithpageheader.h"
625+
626+#include <QDebug>
627+#include <qqmlinfo.h>
628+#include <qqmlengine.h>
629+#include <private/qquickvisualdatamodel_p.h>
630+#include <private/qqmlglobal_p.h>
631+#include <private/qquickitem_p.h>
632+#include <private/qquickanimation_p.h>
633+// #include <private/qquickrectangle_p.h>
634+
635+static const qreal bufferRatio = 0.5;
636+
637+qreal ListViewWithPageHeader::ListItem::height() const
638+{
639+ return m_item->height() + (m_sectionItem ? m_sectionItem->height() : 0);
640+}
641+
642+qreal ListViewWithPageHeader::ListItem::y() const
643+{
644+ return m_item->y() - (m_sectionItem ? m_sectionItem->height() : 0);
645+}
646+
647+void ListViewWithPageHeader::ListItem::setY(qreal newY)
648+{
649+ if (m_sectionItem) {
650+ m_sectionItem->setY(newY);
651+ m_item->setY(newY + m_sectionItem->height());
652+ } else {
653+ m_item->setY(newY);
654+ }
655+}
656+
657+bool ListViewWithPageHeader::ListItem::culled() const
658+{
659+ return QQuickItemPrivate::get(m_item)->culled;
660+}
661+
662+void ListViewWithPageHeader::ListItem::setCulled(bool culled)
663+{
664+ QQuickItemPrivate::get(m_item)->setCulled(culled);
665+ if (m_sectionItem)
666+ QQuickItemPrivate::get(m_sectionItem)->setCulled(culled);
667+}
668+
669+ListViewWithPageHeader::ListViewWithPageHeader()
670+ : m_delegateModel(nullptr)
671+ , m_asyncRequestedIndex(-1)
672+ , m_delegateValidated(false)
673+ , m_firstVisibleIndex(-1)
674+ , m_minYExtent(0)
675+ , m_contentHeightDirty(false)
676+ , m_headerItem(nullptr)
677+ , m_previousContentY(0)
678+ , m_headerItemShownHeight(0)
679+ , m_sectionDelegate(nullptr)
680+ , m_topSectionItem(nullptr)
681+ , m_forceNoClip(false)
682+ , m_inLayout(false)
683+{
684+ m_clipItem = new QQuickItem(contentItem());
685+// m_clipItem = new QQuickRectangle(contentItem());
686+// ((QQuickRectangle*)m_clipItem)->setColor(Qt::gray);
687+
688+ m_headerShowAnimation = new QQuickNumberAnimation(this);
689+ m_headerShowAnimation->setEasing(QEasingCurve::OutQuad);
690+ m_headerShowAnimation->setProperty("contentY");
691+ m_headerShowAnimation->setDuration(200);
692+ m_headerShowAnimation->setTargetObject(this);
693+
694+ connect(this, SIGNAL(contentWidthChanged()), this, SLOT(onContentWidthChanged()));
695+ connect(this, SIGNAL(contentHeightChanged()), this, SLOT(onContentHeightChanged()));
696+ connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
697+ connect(m_headerShowAnimation, SIGNAL(stopped()), this, SLOT(onShowHeaderAnimationFinished()));
698+}
699+
700+ListViewWithPageHeader::~ListViewWithPageHeader()
701+{
702+}
703+
704+QAbstractItemModel *ListViewWithPageHeader::model() const
705+{
706+ return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
707+}
708+
709+void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
710+{
711+ if (model != this->model()) {
712+ if (!m_delegateModel) {
713+ createDelegateModel();
714+ } else {
715+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
716+ }
717+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
718+ connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
719+ Q_EMIT modelChanged();
720+ // TODO?
721+// Q_EMIT contentHeightChanged();
722+// Q_EMIT contentYChanged();
723+ }
724+}
725+
726+QQmlComponent *ListViewWithPageHeader::delegate() const
727+{
728+ return m_delegateModel ? m_delegateModel->delegate() : nullptr;
729+}
730+
731+void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
732+{
733+ if (delegate != this->delegate()) {
734+ if (!m_delegateModel) {
735+ createDelegateModel();
736+ }
737+
738+ // Cleanup the existing items
739+ Q_FOREACH(ListItem *item, m_visibleItems)
740+ releaseItem(item);
741+ m_visibleItems.clear();
742+ m_firstVisibleIndex = -1;
743+ adjustMinYExtent();
744+ setContentY(0);
745+ m_clipItem->setY(0);
746+ if (m_topSectionItem) {
747+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
748+ }
749+
750+ m_delegateModel->setDelegate(delegate);
751+
752+ Q_EMIT delegateChanged();
753+ m_delegateValidated = false;
754+ m_contentHeightDirty = true;
755+ polish();
756+ }
757+}
758+
759+QQuickItem *ListViewWithPageHeader::header() const
760+{
761+ return m_headerItem;
762+}
763+
764+void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
765+{
766+ if (m_headerItem != headerItem) {
767+ qreal oldHeaderHeight = 0;
768+ qreal oldHeaderY = 0;
769+ if (m_headerItem) {
770+ oldHeaderHeight = m_headerItem->height();
771+ oldHeaderY = m_headerItem->y();
772+ m_headerItem->setParentItem(nullptr);
773+ }
774+ m_headerItem = headerItem;
775+ if (m_headerItem) {
776+ m_headerItem->setParentItem(contentItem());
777+ m_headerItem->setZ(1);
778+ }
779+ qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
780+ if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
781+ headerHeightChanged(newHeaderHeight, oldHeaderHeight, oldHeaderY);
782+ polish();
783+ m_contentHeightDirty = true;
784+ }
785+ Q_EMIT headerChanged();
786+ }
787+}
788+
789+QQmlComponent *ListViewWithPageHeader::sectionDelegate() const
790+{
791+ return m_sectionDelegate;
792+}
793+
794+void ListViewWithPageHeader::setSectionDelegate(QQmlComponent *delegate)
795+{
796+ if (delegate != m_sectionDelegate) {
797+ // TODO clean existing sections
798+
799+ m_sectionDelegate = delegate;
800+
801+ m_topSectionItem = getSectionItem(QString());
802+ m_topSectionItem->setZ(3);
803+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
804+
805+ // TODO create sections for existing items
806+
807+ Q_EMIT sectionDelegateChanged();
808+ }
809+}
810+
811+QString ListViewWithPageHeader::sectionProperty() const
812+{
813+ return m_sectionProperty;
814+}
815+
816+void ListViewWithPageHeader::setSectionProperty(const QString &property)
817+{
818+ if (property != m_sectionProperty) {
819+ m_sectionProperty = property;
820+
821+ updateWatchedRoles();
822+
823+ // TODO recreate sections
824+
825+ Q_EMIT sectionPropertyChanged();
826+ }
827+}
828+
829+bool ListViewWithPageHeader::forceNoClip() const
830+{
831+ return m_forceNoClip;
832+}
833+
834+void ListViewWithPageHeader::setForceNoClip(bool noClip)
835+{
836+ if (noClip != m_forceNoClip) {
837+ m_forceNoClip = noClip;
838+ updateClipItem();
839+ Q_EMIT forceNoClipChanged();
840+ }
841+}
842+
843+void ListViewWithPageHeader::positionAtBeginning()
844+{
845+ qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
846+ if (m_firstVisibleIndex != 0) {
847+ // TODO This could be optimized by trying to reuse the interesection
848+ // of items that may end up intersecting between the existing
849+ // m_visibleItems and the items we are creating in the next loop
850+ Q_FOREACH(ListItem *item, m_visibleItems)
851+ releaseItem(item);
852+ m_visibleItems.clear();
853+ m_firstVisibleIndex = -1;
854+
855+ // Create the item 0, it will be already correctly positioned at createItem()
856+ m_clipItem->setY(0);
857+ ListItem *item = createItem(0, false);
858+ // Create the subsequent items
859+ int modelIndex = 1;
860+ qreal pos = item->y() + item->height();
861+ const qreal buffer = height() * bufferRatio;
862+ const qreal bufferTo = height() + buffer;
863+ while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
864+ if (!(item = createItem(modelIndex, false)))
865+ break;
866+ pos += item->height();
867+ ++modelIndex;
868+ }
869+
870+ m_previousContentY = m_visibleItems.first()->y() - headerHeight;
871+ }
872+ setContentY(m_visibleItems.first()->y() + m_clipItem->y() - headerHeight);
873+}
874+
875+void ListViewWithPageHeader::showHeader()
876+{
877+ auto to = qMax(-minYExtent(), contentY() - m_headerItem->height() + m_headerItemShownHeight);
878+ if (to != contentY()) {
879+ m_headerShowAnimation->setTo(to);
880+ m_headerShowAnimation->start();
881+ }
882+}
883+
884+qreal ListViewWithPageHeader::minYExtent() const
885+{
886+// qDebug() << "ListViewWithPageHeader::minYExtent" << m_minYExtent;
887+ return m_minYExtent;
888+}
889+
890+void ListViewWithPageHeader::componentComplete()
891+{
892+ if (m_delegateModel)
893+ m_delegateModel->componentComplete();
894+
895+ QQuickFlickable::componentComplete();
896+
897+ polish();
898+}
899+
900+void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
901+{
902+ QQuickFlickable::viewportMoved(orient);
903+// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
904+ qreal diff = m_previousContentY - contentY();
905+ if (m_headerItem) {
906+ auto oldHeaderItemShownHeight = m_headerItemShownHeight;
907+ if (contentY() < -m_minYExtent) {
908+ // Stick the header item to the top when dragging down
909+ m_headerItem->setY(contentY());
910+ m_headerItem->setHeight(m_headerItem->implicitHeight() + (-m_minYExtent - contentY()));
911+ } else {
912+ m_headerItem->setHeight(m_headerItem->implicitHeight());
913+ // We are going down (but it's not because of the rebound at the end)
914+ // (but the header was not shown by it's own position)
915+ // or the header is partially shown
916+ const bool scrolledUp = m_previousContentY > contentY();
917+ const bool notRebounding = contentY() + height() < contentHeight();
918+ const bool notShownByItsOwn = contentY() + diff > m_headerItem->y() + m_headerItem->height();
919+
920+ if (!scrolledUp && contentY() == -m_minYExtent) {
921+ m_headerItemShownHeight = 0;
922+ m_headerItem->setY(contentY());
923+ } else if ((scrolledUp && notRebounding && notShownByItsOwn) || (m_headerItemShownHeight > 0)) {
924+ m_headerItemShownHeight += diff;
925+ if (contentY() == -m_minYExtent) {
926+ m_headerItemShownHeight = 0;
927+ } else {
928+ m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, m_headerItem->height());
929+ }
930+ if (m_headerItemShownHeight > 0) {
931+ m_headerItem->setY(contentY() - m_headerItem->height() + m_headerItemShownHeight);
932+ } else {
933+ m_headerItem->setY(-m_minYExtent);
934+ }
935+ }
936+ }
937+ // We will be changing the clip item, need to accomadate for it
938+ // otherwise we move the firstItem down/up twice (unless the
939+ // show header animation is running, where we want to keep the viewport stable)
940+ if (!m_headerShowAnimation->isRunning()) {
941+ diff += oldHeaderItemShownHeight - m_headerItemShownHeight;
942+ } else {
943+ diff = -diff;
944+ }
945+ }
946+ if (!m_visibleItems.isEmpty()) {
947+ updateClipItem();
948+ ListItem *firstItem = m_visibleItems.first();
949+ firstItem->setY(firstItem->y() + diff);
950+ if (m_headerShowAnimation->isRunning()) {
951+ adjustMinYExtent();
952+ }
953+ }
954+
955+ m_previousContentY = contentY();
956+ layout();
957+ polish();
958+}
959+
960+void ListViewWithPageHeader::createDelegateModel()
961+{
962+ m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
963+ connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
964+ if (isComponentComplete())
965+ m_delegateModel->componentComplete();
966+ updateWatchedRoles();
967+}
968+
969+void ListViewWithPageHeader::refill()
970+{
971+ if (m_inLayout) {
972+ return;
973+ }
974+ if (!isComponentComplete()) {
975+ return;
976+ }
977+
978+ const qreal buffer = height() * bufferRatio;
979+ const qreal from = contentY();
980+ const qreal to = from + height();
981+ const qreal bufferFrom = from - buffer;
982+ const qreal bufferTo = to + buffer;
983+
984+ bool added = addVisibleItems(from, to, false);
985+ bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
986+ added |= addVisibleItems(bufferFrom, bufferTo, true);
987+
988+ if (added || removed) {
989+ m_contentHeightDirty = true;
990+ }
991+}
992+
993+bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
994+{
995+ if (!delegate())
996+ return false;
997+
998+ if (m_delegateModel->count() == 0)
999+ return false;
1000+
1001+ ListItem *item;
1002+// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
1003+
1004+ int modelIndex = 0;
1005+ qreal pos = 0;
1006+ if (!m_visibleItems.isEmpty()) {
1007+ modelIndex = m_firstVisibleIndex + m_visibleItems.count();
1008+ item = m_visibleItems.last();
1009+ pos = item->y() + item->height() + m_clipItem->y();
1010+ }
1011+ bool changed = false;
1012+// qDebug() << (modelIndex < m_delegateModel->count()) << pos << fillTo;
1013+ while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
1014+// qDebug() << "refill: append item" << modelIndex << "pos" << pos << "asynchronous" << asynchronous;
1015+ if (!(item = createItem(modelIndex, asynchronous)))
1016+ break;
1017+ pos += item->height();
1018+ ++modelIndex;
1019+ changed = true;
1020+ }
1021+
1022+ modelIndex = 0;
1023+ pos = 0;
1024+ if (!m_visibleItems.isEmpty()) {
1025+ modelIndex = m_firstVisibleIndex - 1;
1026+ item = m_visibleItems.first();
1027+ pos = item->y() + m_clipItem->y();
1028+ }
1029+ while (modelIndex >= 0 && pos > fillFrom) {
1030+// qDebug() << "refill: prepend item" << modelIndex << "pos" << pos << "fillFrom" << fillFrom << "asynchronous" << asynchronous;
1031+ if (!(item = createItem(modelIndex, asynchronous)))
1032+ break;
1033+ pos -= item->height();
1034+ --modelIndex;
1035+ changed = true;
1036+ }
1037+
1038+ return changed;
1039+}
1040+
1041+void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
1042+{
1043+ QQuickItem *item = listItem->m_item;
1044+ QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
1045+ if (flags & QQuickVisualModel::Destroyed) {
1046+ item->setParentItem(nullptr);
1047+ }
1048+ delete listItem->m_sectionItem;
1049+ delete listItem;
1050+}
1051+
1052+void ListViewWithPageHeader::releaseItem(ListItem *listItem)
1053+{
1054+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(listItem->m_item);
1055+ itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
1056+ m_itemsToRelease << listItem;
1057+}
1058+
1059+void ListViewWithPageHeader::updateWatchedRoles()
1060+{
1061+ if (m_delegateModel) {
1062+ QList<QByteArray> roles;
1063+ if (!m_sectionProperty.isEmpty())
1064+ roles << m_sectionProperty.toUtf8();
1065+ m_delegateModel->setWatchedRoles(roles);
1066+ }
1067+}
1068+
1069+QQuickItem *ListViewWithPageHeader::getSectionItem(int modelIndex, bool alreadyInserted)
1070+{
1071+ if (!m_sectionDelegate)
1072+ return nullptr;
1073+
1074+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
1075+ if (modelIndex > 0) {
1076+ const QString prevSection = m_delegateModel->stringValue(modelIndex - 1, m_sectionProperty);
1077+ if (section == prevSection)
1078+ return nullptr;
1079+ }
1080+ if (modelIndex + 1 < model()->rowCount() && !alreadyInserted) {
1081+ // Already inserted items can't steal next section header
1082+ const QString nextSection = m_delegateModel->stringValue(modelIndex + 1, m_sectionProperty);
1083+ if (section == nextSection) {
1084+ // Steal the section header
1085+ ListItem *nextItem = itemAtIndex(modelIndex); // Not +1 since not yet inserted into m_visibleItems
1086+ if (nextItem) {
1087+ QQuickItem *sectionItem = nextItem->m_sectionItem;
1088+ nextItem->m_sectionItem = nullptr;
1089+ return sectionItem;
1090+ }
1091+ }
1092+ }
1093+
1094+ return getSectionItem(section);
1095+}
1096+
1097+QQuickItem *ListViewWithPageHeader::getSectionItem(const QString &sectionText)
1098+{
1099+ QQuickItem *sectionItem = nullptr;
1100+
1101+ QQmlContext *creationContext = m_sectionDelegate->creationContext();
1102+ QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this));
1103+ context->setContextProperty(QLatin1String("section"), sectionText);
1104+ QObject *nobj = m_sectionDelegate->beginCreate(context);
1105+ if (nobj) {
1106+ QQml_setParent_noEvent(context, nobj);
1107+ sectionItem = qobject_cast<QQuickItem *>(nobj);
1108+ if (!sectionItem) {
1109+ delete nobj;
1110+ } else {
1111+ sectionItem->setZ(2);
1112+ QQml_setParent_noEvent(sectionItem, m_clipItem);
1113+ sectionItem->setParentItem(m_clipItem);
1114+ }
1115+ } else {
1116+ delete context;
1117+ }
1118+ m_sectionDelegate->completeCreate();
1119+
1120+ // TODO attach to sectionItem so we can accomodate to it changing its height
1121+
1122+ return sectionItem;
1123+}
1124+
1125+bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
1126+{
1127+// qDebug() << "ListViewWithPageHeader::removeNonVisibleItems" << bufferFrom << bufferTo;
1128+ // Do not remove items if we are overshooting up or down, since we'll come back
1129+ // to the "stable" position and delete/create items without any reason
1130+ if (contentY() < -m_minYExtent) {
1131+ return false;
1132+ } else if (contentY() + height() > contentHeight()) {
1133+ return false;
1134+ }
1135+ bool changed = false;
1136+
1137+ bool foundVisible = false;
1138+ int i = 0;
1139+ int removedItems = 0;
1140+ const auto oldFirstVisibleIndex = m_firstVisibleIndex;
1141+ while (i < m_visibleItems.count()) {
1142+ ListItem *item = m_visibleItems[i];
1143+ const qreal pos = item->y() + m_clipItem->y();
1144+// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
1145+ if (pos + item->height() < bufferFrom || pos > bufferTo) {
1146+// qDebug() << "Releasing" << i << (pos + item->height() < bufferFrom) << pos + item->height() << bufferFrom << (pos > bufferTo) << pos << bufferTo;
1147+ releaseItem(item);
1148+ m_visibleItems.removeAt(i);
1149+ changed = true;
1150+ ++removedItems;
1151+ } else {
1152+ if (!foundVisible) {
1153+ foundVisible = true;
1154+ const int itemIndex = m_firstVisibleIndex + removedItems + i;
1155+ m_firstVisibleIndex = itemIndex;
1156+ }
1157+ ++i;
1158+ }
1159+ }
1160+ if (m_firstVisibleIndex != oldFirstVisibleIndex) {
1161+ adjustMinYExtent();
1162+ }
1163+
1164+ return changed;
1165+}
1166+
1167+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
1168+{
1169+// qDebug() << "CREATE ITEM" << modelIndex;
1170+ if (asynchronous && m_asyncRequestedIndex != -1)
1171+ return nullptr;
1172+
1173+ m_asyncRequestedIndex = -1;
1174+ QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
1175+ if (!item) {
1176+ m_asyncRequestedIndex = modelIndex;
1177+ return 0;
1178+ } else {
1179+// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
1180+ ListItem *listItem = new ListItem;
1181+ listItem->m_item = item;
1182+ listItem->m_sectionItem = getSectionItem(modelIndex, false /*Not yet inserted into m_visibleItems*/);
1183+ QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
1184+ ListItem *prevItem = itemAtIndex(modelIndex - 1);
1185+ bool lostItem = false; // Is an item that we requested async but because of model changes
1186+ // it is no longer attached to any of the existing items (has no prev nor next item)
1187+ // nor is the first item
1188+ if (prevItem) {
1189+ listItem->setY(prevItem->y() + prevItem->height());
1190+ } else {
1191+ ListItem *currItem = itemAtIndex(modelIndex);
1192+ if (currItem) {
1193+ // There's something already in m_visibleItems at out index, meaning this is an insert, so attach to its top
1194+ listItem->setY(currItem->y() - listItem->height());
1195+ } else {
1196+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
1197+ if (nextItem) {
1198+ listItem->setY(nextItem->y() - listItem->height());
1199+ } else if (modelIndex == 0 && m_headerItem) {
1200+ listItem->setY(m_headerItem->height());
1201+ } else if (!m_visibleItems.isEmpty()) {
1202+ lostItem = true;
1203+ }
1204+ }
1205+ }
1206+ if (lostItem) {
1207+ releaseItem(listItem);
1208+ listItem = nullptr;
1209+ } else {
1210+ listItem->setCulled(listItem->y() + listItem->height() + m_clipItem->y() < contentY() || listItem->y() + m_clipItem->y() >= contentY() + height());
1211+ if (m_visibleItems.isEmpty()) {
1212+ m_visibleItems << listItem;
1213+ } else {
1214+ m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
1215+ }
1216+ if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
1217+ m_firstVisibleIndex = modelIndex;
1218+ polish();
1219+ }
1220+ adjustMinYExtent();
1221+ m_contentHeightDirty = true;
1222+ }
1223+ return listItem;
1224+ }
1225+}
1226+
1227+void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
1228+{
1229+// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
1230+
1231+ item->setParentItem(m_clipItem);
1232+ QQmlContext *context = QQmlEngine::contextForObject(item)->parentContext();
1233+ context->setContextProperty(QLatin1String("ListViewWithPageHeader"), this);
1234+ context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
1235+ if (modelIndex == m_asyncRequestedIndex) {
1236+ createItem(modelIndex, false);
1237+ refill();
1238+ }
1239+}
1240+
1241+void ListViewWithPageHeader::updateClipItem()
1242+{
1243+ m_clipItem->setHeight(height() - m_headerItemShownHeight);
1244+ m_clipItem->setY(contentY() + m_headerItemShownHeight);
1245+ m_clipItem->setClip(!m_forceNoClip && m_headerItemShownHeight > 0);
1246+}
1247+
1248+void ListViewWithPageHeader::onContentHeightChanged()
1249+{
1250+ updateClipItem();
1251+}
1252+
1253+void ListViewWithPageHeader::onContentWidthChanged()
1254+{
1255+ m_clipItem->setWidth(contentItem()->width());
1256+}
1257+
1258+void ListViewWithPageHeader::onHeightChanged()
1259+{
1260+ polish();
1261+}
1262+
1263+void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
1264+{
1265+ // TODO Do something with reset
1266+// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
1267+ const auto oldFirstVisibleIndex = m_firstVisibleIndex;
1268+
1269+ Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
1270+// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
1271+ if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
1272+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
1273+ // If all the items we are removing are either not created or culled
1274+ // we have to grow down to avoid viewport changing
1275+ bool growDown = true;
1276+ for (int i = 0; growDown && i < remove.count; ++i) {
1277+ const int modelIndex = remove.index + i;
1278+ ListItem *item = itemAtIndex(modelIndex);
1279+ if (item && !item->culled()) {
1280+ growDown = false;
1281+ }
1282+ }
1283+ for (int i = remove.count - 1; i >= 0; --i) {
1284+ const int visibleIndex = remove.index + i - m_firstVisibleIndex;
1285+ if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
1286+ ListItem *item = m_visibleItems[visibleIndex];
1287+ // Pass the section item down if needed
1288+ if (item->m_sectionItem && visibleIndex + 1 < m_visibleItems.count()) {
1289+ ListItem *nextItem = m_visibleItems[visibleIndex + 1];
1290+ if (!nextItem->m_sectionItem) {
1291+ nextItem->m_sectionItem = item->m_sectionItem;
1292+ item->m_sectionItem = nullptr;
1293+ }
1294+ }
1295+ releaseItem(item);
1296+ m_visibleItems.removeAt(visibleIndex);
1297+ }
1298+ }
1299+ if (growDown) {
1300+ adjustMinYExtent();
1301+ } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
1302+ // We removed the first item that is the one that positions the rest
1303+ // position the new first item correctly
1304+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
1305+ }
1306+ } else if (remove.index + remove.count <= m_firstVisibleIndex) {
1307+ m_firstVisibleIndex -= remove.count;
1308+ }
1309+ for (int i = remove.count - 1; i >= 0; --i) {
1310+ const int modelIndex = remove.index + i;
1311+ if (modelIndex == m_asyncRequestedIndex) {
1312+ m_asyncRequestedIndex = -1;
1313+ } else if (modelIndex < m_asyncRequestedIndex) {
1314+ m_asyncRequestedIndex--;
1315+ }
1316+ }
1317+ }
1318+
1319+ Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
1320+// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
1321+ const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
1322+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
1323+ if (insertingInValidIndexes || firstItemWithViewOnTop)
1324+ {
1325+ // If the items we are adding won't be really visible
1326+ // we grow up instead of down to not change the viewport
1327+ bool growUp = false;
1328+ if (!firstItemWithViewOnTop) {
1329+ for (int i = 0; i < m_visibleItems.count(); ++i) {
1330+ if (!m_visibleItems[i]->culled()) {
1331+ if (insert.index <= m_firstVisibleIndex + i) {
1332+ growUp = true;
1333+ }
1334+ break;
1335+ }
1336+ }
1337+ }
1338+
1339+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
1340+ for (int i = insert.count - 1; i >= 0; --i) {
1341+ const int modelIndex = insert.index + i;
1342+ ListItem *item = createItem(modelIndex, false);
1343+ if (growUp) {
1344+ ListItem *firstItem = m_visibleItems.first();
1345+ firstItem->setY(firstItem->y() - item->height());
1346+ adjustMinYExtent();
1347+ }
1348+ // Adding an item may break a "same section" chain, so check
1349+ // if we need adding a new section item
1350+ if (m_sectionDelegate) {
1351+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
1352+ if (nextItem && !nextItem->m_sectionItem) {
1353+ nextItem->m_sectionItem = getSectionItem(modelIndex + 1, true /* alredy inserted into m_visibleItems*/);
1354+ if (growUp && nextItem->m_sectionItem) {
1355+ ListItem *firstItem = m_visibleItems.first();
1356+ firstItem->setY(firstItem->y() - nextItem->m_sectionItem->height());
1357+ adjustMinYExtent();
1358+ }
1359+ }
1360+ }
1361+ }
1362+ if (firstItemWithViewOnTop) {
1363+ ListItem *firstItem = m_visibleItems.first();
1364+ firstItem->setY(oldFirstValidIndexPos);
1365+ }
1366+ } else if (insert.index <= m_firstVisibleIndex) {
1367+ m_firstVisibleIndex += insert.count;
1368+ }
1369+
1370+ for (int i = insert.count - 1; i >= 0; --i) {
1371+ const int modelIndex = insert.index + i;
1372+ if (modelIndex <= m_asyncRequestedIndex) {
1373+ m_asyncRequestedIndex++;
1374+ }
1375+ }
1376+ }
1377+
1378+ if (m_firstVisibleIndex != oldFirstVisibleIndex) {
1379+ adjustMinYExtent();
1380+ }
1381+
1382+ layout();
1383+ polish();
1384+ m_contentHeightDirty = true;
1385+}
1386+
1387+void ListViewWithPageHeader::onShowHeaderAnimationFinished()
1388+{
1389+ m_contentHeightDirty = true;
1390+ polish();
1391+}
1392+
1393+void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QRectF &newGeometry, const QRectF &oldGeometry)
1394+{
1395+ const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1396+ if (heightDiff != 0) {
1397+ if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
1398+ ListItem *firstItem = m_visibleItems.first();
1399+ firstItem->setY(firstItem->y() - heightDiff);
1400+ adjustMinYExtent();
1401+ layout();
1402+ }
1403+ refill();
1404+ adjustMinYExtent();
1405+ polish();
1406+ m_contentHeightDirty = true;
1407+ }
1408+}
1409+
1410+void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
1411+{
1412+ const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
1413+ if (m_headerItemShownHeight > 0) {
1414+ // If the header is shown because of the clip
1415+ // Change its size
1416+ m_headerItemShownHeight += heightDiff;
1417+ m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, newHeaderHeight);
1418+ updateClipItem();
1419+ adjustMinYExtent();
1420+ } else {
1421+ if (oldHeaderY + oldHeaderHeight > contentY()) {
1422+ // If the header is shown because its position
1423+ // Change its size
1424+ ListItem *firstItem = m_visibleItems.first();
1425+ firstItem->setY(firstItem->y() + heightDiff);
1426+ layout();
1427+ } else {
1428+ // If the header is not on screen, just change the start of the list
1429+ // so the viewport is not changed
1430+ adjustMinYExtent();
1431+ }
1432+ }
1433+}
1434+
1435+
1436+void ListViewWithPageHeader::adjustMinYExtent()
1437+{
1438+ if (m_visibleItems.isEmpty()) {
1439+ m_minYExtent = 0;
1440+ } else {
1441+ qreal nonCreatedHeight = 0;
1442+ if (m_firstVisibleIndex != 0) {
1443+ // Calculate the average height of items to estimate the position of the list start
1444+ const int visibleItems = m_visibleItems.count();
1445+ qreal visibleItemsHeight = 0;
1446+ Q_FOREACH(ListItem *item, m_visibleItems) {
1447+ visibleItemsHeight += item->height();
1448+ }
1449+ nonCreatedHeight = m_firstVisibleIndex * visibleItemsHeight / visibleItems;
1450+// qDebug() << m_firstVisibleIndex << visibleItemsHeight << visibleItems << nonCreatedHeight;
1451+ }
1452+ m_minYExtent = nonCreatedHeight - m_visibleItems.first()->y() - m_clipItem->y() + (m_headerItem ? m_headerItem->implicitHeight() : 0);
1453+ }
1454+}
1455+
1456+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
1457+{
1458+ const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
1459+ if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
1460+ return m_visibleItems[visibleIndexedModelIndex];
1461+
1462+ return nullptr;
1463+}
1464+
1465+void ListViewWithPageHeader::layout()
1466+{
1467+ if (m_inLayout)
1468+ return;
1469+
1470+ m_inLayout = true;
1471+ if (!m_visibleItems.isEmpty()) {
1472+ const qreal visibleFrom = contentY() - m_clipItem->y() + m_headerItemShownHeight;
1473+ const qreal visibleTo = contentY() + height() - m_clipItem->y();
1474+
1475+ qreal pos = m_visibleItems.first()->y();
1476+
1477+// qDebug() << "ListViewWithPageHeader::layout Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
1478+ int firstReallyVisibleItem = -1;
1479+ int modelIndex = m_firstVisibleIndex;
1480+ Q_FOREACH(ListItem *item, m_visibleItems) {
1481+ const bool cull = pos + item->height() < visibleFrom || pos >= visibleTo;
1482+ item->setCulled(cull);
1483+ item->setY(pos);
1484+ if (!cull && firstReallyVisibleItem == -1) {
1485+ firstReallyVisibleItem = modelIndex;
1486+ if (m_topSectionItem) {
1487+ // Update the top sticky section header
1488+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
1489+ QQmlContext *context = QQmlEngine::contextForObject(m_topSectionItem)->parentContext();
1490+ context->setContextProperty(QLatin1String("section"), section);
1491+
1492+ // Positing the top section sticky item is a two step process
1493+ // First we set it either we cull it (because it doesn't need to be sticked to the top)
1494+ // or stick it to the top
1495+ // Then after the loop we'll make sure that if there's another section just below it
1496+ // pushed the sticky section up to make it disappear
1497+ const qreal topSectionStickPos = m_headerItemShownHeight + contentY() - m_clipItem->y();
1498+ if (topSectionStickPos <= pos) {
1499+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
1500+ if (item->m_sectionItem) {
1501+ // This seems it should happen since why would we cull the top section
1502+ // if the first visible item has no section header? This only happens briefly
1503+ // when increasing the height of a list that is at the bottom, the m_topSectionItem
1504+ // gets shown shortly in the next polish call
1505+ QQuickItemPrivate::get(item->m_sectionItem)->setCulled(false);
1506+ }
1507+ } else {
1508+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(false);
1509+ m_topSectionItem->setY(topSectionStickPos);
1510+ if (item->m_sectionItem) {
1511+ QQuickItemPrivate::get(item->m_sectionItem)->setCulled(true);
1512+ }
1513+ }
1514+ }
1515+ }
1516+ QQmlContext *context = QQmlEngine::contextForObject(item->m_item)->parentContext();
1517+ const qreal clipFrom = visibleFrom + (!item->m_sectionItem && m_topSectionItem && !QQuickItemPrivate::get(m_topSectionItem)->culled ? m_topSectionItem->height() : 0);
1518+ if (!cull && pos < clipFrom) {
1519+ context->setContextProperty(QLatin1String("heightToClip"), clipFrom - pos);
1520+ } else {
1521+ context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
1522+ }
1523+// qDebug() << "ListViewWithPageHeader::layout" << item->m_item;
1524+ pos += item->height();
1525+ ++modelIndex;
1526+ }
1527+
1528+ // Second step of section sticky item positioning
1529+ // Look at the next section header, check if it's pushing up the sticky one
1530+ if (m_topSectionItem) {
1531+ if (firstReallyVisibleItem >= 0) {
1532+ for (int i = firstReallyVisibleItem - m_firstVisibleIndex + 1; i < m_visibleItems.count(); ++i) {
1533+ ListItem *item = m_visibleItems[i];
1534+ if (item->m_sectionItem) {
1535+ if (m_topSectionItem->y() + m_topSectionItem->height() > item->y()) {
1536+ m_topSectionItem->setY(item->y() - m_topSectionItem->height());
1537+ }
1538+ break;
1539+ }
1540+ }
1541+ }
1542+ }
1543+ }
1544+ m_inLayout = false;
1545+}
1546+
1547+void ListViewWithPageHeader::updatePolish()
1548+{
1549+ Q_FOREACH(ListItem *item, m_itemsToRelease)
1550+ reallyReleaseItem(item);
1551+ m_itemsToRelease.clear();
1552+
1553+ if (!model())
1554+ return;
1555+
1556+ layout();
1557+
1558+ refill();
1559+
1560+ if (m_contentHeightDirty) {
1561+ qreal contentHeight;
1562+ if (m_visibleItems.isEmpty()) {
1563+ contentHeight = m_headerItem ? m_headerItem->height() : 0;
1564+ } else {
1565+ const int modelCount = model()->rowCount();
1566+ const int visibleItems = m_visibleItems.count();
1567+ const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
1568+ qreal nonCreatedHeight = 0;
1569+ if (lastValidIndex != modelCount - 1) {
1570+ const int visibleItems = m_visibleItems.count();
1571+ qreal visibleItemsHeight = 0;
1572+ Q_FOREACH(ListItem *item, m_visibleItems) {
1573+ visibleItemsHeight += item->height();
1574+ }
1575+ const int unknownSizes = modelCount - (m_firstVisibleIndex + visibleItems);
1576+ nonCreatedHeight = unknownSizes * visibleItemsHeight / visibleItems;
1577+ }
1578+ ListItem *item = m_visibleItems.last();
1579+ contentHeight = nonCreatedHeight + item->y() + item->height() + m_clipItem->y();
1580+ if (m_firstVisibleIndex != 0) {
1581+ // Make sure that if we are shrinking we tell the view we still fit
1582+ m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
1583+ }
1584+ }
1585+
1586+ m_contentHeightDirty = false;
1587+ setContentHeight(contentHeight);
1588+ }
1589+}
1590+
1591+#include "moc_listviewwithpageheader.cpp"
1592
1593=== added file 'plugins/ListViewWithPageHeader/listviewwithpageheader.h'
1594--- plugins/ListViewWithPageHeader/listviewwithpageheader.h 1970-01-01 00:00:00 +0000
1595+++ plugins/ListViewWithPageHeader/listviewwithpageheader.h 2013-07-01 11:23:32 +0000
1596@@ -0,0 +1,180 @@
1597+/*
1598+ * Copyright (C) 2013 Canonical, Ltd.
1599+ *
1600+ * This program is free software; you can redistribute it and/or modify
1601+ * it under the terms of the GNU General Public License as published by
1602+ * the Free Software Foundation; version 3.
1603+ *
1604+ * This program is distributed in the hope that it will be useful,
1605+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1606+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1607+ * GNU General Public License for more details.
1608+ *
1609+ * You should have received a copy of the GNU General Public License
1610+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1611+ */
1612+
1613+#ifndef LISTVIEWWITHPAGEHEADER_H
1614+#define LISTVIEWWITHPAGEHEADER_H
1615+
1616+#include <private/qquickitemchangelistener_p.h>
1617+#include <private/qquickflickable_p.h>
1618+
1619+class QAbstractItemModel;
1620+class QQuickChangeSet;
1621+class QQuickNumberAnimation;
1622+class QQuickVisualDataModel;
1623+
1624+
1625+/**
1626+ Note for users of this class
1627+
1628+ ListViewWithPageHeader already loads delegates async when appropiate so if
1629+ your delegate uses a Loader you should not enable the asynchronous feature since
1630+ that will need to introduce sizing problems
1631+
1632+ With the double async it may happen what while we are scrolling down
1633+ 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)
1634+ we are already "at the end" of the list, but then a few milliseconds later the loader finishes loading and we could
1635+ have kept scrolling. This is specially visible at the end of the list where you realize
1636+ that scrolling ended a bit before the end of the list but the speed of the flicking was good
1637+ to reach the end
1638+
1639+ By not having the second async we get a better sizing when the delegate is created and things work better
1640+*/
1641+
1642+class ListViewWithPageHeader : public QQuickFlickable, public QQuickItemChangeListener
1643+{
1644+ Q_OBJECT
1645+ Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
1646+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
1647+ Q_PROPERTY(QQuickItem *pageHeader READ header WRITE setHeader NOTIFY headerChanged)
1648+ Q_PROPERTY(QQmlComponent *sectionDelegate READ sectionDelegate WRITE setSectionDelegate NOTIFY sectionDelegateChanged)
1649+ Q_PROPERTY(QString sectionProperty READ sectionProperty WRITE setSectionProperty NOTIFY sectionPropertyChanged)
1650+ Q_PROPERTY(bool forceNoClip READ forceNoClip WRITE setForceNoClip NOTIFY forceNoClipChanged)
1651+
1652+ friend class ListViewWithPageHeaderTest;
1653+ friend class ListViewWithPageHeaderTestSection;
1654+
1655+public:
1656+ ListViewWithPageHeader();
1657+ ~ListViewWithPageHeader();
1658+
1659+ QAbstractItemModel *model() const;
1660+ void setModel(QAbstractItemModel *model);
1661+
1662+ QQmlComponent *delegate() const;
1663+ void setDelegate(QQmlComponent *delegate);
1664+
1665+ QQuickItem *header() const;
1666+ void setHeader(QQuickItem *header);
1667+
1668+ QQmlComponent *sectionDelegate() const;
1669+ void setSectionDelegate(QQmlComponent *delegate);
1670+
1671+ QString sectionProperty() const;
1672+ void setSectionProperty(const QString &property);
1673+
1674+ bool forceNoClip() const;
1675+ void setForceNoClip(bool noClip);
1676+
1677+ Q_INVOKABLE void positionAtBeginning();
1678+ Q_INVOKABLE void showHeader();
1679+
1680+Q_SIGNALS:
1681+ void modelChanged();
1682+ void delegateChanged();
1683+ void headerChanged();
1684+ void sectionDelegateChanged();
1685+ void sectionPropertyChanged();
1686+ void forceNoClipChanged();
1687+
1688+protected:
1689+ void componentComplete();
1690+ void viewportMoved(Qt::Orientations orient);
1691+ qreal minYExtent() const;
1692+ void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry);
1693+ void updatePolish();
1694+
1695+private Q_SLOTS:
1696+ void itemCreated(int modelIndex, QQuickItem *item);
1697+ void onContentHeightChanged();
1698+ void onContentWidthChanged();
1699+ void onHeightChanged();
1700+ void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
1701+ void onShowHeaderAnimationFinished();
1702+
1703+private:
1704+ class ListItem
1705+ {
1706+ public:
1707+ qreal height() const;
1708+
1709+ qreal y() const;
1710+ void setY(qreal newY);
1711+
1712+ bool culled() const;
1713+ void setCulled(bool culled);
1714+
1715+ QQuickItem *m_item;
1716+ QQuickItem *m_sectionItem;
1717+ };
1718+
1719+ void createDelegateModel();
1720+
1721+ void layout();
1722+ void refill();
1723+ bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
1724+ bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
1725+ ListItem *createItem(int modelIndex, bool asynchronous);
1726+
1727+ void adjustMinYExtent();
1728+ void updateClipItem();
1729+ void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
1730+ ListItem *itemAtIndex(int modelIndex) const; // Returns the item at modelIndex if has been created
1731+ void releaseItem(ListItem *item);
1732+ void reallyReleaseItem(ListItem *item);
1733+ void updateWatchedRoles();
1734+ QQuickItem *getSectionItem(int modelIndex, bool alreadyInserted);
1735+ QQuickItem *getSectionItem(const QString &sectionText);
1736+
1737+ QQuickVisualDataModel *m_delegateModel;
1738+
1739+ // Index we are waiting because we requested it asynchronously
1740+ int m_asyncRequestedIndex;
1741+
1742+ // Used to only give a warning once if the delegate does not return objects
1743+ bool m_delegateValidated;
1744+
1745+ // Visible indexes, [0] is m_firstValidIndex, [0+1] is m_firstValidIndex +1, ...
1746+ QList<ListItem *> m_visibleItems;
1747+ int m_firstVisibleIndex;
1748+
1749+ qreal m_minYExtent;
1750+
1751+ QQuickItem *m_clipItem;
1752+
1753+ // If any of the heights has changed
1754+ // or new items have been added/removed
1755+ bool m_contentHeightDirty;
1756+
1757+ QQuickItem *m_headerItem;
1758+ qreal m_previousContentY;
1759+ qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
1760+ // i.e. it's being shown after dragging down in the middle of the list
1761+ QQuickNumberAnimation *m_headerShowAnimation;
1762+
1763+ QQmlComponent *m_sectionDelegate;
1764+ QString m_sectionProperty;
1765+ QQuickItem *m_topSectionItem;
1766+
1767+ bool m_forceNoClip;
1768+ bool m_inLayout;
1769+
1770+ // Qt 5.0 doesn't like releasing the items just after itemCreated
1771+ // so we delay the releasing until the next updatePolish
1772+ QList<ListItem *> m_itemsToRelease;
1773+};
1774+
1775+
1776+#endif
1777
1778=== added file 'plugins/ListViewWithPageHeader/plugin.cpp'
1779--- plugins/ListViewWithPageHeader/plugin.cpp 1970-01-01 00:00:00 +0000
1780+++ plugins/ListViewWithPageHeader/plugin.cpp 2013-07-01 11:23:32 +0000
1781@@ -0,0 +1,29 @@
1782+/*
1783+ * Copyright (C) 2013 Canonical, Ltd.
1784+ *
1785+ * This program is free software; you can redistribute it and/or modify
1786+ * it under the terms of the GNU General Public License as published by
1787+ * the Free Software Foundation; version 3.
1788+ *
1789+ * This program is distributed in the hope that it will be useful,
1790+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1791+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1792+ * GNU General Public License for more details.
1793+ *
1794+ * You should have received a copy of the GNU General Public License
1795+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1796+ *
1797+ */
1798+
1799+#include "plugin.h"
1800+
1801+#include "listviewwithpageheader.h"
1802+
1803+#include <QAbstractItemModel>
1804+
1805+void ListViewWithPageHeaderPlugin::registerTypes(const char *uri)
1806+{
1807+ Q_ASSERT(uri == QLatin1String("ListViewWithPageHeader"));
1808+ qmlRegisterType<QAbstractItemModel>();
1809+ qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
1810+}
1811
1812=== added file 'plugins/ListViewWithPageHeader/plugin.h'
1813--- plugins/ListViewWithPageHeader/plugin.h 1970-01-01 00:00:00 +0000
1814+++ plugins/ListViewWithPageHeader/plugin.h 2013-07-01 11:23:32 +0000
1815@@ -0,0 +1,33 @@
1816+/*
1817+ * Copyright (C) 2012 Canonical, Ltd.
1818+ *
1819+ * This program is free software; you can redistribute it and/or modify
1820+ * it under the terms of the GNU General Public License as published by
1821+ * the Free Software Foundation; version 3.
1822+ *
1823+ * This program is distributed in the hope that it will be useful,
1824+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1825+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1826+ * GNU General Public License for more details.
1827+ *
1828+ * You should have received a copy of the GNU General Public License
1829+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1830+ *
1831+ */
1832+
1833+#ifndef LISTVIEWWITHPAGEHEADER_PLUGIN_H
1834+#define LISTVIEWWITHPAGEHEADER_PLUGIN_H
1835+
1836+#include <QtQml/QQmlEngine>
1837+#include <QtQml/QQmlExtensionPlugin>
1838+
1839+class ListViewWithPageHeaderPlugin : public QQmlExtensionPlugin
1840+{
1841+ Q_OBJECT
1842+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
1843+
1844+public:
1845+ void registerTypes(const char *uri);
1846+};
1847+
1848+#endif
1849
1850=== added file 'plugins/ListViewWithPageHeader/qmldir'
1851--- plugins/ListViewWithPageHeader/qmldir 1970-01-01 00:00:00 +0000
1852+++ plugins/ListViewWithPageHeader/qmldir 2013-07-01 11:23:32 +0000
1853@@ -0,0 +1,2 @@
1854+module ListViewWithPageHeader
1855+plugin ListViewWithPageHeader-qml
1856
1857=== modified file 'run_on_device'
1858--- run_on_device 2013-06-24 11:08:38 +0000
1859+++ run_on_device 2013-07-01 11:23:32 +0000
1860@@ -75,7 +75,7 @@
1861 sync_code() {
1862 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
1863 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
1864- rsync -crlOzv -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
1865+ 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/
1866 rm -rf /tmp/$CODE_DIR
1867 }
1868
1869
1870=== modified file 'tests/plugins/CMakeLists.txt'
1871--- tests/plugins/CMakeLists.txt 2013-06-05 22:03:08 +0000
1872+++ tests/plugins/CMakeLists.txt 2013-07-01 11:23:32 +0000
1873@@ -1,2 +1,3 @@
1874 add_subdirectory(Utils)
1875 add_subdirectory(Ubuntu)
1876+add_subdirectory(ListViewWithPageHeader)
1877
1878=== added directory 'tests/plugins/ListViewWithPageHeader'
1879=== added file 'tests/plugins/ListViewWithPageHeader/CMakeLists.txt'
1880--- tests/plugins/ListViewWithPageHeader/CMakeLists.txt 1970-01-01 00:00:00 +0000
1881+++ tests/plugins/ListViewWithPageHeader/CMakeLists.txt 2013-07-01 11:23:32 +0000
1882@@ -0,0 +1,31 @@
1883+# There's no cmake var for v8 include path :-/
1884+# so create one
1885+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
1886+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
1887+
1888+include_directories(
1889+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
1890+ ${Qt5Quick_INCLUDE_DIRS}
1891+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
1892+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
1893+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader
1894+ ${CMAKE_CURRENT_BINARY_DIR}
1895+ )
1896+
1897+remove_definitions(-DQT_NO_KEYWORDS)
1898+
1899+add_definitions(-DLISTVIEWWITHPAGEHEADER_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}")
1900+add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
1901+
1902+add_executable(listviewwithpageheadertestExec listviewwithpageheadertest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
1903+qt5_use_modules(listviewwithpageheadertestExec Test Core Qml)
1904+target_link_libraries(listviewwithpageheadertestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
1905+add_custom_target(testlistviewwithpageheader ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheader.xml,xunitxml -o -,txt)
1906+
1907+add_executable(listviewwithpageheadertestsectionExec listviewwithpageheadertestsection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
1908+qt5_use_modules(listviewwithpageheadertestsectionExec Test Core Qml)
1909+target_link_libraries(listviewwithpageheadertestsectionExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
1910+add_custom_target(testlistviewwithpageheadersection ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersection.xml,xunitxml -o -,txt)
1911+
1912+add_dependencies(qmluitests testlistviewwithpageheader)
1913+add_dependencies(qmluitests testlistviewwithpageheadersection)
1914
1915=== added file 'tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp'
1916--- tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp 1970-01-01 00:00:00 +0000
1917+++ tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp 2013-07-01 11:23:32 +0000
1918@@ -0,0 +1,1311 @@
1919+/*
1920+ * Copyright (C) 2013 Canonical, Ltd.
1921+ *
1922+ * This program is free software; you can redistribute it and/or modify
1923+ * it under the terms of the GNU General Public License as published by
1924+ * the Free Software Foundation; version 3.
1925+ *
1926+ * This program is distributed in the hope that it will be useful,
1927+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1928+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1929+ * GNU General Public License for more details.
1930+ *
1931+ * You should have received a copy of the GNU General Public License
1932+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1933+ */
1934+
1935+#include "listviewwithpageheader.h"
1936+
1937+#include <QAbstractItemModel>
1938+#include <QQmlEngine>
1939+#include <QQuickView>
1940+#include <QtTestGui>
1941+#include <private/qquicklistmodel_p.h>
1942+#include <private/qquickanimation_p.h>
1943+#include <private/qquickitem_p.h>
1944+
1945+class ListViewWithPageHeaderTest : public QObject
1946+{
1947+ Q_OBJECT
1948+
1949+private:
1950+ void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled)
1951+ {
1952+ ListViewWithPageHeader::ListItem *item = lvwph->m_visibleItems[visibleIndex];
1953+ QTRY_COMPARE(item->y(), pos);
1954+ QTRY_COMPARE(item->height(), height);
1955+ QCOMPARE(QQuickItemPrivate::get(item->m_item)->culled, culled);
1956+ }
1957+
1958+ void changeContentY(qreal change)
1959+ {
1960+ const qreal dest = lvwph->contentY() + change;
1961+ if (dest > lvwph->contentY()) {
1962+ const qreal jump = 25;
1963+ while (lvwph->contentY() + jump < dest) {
1964+ lvwph->setContentY(lvwph->contentY() + jump);
1965+ QTest::qWait(1);
1966+ }
1967+ } else {
1968+ const qreal jump = -25;
1969+ while (lvwph->contentY() + jump > dest) {
1970+ lvwph->setContentY(lvwph->contentY() + jump);
1971+ QTest::qWait(1);
1972+ }
1973+ }
1974+ lvwph->setContentY(dest);
1975+ QTest::qWait(1);
1976+ }
1977+
1978+ void scrollToTop()
1979+ {
1980+ const qreal jump = -25;
1981+ while (!lvwph->isAtYBeginning()) {
1982+ if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
1983+ lvwph->setContentY(lvwph->contentY() + jump);
1984+ } else {
1985+ lvwph->setContentY(lvwph->contentY() - 1);
1986+ }
1987+ QTest::qWait(1);
1988+ }
1989+ }
1990+
1991+ void scrollToBottom()
1992+ {
1993+ const qreal jump = 25;
1994+ while (!lvwph->isAtYEnd()) {
1995+ if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
1996+ lvwph->setContentY(lvwph->contentY() + jump);
1997+ } else {
1998+ lvwph->setContentY(lvwph->contentY() + 1);
1999+ }
2000+ QTest::qWait(1);
2001+ }
2002+ }
2003+
2004+ void verifyInitialTopPosition()
2005+ {
2006+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2007+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2008+ verifyItem(0, 50., 150., false);
2009+ verifyItem(1, 200., 200., false);
2010+ verifyItem(2, 400., 350., false);
2011+ verifyItem(3, 750., 350., true);
2012+ QCOMPARE(lvwph->m_minYExtent, 0.);
2013+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2014+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2015+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2016+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2017+ QCOMPARE(lvwph->contentY(), 0.);
2018+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2019+ }
2020+
2021+private Q_SLOTS:
2022+
2023+ void initTestCase()
2024+ {
2025+ }
2026+
2027+ void init()
2028+ {
2029+ view = new QQuickView();
2030+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
2031+ view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test.qml"));
2032+ lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
2033+ model = view->rootObject()->findChild<QQuickListModel*>();
2034+ otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
2035+ QVERIFY(lvwph);
2036+ QVERIFY(model);
2037+ QVERIFY(otherDelegate);
2038+ view->show();
2039+ QTest::qWaitForWindowExposed(view);
2040+
2041+ verifyInitialTopPosition();
2042+ }
2043+
2044+ void cleanup()
2045+ {
2046+ delete view;
2047+ }
2048+
2049+ void testCreationDeletion()
2050+ {
2051+ // Nothing, init/cleanup already tests this
2052+ }
2053+
2054+ void testDrag1PixelUp()
2055+ {
2056+ lvwph->setContentY(lvwph->contentY() + 1);
2057+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2058+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2059+ verifyItem(0, 49., 150., false);
2060+ verifyItem(1, 199., 200., false);
2061+ verifyItem(2, 399., 350., false);
2062+ verifyItem(3, 749., 350., true);
2063+ QCOMPARE(lvwph->m_minYExtent, 0.);
2064+ QCOMPARE(lvwph->m_clipItem->y(), 1.);
2065+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2066+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2067+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2068+ QCOMPARE(lvwph->contentY(), 1.);
2069+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2070+ }
2071+
2072+ void testHeaderDetachDragDown()
2073+ {
2074+ QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
2075+ QTest::qWait(100);
2076+ QTest::mouseMove(view, QPoint(0, 5));
2077+ QTest::qWait(100);
2078+ QTest::mouseMove(view, QPoint(0, 10));
2079+ QTest::qWait(100);
2080+ QTest::mouseMove(view, QPoint(0, 15));
2081+ QTest::qWait(100);
2082+ QTest::mouseMove(view, QPoint(0, 20));
2083+ QTest::qWait(100);
2084+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2085+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2086+ verifyItem(0, 55., 150., false);
2087+ verifyItem(1, 205., 200., false);
2088+ verifyItem(2, 405., 350., false);
2089+ verifyItem(3, 755., 350., true);
2090+ QCOMPARE(lvwph->m_minYExtent, 0.);
2091+ QCOMPARE(lvwph->m_clipItem->y(), -5.);
2092+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2093+ QCOMPARE(lvwph->m_headerItem->y(), -5.);
2094+ QCOMPARE(lvwph->m_headerItem->height(), 55.);
2095+ QCOMPARE(lvwph->contentY(), -5.);
2096+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2097+
2098+ QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
2099+
2100+ verifyInitialTopPosition();
2101+ }
2102+
2103+ void testDrag375PixelUp()
2104+ {
2105+ changeContentY(375);
2106+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2107+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2108+ verifyItem(0, -325., 150., true);
2109+ verifyItem(1, -175, 200., false);
2110+ verifyItem(2, 25, 350., false);
2111+ verifyItem(3, 375, 350., false);
2112+ verifyItem(4, 725, 350., true);
2113+ QCOMPARE(lvwph->m_minYExtent, 0.);
2114+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2115+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2116+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2117+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2118+ QCOMPARE(lvwph->contentY(), 375.);
2119+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2120+ }
2121+
2122+ void testDrag520PixelUp()
2123+ {
2124+ changeContentY(520);
2125+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2126+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2127+ verifyItem(0, -320., 200., true);
2128+ verifyItem(1, -120, 350., false);
2129+ verifyItem(2, 230, 350., false);
2130+ verifyItem(3, 580, 350., true);
2131+ // Just here as first check against m_minYExtent when m_firstVisibleIndex is not 0
2132+ // We as humans know that m_minYExtent will be 0 but since the first delegate is not there anymore
2133+ // we have to estimate its size, the average item height is 312.5 which is 162.5 more than the
2134+ // "real" size of 150 and that's why the m_minYExtent has that "peculiar" value
2135+ // It's fine since what it means is that we could scroll more up than the original position
2136+ // but we will recalculate m_minYExtent when the item 0 is created and set it correctly
2137+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2138+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2139+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2140+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2141+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2142+ QCOMPARE(lvwph->contentY(), 520.);
2143+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2144+ }
2145+
2146+ void testDragHeaderUpThenShow()
2147+ {
2148+ changeContentY(120);
2149+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2150+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2151+ verifyItem(0, -70., 150., false);
2152+ verifyItem(1, 80., 200., false);
2153+ verifyItem(2, 280., 350., false);
2154+ verifyItem(3, 630., 350., true);
2155+ QCOMPARE(lvwph->m_minYExtent, 0.);
2156+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
2157+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2158+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2159+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2160+ QCOMPARE(lvwph->contentY(), 120.);
2161+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2162+
2163+ changeContentY(-30);
2164+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2165+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2166+ verifyItem(0, -70., 150., false);
2167+ verifyItem(1, 80., 200., false);
2168+ verifyItem(2, 280., 350., false);
2169+ verifyItem(3, 630., 350., true);
2170+ QCOMPARE(lvwph->m_minYExtent, 0.);
2171+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
2172+ QCOMPARE(lvwph->m_clipItem->clip(), true);
2173+ QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
2174+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2175+ QCOMPARE(lvwph->contentY(), 90.);
2176+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
2177+ }
2178+
2179+ void testDragHeaderUpThenShowWithoutHidingTotally()
2180+ {
2181+ changeContentY(10);
2182+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2183+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2184+ verifyItem(0, 40., 150., false);
2185+ verifyItem(1, 190., 200., false);
2186+ verifyItem(2, 390., 350., false);
2187+ verifyItem(3, 740., 350., true);
2188+ QCOMPARE(lvwph->m_minYExtent, 0.);
2189+ QCOMPARE(lvwph->m_clipItem->y(), 10.);
2190+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2191+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2192+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2193+ QCOMPARE(lvwph->contentY(), 10.);
2194+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2195+
2196+ changeContentY(-1);
2197+
2198+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2199+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2200+ verifyItem(0, 41., 150., false);
2201+ verifyItem(1, 191., 200., false);
2202+ verifyItem(2, 391., 350., false);
2203+ verifyItem(3, 741., 350., true);
2204+ QCOMPARE(lvwph->m_minYExtent, 0.);
2205+ QCOMPARE(lvwph->m_clipItem->y(), 9.);
2206+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2207+ QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
2208+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2209+ QCOMPARE(lvwph->contentY(), 9.);
2210+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2211+ }
2212+
2213+ void testPositionAtBeginningIndex0Visible()
2214+ {
2215+ changeContentY(375);
2216+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2217+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2218+ verifyItem(0, -325., 150., true);
2219+ verifyItem(1, -175, 200., false);
2220+ verifyItem(2, 25, 350., false);
2221+ verifyItem(3, 375, 350., false);
2222+ verifyItem(4, 725, 350., true);
2223+ QCOMPARE(lvwph->m_minYExtent, 0.);
2224+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2225+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2226+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2227+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2228+ QCOMPARE(lvwph->contentY(), 375.);
2229+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2230+
2231+ lvwph->positionAtBeginning();
2232+
2233+ verifyInitialTopPosition();
2234+ }
2235+
2236+ void testPositionAtBeginningIndex0NotVisible()
2237+ {
2238+ changeContentY(520);
2239+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2240+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2241+ verifyItem(0, -320., 200., true);
2242+ verifyItem(1, -120, 350., false);
2243+ verifyItem(2, 230, 350., false);
2244+ verifyItem(3, 580, 350., true);
2245+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2246+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2247+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2248+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2249+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2250+ QCOMPARE(lvwph->contentY(), 520.);
2251+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2252+
2253+ lvwph->positionAtBeginning();
2254+
2255+ verifyInitialTopPosition();
2256+ }
2257+
2258+ void testIndex0GrowOnScreen()
2259+ {
2260+ model->setProperty(0, "size", 400);
2261+
2262+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2263+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2264+ verifyItem(0, 50., 400., false);
2265+ verifyItem(1, 450., 200., false);
2266+ verifyItem(2, 650., 350., true);
2267+ QCOMPARE(lvwph->m_minYExtent, 0.);
2268+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2269+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2270+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2271+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2272+ QCOMPARE(lvwph->contentY(), 0.);
2273+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2274+ }
2275+
2276+ void testIndex0GrowOffScreen()
2277+ {
2278+ changeContentY(375);
2279+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2280+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2281+ verifyItem(0, -325., 150., true);
2282+ verifyItem(1, -175, 200., false);
2283+ verifyItem(2, 25, 350., false);
2284+ verifyItem(3, 375, 350., false);
2285+ verifyItem(4, 725, 350., true);
2286+ QCOMPARE(lvwph->m_minYExtent, 0.);
2287+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2288+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2289+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2290+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2291+ QCOMPARE(lvwph->contentY(), 375.);
2292+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2293+
2294+ model->setProperty(0, "size", 400);
2295+
2296+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2297+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2298+ verifyItem(0, -575., 400., true);
2299+ verifyItem(1, -175, 200., false);
2300+ verifyItem(2, 25, 350., false);
2301+ verifyItem(3, 375, 350., false);
2302+ verifyItem(4, 725, 350., true);
2303+ QCOMPARE(lvwph->m_minYExtent, 250.);
2304+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2305+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2306+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2307+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2308+ QCOMPARE(lvwph->contentY(), 375.);
2309+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2310+
2311+ scrollToTop();
2312+
2313+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2314+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2315+ verifyItem(0, 50., 400., false);
2316+ verifyItem(1, 450, 200., false);
2317+ verifyItem(2, 650, 350., true);
2318+ QCOMPARE(lvwph->m_minYExtent, 250.);
2319+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
2320+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2321+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
2322+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2323+ QCOMPARE(lvwph->contentY(), -250.);
2324+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2325+
2326+ changeContentY(30);
2327+
2328+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2329+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2330+ verifyItem(0, 20., 400., false);
2331+ verifyItem(1, 420, 200., false);
2332+ verifyItem(2, 620, 350., true);
2333+ QCOMPARE(lvwph->m_minYExtent, 250.);
2334+ QCOMPARE(lvwph->m_clipItem->y(), -220.);
2335+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2336+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
2337+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2338+ QCOMPARE(lvwph->contentY(), -220.);
2339+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2340+ }
2341+
2342+ void testIndex0GrowNotCreated()
2343+ {
2344+ changeContentY(520);
2345+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2346+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2347+ verifyItem(0, -320., 200., true);
2348+ verifyItem(1, -120, 350., false);
2349+ verifyItem(2, 230, 350., false);
2350+ verifyItem(3, 580, 350., true);
2351+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2352+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2353+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2354+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2355+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2356+ QCOMPARE(lvwph->contentY(), 520.);
2357+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2358+
2359+ model->setProperty(0, "size", 400);
2360+
2361+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2362+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2363+ verifyItem(0, -320., 200., true);
2364+ verifyItem(1, -120, 350., false);
2365+ verifyItem(2, 230, 350., false);
2366+ verifyItem(3, 580, 350., true);
2367+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2368+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2369+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2370+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2371+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2372+ QCOMPARE(lvwph->contentY(), 520.);
2373+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2374+
2375+ scrollToTop();
2376+
2377+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2378+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2379+ verifyItem(0, 50., 400., false);
2380+ verifyItem(1, 450, 200., false);
2381+ verifyItem(2, 650, 350., true);
2382+ QCOMPARE(lvwph->m_minYExtent, 250.);
2383+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
2384+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2385+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
2386+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2387+ QCOMPARE(lvwph->contentY(), -250.);
2388+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2389+ }
2390+
2391+ void testShowHideShowHeaderAtBottom()
2392+ {
2393+ scrollToBottom();
2394+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2395+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2396+ verifyItem(0, -508., 350., true);
2397+ verifyItem(1, -158, 350., false);
2398+ verifyItem(2, 192, 350., false);
2399+ QCOMPARE(lvwph->m_minYExtent, 350.);
2400+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2401+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2402+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2403+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2404+ QCOMPARE(lvwph->contentY(), 1258.);
2405+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2406+
2407+ changeContentY(-30);
2408+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2409+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2410+ verifyItem(0, -508., 350., true);
2411+ verifyItem(1, -158, 350., false);
2412+ verifyItem(2, 192, 350., false);
2413+ QCOMPARE(lvwph->m_minYExtent, 350.);
2414+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2415+ QCOMPARE(lvwph->m_clipItem->clip(), true);
2416+ QCOMPARE(lvwph->m_headerItem->y(), 1208.);
2417+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2418+ QCOMPARE(lvwph->contentY(), 1228.);
2419+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
2420+
2421+ changeContentY(30);
2422+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2423+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2424+ verifyItem(0, -508., 350., true);
2425+ verifyItem(1, -158, 350., false);
2426+ verifyItem(2, 192, 350., false);
2427+ QCOMPARE(lvwph->m_minYExtent, 350.);
2428+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2429+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2430+ QCOMPARE(lvwph->m_headerItem->y(), -350.);
2431+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2432+ QCOMPARE(lvwph->contentY(), 1258.);
2433+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2434+
2435+ changeContentY(-30);
2436+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2437+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2438+ verifyItem(0, -508., 350., true);
2439+ verifyItem(1, -158, 350., false);
2440+ verifyItem(2, 192, 350., false);
2441+ QCOMPARE(lvwph->m_minYExtent, 350.);
2442+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2443+ QCOMPARE(lvwph->m_clipItem->clip(), true);
2444+ QCOMPARE(lvwph->m_headerItem->y(), 1208.);
2445+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2446+ QCOMPARE(lvwph->contentY(), 1228.);
2447+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
2448+ }
2449+
2450+ void testChangeDelegateAtBottom()
2451+ {
2452+ scrollToBottom();
2453+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2454+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2455+ verifyItem(0, -508., 350., true);
2456+ verifyItem(1, -158, 350., false);
2457+ verifyItem(2, 192, 350., false);
2458+ QCOMPARE(lvwph->m_minYExtent, 350.);
2459+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2460+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2461+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2462+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2463+ QCOMPARE(lvwph->contentY(), 1258.);
2464+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2465+
2466+ lvwph->setDelegate(otherDelegate);
2467+
2468+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
2469+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2470+ verifyItem(0, 50., 35., false);
2471+ verifyItem(1, 85, 35., false);
2472+ verifyItem(2, 120, 35., false);
2473+ verifyItem(3, 155, 35., false);
2474+ verifyItem(4, 190, 35., false);
2475+ verifyItem(5, 225, 35., false);
2476+ QCOMPARE(lvwph->m_minYExtent, 0.);
2477+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2478+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2479+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2480+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2481+ QCOMPARE(lvwph->contentY(), 0.);
2482+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2483+ }
2484+
2485+ void testSetEmptyHeaderAtTop()
2486+ {
2487+ lvwph->setHeader(nullptr);
2488+
2489+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2490+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2491+ verifyItem(0, 0., 150., false);
2492+ verifyItem(1, 150., 200., false);
2493+ verifyItem(2, 350., 350., false);
2494+ verifyItem(3, 700., 350., true);
2495+ QCOMPARE(lvwph->m_minYExtent, 0.);
2496+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2497+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2498+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2499+ QCOMPARE(lvwph->contentY(), 0.);
2500+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2501+ }
2502+
2503+ void testSetEmptyHeaderAtBottom()
2504+ {
2505+ scrollToBottom();
2506+ lvwph->setHeader(nullptr);
2507+
2508+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2509+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2510+ verifyItem(0, -508., 350., true);
2511+ verifyItem(1, -158, 350., false);
2512+ verifyItem(2, 192, 350., false);
2513+ QCOMPARE(lvwph->m_minYExtent, 300.);
2514+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2515+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2516+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2517+ QCOMPARE(lvwph->contentY(), 1258.);
2518+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2519+
2520+ scrollToTop();
2521+
2522+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2523+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2524+ verifyItem(0, 0., 150., false);
2525+ verifyItem(1, 150., 200., false);
2526+ verifyItem(2, 350., 350., false);
2527+ verifyItem(3, 700., 350., true);
2528+ QCOMPARE(lvwph->m_minYExtent, -50.);
2529+ QCOMPARE(lvwph->m_clipItem->y(), 50.);
2530+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2531+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2532+ QCOMPARE(lvwph->contentY(), 50.);
2533+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2534+ }
2535+
2536+ void testSetEmptyHeaderWhenPartlyShownClipped()
2537+ {
2538+ scrollToBottom();
2539+ changeContentY(-30);
2540+ lvwph->setHeader(nullptr);
2541+
2542+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2543+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2544+ verifyItem(0, -508., 350., true);
2545+ verifyItem(1, -158, 350., false);
2546+ verifyItem(2, 192, 350., false);
2547+ QCOMPARE(lvwph->m_minYExtent, 330.);
2548+ QCOMPARE(lvwph->m_clipItem->y(), 1228.);
2549+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2550+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2551+ QCOMPARE(lvwph->contentY(), 1228.);
2552+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2553+ QTRY_VERIFY(lvwph->isAtYEnd());
2554+
2555+ scrollToTop();
2556+
2557+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2558+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2559+ verifyItem(0, 0., 150., false);
2560+ verifyItem(1, 150., 200., false);
2561+ verifyItem(2, 350., 350., false);
2562+ verifyItem(3, 700., 350., true);
2563+ QCOMPARE(lvwph->m_minYExtent, -20.);
2564+ QCOMPARE(lvwph->m_clipItem->y(), 20.);
2565+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2566+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2567+ QCOMPARE(lvwph->contentY(), 20.);
2568+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2569+ }
2570+
2571+ void testSetEmptyHeaderWhenPartlyShownNotClipped()
2572+ {
2573+ changeContentY(30);
2574+ lvwph->setHeader(nullptr);
2575+
2576+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2577+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2578+ verifyItem(0, -30., 150., false);
2579+ verifyItem(1, 120., 200., false);
2580+ verifyItem(2, 320., 350., false);
2581+ verifyItem(3, 670., 350., true);
2582+ QCOMPARE(lvwph->m_minYExtent, 0.);
2583+ QCOMPARE(lvwph->m_clipItem->y(), 30.);
2584+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2585+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
2586+ QCOMPARE(lvwph->contentY(), 30.);
2587+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2588+ }
2589+
2590+ void testSetNullDelegate()
2591+ {
2592+ lvwph->setDelegate(nullptr);
2593+
2594+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
2595+ QCOMPARE(lvwph->m_firstVisibleIndex, -1);
2596+ QCOMPARE(lvwph->m_minYExtent, 0.);
2597+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2598+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2599+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2600+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2601+ QCOMPARE(lvwph->contentY(), 0.);
2602+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2603+ QTRY_COMPARE(lvwph->contentHeight(), 50.);
2604+ QVERIFY(lvwph->isAtYBeginning());
2605+ QVERIFY(lvwph->isAtYEnd());
2606+ }
2607+
2608+ void testInsertItems()
2609+ {
2610+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
2611+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
2612+
2613+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2614+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2615+ verifyItem(0, 50., 150., false);
2616+ verifyItem(1, 200., 125., false);
2617+ verifyItem(2, 325., 100., false);
2618+ verifyItem(3, 425., 200., false);
2619+ verifyItem(4, 625., 350., true);
2620+ QCOMPARE(lvwph->m_minYExtent, 0.);
2621+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2622+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2623+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2624+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2625+ QCOMPARE(lvwph->contentY(), 0.);
2626+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2627+ }
2628+
2629+ void testInsertItemsOnNotShownPosition()
2630+ {
2631+ changeContentY(700);
2632+
2633+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2634+ QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
2635+ verifyItem(0, -300., 350., false);
2636+ verifyItem(1, 50, 350., false);
2637+ verifyItem(2, 400, 350., false);
2638+ verifyItem(3, 750, 350., true);
2639+ QCOMPARE(lvwph->m_minYExtent, 350.);
2640+ QCOMPARE(lvwph->m_clipItem->y(), 700.);
2641+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2642+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2643+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2644+ QCOMPARE(lvwph->contentY(), 700.);
2645+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2646+
2647+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
2648+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
2649+
2650+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2651+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
2652+ verifyItem(0, -300., 350., false);
2653+ verifyItem(1, 50, 350., false);
2654+ verifyItem(2, 400, 350., false);
2655+ verifyItem(3, 750, 350., true);
2656+ QCOMPARE(lvwph->m_minYExtent, 1050.);
2657+ QCOMPARE(lvwph->m_clipItem->y(), 700.);
2658+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2659+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2660+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2661+ QCOMPARE(lvwph->contentY(), 700.);
2662+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2663+
2664+ scrollToTop();
2665+
2666+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2667+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2668+ verifyItem(0, 50., 150., false);
2669+ verifyItem(1, 200., 125., false);
2670+ verifyItem(2, 325., 100., false);
2671+ verifyItem(3, 425., 200., false);
2672+ verifyItem(4, 625., 350., true);
2673+ QCOMPARE(lvwph->m_minYExtent, 225.);
2674+ QCOMPARE(lvwph->m_clipItem->y(), -225.);
2675+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2676+ QCOMPARE(lvwph->m_headerItem->y(), -225.);
2677+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2678+ QCOMPARE(lvwph->contentY(), -225.);
2679+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2680+ }
2681+
2682+ void testInsertItemsAtEndOfViewport()
2683+ {
2684+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100));
2685+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125));
2686+
2687+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2688+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2689+ verifyItem(0, 50., 150., false);
2690+ verifyItem(1, 200., 200., false);
2691+ verifyItem(2, 400., 350., false);
2692+ verifyItem(3, 750., 125., true);
2693+ QCOMPARE(lvwph->m_minYExtent, 0.);
2694+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2695+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2696+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2697+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2698+ QCOMPARE(lvwph->contentY(), 0.);
2699+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2700+ }
2701+
2702+ void testInsertItemsBeforeValidIndex()
2703+ {
2704+ changeContentY(520);
2705+
2706+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2707+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2708+ verifyItem(0, -320., 200., true);
2709+ verifyItem(1, -120, 350., false);
2710+ verifyItem(2, 230, 350., false);
2711+ verifyItem(3, 580, 350., true);
2712+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2713+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2714+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2715+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2716+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2717+ QCOMPARE(lvwph->contentY(), 520.);
2718+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2719+
2720+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
2721+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
2722+
2723+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2724+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2725+ verifyItem(0, -320., 200., true);
2726+ verifyItem(1, -120, 350., false);
2727+ verifyItem(2, 230, 350., false);
2728+ verifyItem(3, 580, 350., true);
2729+ QCOMPARE(lvwph->m_minYExtent, 787.5);
2730+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2731+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2732+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2733+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2734+ QCOMPARE(lvwph->contentY(), 520.);
2735+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2736+ }
2737+
2738+ void testInsertItemsBeforeViewport()
2739+ {
2740+ changeContentY(375);
2741+
2742+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2743+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2744+ verifyItem(0, -325., 150., true);
2745+ verifyItem(1, -175, 200., false);
2746+ verifyItem(2, 25, 350., false);
2747+ verifyItem(3, 375, 350., false);
2748+ verifyItem(4, 725, 350., true);
2749+ QCOMPARE(lvwph->m_minYExtent, 0.);
2750+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2751+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2752+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2753+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2754+ QCOMPARE(lvwph->contentY(), 375.);
2755+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2756+
2757+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
2758+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
2759+
2760+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2761+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
2762+ verifyItem(0, -275., 100., true);
2763+ verifyItem(1, -175, 200., false);
2764+ verifyItem(2, 25, 350., false);
2765+ verifyItem(3, 375, 350., false);
2766+ verifyItem(4, 725, 350., true);
2767+ QCOMPARE(lvwph->m_minYExtent, 490.);
2768+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
2769+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2770+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2771+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2772+ QCOMPARE(lvwph->contentY(), 375.);
2773+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2774+
2775+ scrollToTop();
2776+
2777+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2778+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2779+ verifyItem(0, 50., 150., false);
2780+ verifyItem(1, 200., 125., false);
2781+ verifyItem(2, 325., 100., false);
2782+ verifyItem(3, 425., 200., false);
2783+ verifyItem(4, 625., 350., true);
2784+ QCOMPARE(lvwph->m_minYExtent, 225.);
2785+ QCOMPARE(lvwph->m_clipItem->y(), -225.);
2786+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2787+ QCOMPARE(lvwph->m_headerItem->y(), -225.);
2788+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2789+ QCOMPARE(lvwph->contentY(), -225.);
2790+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2791+ }
2792+
2793+ void testInsertItemsAtBottom()
2794+ {
2795+ scrollToBottom();
2796+
2797+ QVERIFY(lvwph->isAtYEnd());
2798+
2799+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100));
2800+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125));
2801+
2802+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
2803+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
2804+ verifyItem(0, -508., 350., true);
2805+ verifyItem(1, -158, 350., false);
2806+ verifyItem(2, 192, 350., false);
2807+ verifyItem(3, 542, 125., true);
2808+ verifyItem(4, 667, 100., true);
2809+ QCOMPARE(lvwph->m_minYExtent, 65.);
2810+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2811+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2812+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2813+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2814+ QCOMPARE(lvwph->contentY(), 1258.);
2815+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2816+ QVERIFY(!lvwph->isAtYEnd());
2817+
2818+ scrollToBottom();
2819+
2820+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2821+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
2822+ verifyItem(0, -383., 350., true);
2823+ verifyItem(1, -33, 350., false);
2824+ verifyItem(2, 317, 125., false);
2825+ verifyItem(3, 442, 100., false);
2826+ QCOMPARE(lvwph->m_minYExtent, -125.);
2827+ QCOMPARE(lvwph->m_clipItem->y(), 1483.);
2828+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2829+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2830+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2831+ QCOMPARE(lvwph->contentY(), 1483.);
2832+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2833+ QVERIFY(lvwph->isAtYEnd());
2834+ }
2835+
2836+ void testInsertItemAtTop()
2837+ {
2838+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2839+
2840+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2841+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2842+ verifyItem(0, 50., 75., false);
2843+ verifyItem(1, 125., 150., false);
2844+ verifyItem(2, 275., 200., false);
2845+ verifyItem(3, 475., 350., false);
2846+ QCOMPARE(lvwph->m_minYExtent, 0.);
2847+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2848+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2849+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2850+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2851+ QCOMPARE(lvwph->contentY(), 0.);
2852+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2853+ QVERIFY(lvwph->isAtYBeginning());
2854+ }
2855+
2856+ void testInsertItem10SmallItemsAtTopWhenAtBottom()
2857+ {
2858+ scrollToBottom();
2859+
2860+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2861+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2862+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2863+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2864+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2865+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2866+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2867+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2868+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2869+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
2870+
2871+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2872+ QCOMPARE(lvwph->m_firstVisibleIndex, 13);
2873+ verifyItem(0, -508., 350., true);
2874+ verifyItem(1, -158, 350., false);
2875+ verifyItem(2, 192, 350., false);
2876+ QCOMPARE(lvwph->m_minYExtent, 3850.);
2877+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2878+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2879+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2880+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2881+ QCOMPARE(lvwph->contentY(), 1258.);
2882+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2883+
2884+ changeContentY(-1700);
2885+
2886+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 12);
2887+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2888+ verifyItem(0, -308., 75., true);
2889+ verifyItem(1, -233., 75., true);
2890+ verifyItem(2, -158., 75., true);
2891+ verifyItem(3, -83., 75., true);
2892+ verifyItem(4, -8., 75., false);
2893+ verifyItem(5, 67., 75., false);
2894+ verifyItem(6, 142., 75., false);
2895+ verifyItem(7, 217., 75., false);
2896+ verifyItem(8, 292., 75., false);
2897+ verifyItem(9, 367., 75., false);
2898+ verifyItem(10, 442., 150., false);
2899+ verifyItem(11, 592., 200., true);
2900+ QCOMPARE(lvwph->m_minYExtent, 750.);
2901+ QCOMPARE(lvwph->m_clipItem->y(), -392.);
2902+ QCOMPARE(lvwph->m_clipItem->clip(), true);
2903+ QCOMPARE(lvwph->m_headerItem->y(), -442.);
2904+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2905+ QCOMPARE(lvwph->contentY(), -442.);
2906+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
2907+ }
2908+
2909+ void testRemoveItemsAtTop()
2910+ {
2911+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
2912+
2913+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2914+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2915+ verifyItem(0, 50., 350., false);
2916+ verifyItem(1, 400., 350., false);
2917+ verifyItem(2, 750., 350., true);
2918+ QCOMPARE(lvwph->m_minYExtent, 0.);
2919+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2920+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2921+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2922+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2923+ QCOMPARE(lvwph->contentY(), 0.);
2924+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2925+ QVERIFY(lvwph->isAtYBeginning());
2926+ }
2927+
2928+ void testRemoveNonCreatedItemsAtTopWhenAtBottom()
2929+ {
2930+ scrollToBottom();
2931+
2932+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
2933+
2934+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2935+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2936+ verifyItem(0, -508., 350., true);
2937+ verifyItem(1, -158, 350., false);
2938+ verifyItem(2, 192, 350., false);
2939+ QCOMPARE(lvwph->m_minYExtent, -350.);
2940+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
2941+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2942+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2943+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2944+ QCOMPARE(lvwph->contentY(), 1258.);
2945+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2946+ }
2947+
2948+ void testRemoveLastItemsAtBottom()
2949+ {
2950+ scrollToBottom();
2951+
2952+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
2953+
2954+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2955+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2956+ verifyItem(0, -358., 200., true);
2957+ verifyItem(1, -158, 350., false);
2958+ verifyItem(2, 192, 350., false);
2959+ QCOMPARE(lvwph->m_minYExtent, 150.);
2960+ QCOMPARE(lvwph->m_clipItem->y(), 558.);
2961+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2962+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2963+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2964+ QCOMPARE(lvwph->contentY(), 558.);
2965+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2966+ }
2967+
2968+ void testRemoveItemOutOfViewport()
2969+ {
2970+ changeContentY(520);
2971+
2972+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2973+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
2974+ verifyItem(0, -320., 200., true);
2975+ verifyItem(1, -120, 350., false);
2976+ verifyItem(2, 230, 350., false);
2977+ verifyItem(3, 580, 350., true);
2978+ QCOMPARE(lvwph->m_minYExtent, 162.5);
2979+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2980+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2981+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2982+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2983+ QCOMPARE(lvwph->contentY(), 520.);
2984+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2985+
2986+
2987+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
2988+
2989+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
2990+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2991+ verifyItem(0, -270., 150., true);
2992+ verifyItem(1, -120., 350., false);
2993+ verifyItem(2, 230, 350., false);
2994+ verifyItem(3, 580, 350., true);
2995+ QCOMPARE(lvwph->m_minYExtent, -200.);
2996+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
2997+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2998+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2999+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3000+ QCOMPARE(lvwph->contentY(), 520.);
3001+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3002+ }
3003+
3004+ void testMoveFirstItems()
3005+ {
3006+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
3007+
3008+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3009+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3010+ verifyItem(0, 50., 200., false);
3011+ verifyItem(1, 250., 150., false);
3012+ verifyItem(2, 400., 350., false);
3013+ verifyItem(3, 750., 350., true);
3014+ QCOMPARE(lvwph->m_minYExtent, 0.);
3015+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3016+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3017+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3018+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3019+ QCOMPARE(lvwph->contentY(), 0.);
3020+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3021+
3022+ }
3023+
3024+ void testMoveFirstOutOfVisibleItems()
3025+ {
3026+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
3027+
3028+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3029+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3030+ verifyItem(0, 50., 200., false);
3031+ verifyItem(1, 250., 350., false);
3032+ verifyItem(2, 600., 350., true);
3033+ QCOMPARE(lvwph->m_minYExtent, 0.);
3034+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3035+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3036+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3037+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3038+ QCOMPARE(lvwph->contentY(), 0.);
3039+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3040+
3041+ }
3042+
3043+ void testMoveFirstToLastAtBottom()
3044+ {
3045+ scrollToBottom();
3046+
3047+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3048+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3049+ verifyItem(0, -508., 350., true);
3050+ verifyItem(1, -158, 350., false);
3051+ verifyItem(2, 192, 350., false);
3052+ QCOMPARE(lvwph->m_minYExtent, 350.);
3053+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
3054+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3055+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3056+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3057+ QCOMPARE(lvwph->contentY(), 1258.);
3058+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3059+
3060+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
3061+
3062+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3063+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
3064+ verifyItem(0, -508., 350., true);
3065+ verifyItem(1, -158, 350., false);
3066+ verifyItem(2, 192, 350., false);
3067+ verifyItem(3, 542, 150., true);
3068+ QCOMPARE(lvwph->m_minYExtent, -100.);
3069+ QCOMPARE(lvwph->m_clipItem->y(), 1258.);
3070+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3071+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3072+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3073+ QCOMPARE(lvwph->contentY(), 1258.);
3074+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3075+ QVERIFY(!lvwph->isAtYEnd());
3076+ }
3077+
3078+ void testChangeSizeVisibleItemNotOnViewport()
3079+ {
3080+ changeContentY(440);
3081+
3082+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3083+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3084+ verifyItem(0, -390., 150., true);
3085+ verifyItem(1, -240., 200., true);
3086+ verifyItem(2, -40, 350., false);
3087+ verifyItem(3, 310, 350., false);
3088+ verifyItem(4, 660, 350., true);
3089+ QCOMPARE(lvwph->m_minYExtent, 0.);
3090+ QCOMPARE(lvwph->m_clipItem->y(), 440.);
3091+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3092+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3093+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3094+ QCOMPARE(lvwph->contentY(), 440.);
3095+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3096+
3097+ model->setProperty(1, "size", 100);
3098+
3099+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3100+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3101+ verifyItem(0, -290., 150., true);
3102+ verifyItem(1, -140., 100., true);
3103+ verifyItem(2, -40, 350., false);
3104+ verifyItem(3, 310, 350., false);
3105+ verifyItem(4, 660, 350., true);
3106+ QCOMPARE(lvwph->m_minYExtent, -100.);
3107+ QCOMPARE(lvwph->m_clipItem->y(), 440.);
3108+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3109+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3110+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3111+ QCOMPARE(lvwph->contentY(), 440.);
3112+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3113+ }
3114+
3115+ void testShowHeaderCloseToTheTop()
3116+ {
3117+ changeContentY(375);
3118+
3119+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3120+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3121+ verifyItem(0, -325., 150., true);
3122+ verifyItem(1, -175, 200., false);
3123+ verifyItem(2, 25, 350., false);
3124+ verifyItem(3, 375, 350., false);
3125+ verifyItem(4, 725, 350., true);
3126+ QCOMPARE(lvwph->m_minYExtent, 0.);
3127+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3128+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3129+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3130+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3131+ QCOMPARE(lvwph->contentY(), 375.);
3132+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
3133+
3134+ lvwph->showHeader();
3135+
3136+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
3137+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3138+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3139+ verifyItem(0, -375., 150., true);
3140+ verifyItem(1, -225, 200., true);
3141+ verifyItem(2, -25, 350., false);
3142+ verifyItem(3, 325, 350., false);
3143+ verifyItem(4, 675, 350., true);
3144+ QCOMPARE(lvwph->m_minYExtent, 50.);
3145+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3146+ QCOMPARE(lvwph->m_clipItem->clip(), true);
3147+ QCOMPARE(lvwph->m_headerItem->y(), 325.);
3148+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3149+ QCOMPARE(lvwph->contentY(), 325.);
3150+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);;
3151+
3152+ scrollToTop();
3153+
3154+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3155+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3156+ verifyItem(0, 50., 150., false);
3157+ verifyItem(1, 200., 200., false);
3158+ verifyItem(2, 400., 350., false);
3159+ verifyItem(3, 750., 350., true);
3160+ QCOMPARE(lvwph->m_minYExtent, 50.);
3161+ QCOMPARE(lvwph->m_clipItem->y(), -50.);
3162+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3163+ QCOMPARE(lvwph->m_headerItem->y(), -50.);
3164+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3165+ QCOMPARE(lvwph->contentY(), -50.);
3166+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3167+ }
3168+
3169+ void testShowHeaderAtBottom()
3170+ {
3171+ scrollToBottom();
3172+
3173+ lvwph->showHeader();
3174+
3175+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
3176+ QTRY_VERIFY (lvwph->isAtYEnd());
3177+ }
3178+
3179+ void growWindow()
3180+ {
3181+ view->rootObject()->setHeight(850);
3182+
3183+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3184+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3185+ verifyItem(0, 50., 150., false);
3186+ verifyItem(1, 200., 200., false);
3187+ verifyItem(2, 400., 350., false);
3188+ verifyItem(3, 750., 350., false);
3189+ verifyItem(4, 1100., 350., true);
3190+ QCOMPARE(lvwph->m_minYExtent, 0.);
3191+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3192+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3193+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3194+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3195+ QCOMPARE(lvwph->contentY(), 0.);
3196+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3197+ }
3198+
3199+ void growWindowAtBottom()
3200+ {
3201+ scrollToBottom();
3202+
3203+ view->rootObject()->setHeight(850);
3204+
3205+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3206+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
3207+ verifyItem(0, -550., 350., true);
3208+ verifyItem(1, -200., 350., false);
3209+ verifyItem(2, 150, 350., false);
3210+ verifyItem(3, 500, 350., false);
3211+ QCOMPARE(lvwph->m_minYExtent, 350.);
3212+ QCOMPARE(lvwph->m_clipItem->y(), 950.);
3213+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3214+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3215+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3216+ QCOMPARE(lvwph->contentY(), 950.);
3217+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3218+ }
3219+
3220+private:
3221+ QQuickView *view;
3222+ ListViewWithPageHeader *lvwph;
3223+ QQuickListModel *model;
3224+ QQmlComponent *otherDelegate;
3225+};
3226+
3227+QTEST_MAIN(ListViewWithPageHeaderTest)
3228+
3229+#include "listviewwithpageheadertest.moc"
3230
3231=== added file 'tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp'
3232--- tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp 1970-01-01 00:00:00 +0000
3233+++ tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp 2013-07-01 11:23:32 +0000
3234@@ -0,0 +1,1567 @@
3235+/*
3236+ * Copyright (C) 2013 Canonical, Ltd.
3237+ *
3238+ * This program is free software; you can redistribute it and/or modify
3239+ * it under the terms of the GNU General Public License as published by
3240+ * the Free Software Foundation; version 3.
3241+ *
3242+ * This program is distributed in the hope that it will be useful,
3243+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3244+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3245+ * GNU General Public License for more details.
3246+ *
3247+ * You should have received a copy of the GNU General Public License
3248+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3249+ */
3250+
3251+#include "listviewwithpageheader.h"
3252+
3253+#include <QAbstractItemModel>
3254+#include <QQmlEngine>
3255+#include <QQuickView>
3256+#include <QtTestGui>
3257+#include <private/qquicklistmodel_p.h>
3258+#include <private/qquickanimation_p.h>
3259+#include <private/qquickitem_p.h>
3260+
3261+class ListViewWithPageHeaderTestSection : public QObject
3262+{
3263+ Q_OBJECT
3264+
3265+private:
3266+ void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled, const QString &sectionHeader, bool sectionHeaderCulled)
3267+ {
3268+ QTRY_VERIFY(visibleIndex < lvwph->m_visibleItems.count());
3269+ QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->y(), pos);
3270+ QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
3271+ QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
3272+ QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->m_sectionItem), sectionHeader);
3273+ if (!sectionHeader.isNull()) {
3274+ QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_sectionItem)->culled, sectionHeaderCulled);
3275+ }
3276+ }
3277+
3278+ void changeContentY(qreal change)
3279+ {
3280+ const qreal dest = lvwph->contentY() + change;
3281+ if (dest > lvwph->contentY()) {
3282+ const qreal jump = 25;
3283+ while (lvwph->contentY() + jump < dest) {
3284+ lvwph->setContentY(lvwph->contentY() + jump);
3285+ QTest::qWait(1);
3286+ }
3287+ } else {
3288+ const qreal jump = -25;
3289+ while (lvwph->contentY() + jump > dest) {
3290+ lvwph->setContentY(lvwph->contentY() + jump);
3291+ QTest::qWait(1);
3292+ }
3293+ }
3294+ lvwph->setContentY(dest);
3295+ QTest::qWait(1);
3296+ }
3297+
3298+ void scrollToTop()
3299+ {
3300+ const qreal jump = -25;
3301+ while (!lvwph->isAtYBeginning()) {
3302+ if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
3303+ lvwph->setContentY(lvwph->contentY() + jump);
3304+ } else {
3305+ lvwph->setContentY(lvwph->contentY() - 1);
3306+ }
3307+ QTest::qWait(1);
3308+ }
3309+ }
3310+
3311+ void scrollToBottom()
3312+ {
3313+ const qreal jump = 25;
3314+ while (!lvwph->isAtYEnd()) {
3315+ if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
3316+ lvwph->setContentY(lvwph->contentY() + jump);
3317+ } else {
3318+ lvwph->setContentY(lvwph->contentY() + 1);
3319+ }
3320+ QTest::qWait(1);
3321+ }
3322+ }
3323+
3324+ void verifyInitialTopPosition()
3325+ {
3326+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3327+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3328+ verifyItem(0, 50., 190., false, "Agressive", false);
3329+ verifyItem(1, 240., 240., false, "Regular", false);
3330+ verifyItem(2, 480., 390., false, "Mild", false);
3331+ QCOMPARE(lvwph->m_minYExtent, 0.);
3332+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3333+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3334+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3335+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3336+ QCOMPARE(lvwph->contentY(), 0.);
3337+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3338+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3339+ }
3340+
3341+ QString section(QQuickItem *item)
3342+ {
3343+ return item ? QQmlEngine::contextForObject(item)->parentContext()->contextProperty(QLatin1String("section")).toString() : QString();
3344+ }
3345+
3346+private Q_SLOTS:
3347+
3348+ void initTestCase()
3349+ {
3350+ }
3351+
3352+ void init()
3353+ {
3354+ view = new QQuickView();
3355+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
3356+ view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test_section.qml"));
3357+ lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
3358+ model = view->rootObject()->findChild<QQuickListModel*>();
3359+ otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
3360+ QVERIFY(lvwph);
3361+ QVERIFY(model);
3362+ QVERIFY(otherDelegate);
3363+ view->show();
3364+ QTest::qWaitForWindowExposed(view);
3365+
3366+ verifyInitialTopPosition();
3367+ }
3368+
3369+ void cleanup()
3370+ {
3371+ delete view;
3372+ }
3373+
3374+ void testCreationDeletion()
3375+ {
3376+ // Nothing, init/cleanup already tests this
3377+ }
3378+
3379+ void testDrag1PixelUp()
3380+ {
3381+ lvwph->setContentY(lvwph->contentY() + 1);
3382+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3383+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3384+ verifyItem(0, 49., 190., false, "Agressive", false);
3385+ verifyItem(1, 239., 240., false, "Regular", false);
3386+ verifyItem(2, 479., 390., false, "Mild", false);
3387+ QCOMPARE(lvwph->m_minYExtent, 0.);
3388+ QCOMPARE(lvwph->m_clipItem->y(), 1.);
3389+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3390+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3391+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3392+ QCOMPARE(lvwph->contentY(), 1.);
3393+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3394+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3395+ }
3396+
3397+ void testHeaderDetachDragDown()
3398+ {
3399+ QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
3400+ QTest::qWait(100);
3401+ QTest::mouseMove(view, QPoint(0, 5));
3402+ QTest::qWait(100);
3403+ QTest::mouseMove(view, QPoint(0, 10));
3404+ QTest::qWait(100);
3405+ QTest::mouseMove(view, QPoint(0, 15));
3406+ QTest::qWait(100);
3407+ QTest::mouseMove(view, QPoint(0, 20));
3408+ QTest::qWait(100);
3409+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3410+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3411+ verifyItem(0, 55., 190., false, "Agressive", false);
3412+ verifyItem(1, 245., 240., false, "Regular", false);
3413+ verifyItem(2, 485., 390., false, "Mild", false);
3414+ QCOMPARE(lvwph->m_minYExtent, 0.);
3415+ QCOMPARE(lvwph->m_clipItem->y(), -5.);
3416+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3417+ QCOMPARE(lvwph->m_headerItem->y(), -5.);
3418+ QCOMPARE(lvwph->m_headerItem->height(), 55.);
3419+ QCOMPARE(lvwph->contentY(), -5.);
3420+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3421+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3422+
3423+ QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
3424+
3425+ verifyInitialTopPosition();
3426+ }
3427+
3428+ void testDrag375PixelUp()
3429+ {
3430+ changeContentY(375);
3431+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3432+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3433+ verifyItem(0, -325., 190., true, "Agressive", true);
3434+ verifyItem(1, -135, 240., false, "Regular", true);
3435+ verifyItem(2, 105, 390., false, "Mild", false);
3436+ verifyItem(3, 495, 390., false, "Bold", false);
3437+ QCOMPARE(lvwph->m_minYExtent, 0.);
3438+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3439+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3440+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3441+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3442+ QCOMPARE(lvwph->contentY(), 375.);
3443+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3444+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3445+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
3446+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3447+ }
3448+
3449+ void testDrag520PixelUp()
3450+ {
3451+ changeContentY(520);
3452+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3453+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
3454+ verifyItem(0, -280., 240., true, "Regular", true);
3455+ verifyItem(1, -40, 390., false, "Mild", true);
3456+ verifyItem(2, 350, 390., false, "Bold", false);
3457+ verifyItem(3, 740, 350., true, QString(), true);
3458+ QCOMPARE(lvwph->m_minYExtent, 152.5);
3459+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
3460+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3461+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3462+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3463+ QCOMPARE(lvwph->contentY(), 520.);
3464+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3465+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3466+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
3467+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3468+ }
3469+
3470+ void testDragHeaderUpThenShow()
3471+ {
3472+ changeContentY(120);
3473+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3474+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3475+ verifyItem(0, -70., 190., false, "Agressive", true);
3476+ verifyItem(1, 120., 240., false, "Regular", false);
3477+ verifyItem(2, 360., 390., false, "Mild", false);
3478+ verifyItem(3, 750., 390., true, "Bold", true);
3479+ QCOMPARE(lvwph->m_minYExtent, 0.);
3480+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
3481+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3482+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3483+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3484+ QCOMPARE(lvwph->contentY(), 120.);
3485+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3486+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3487+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
3488+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3489+
3490+ changeContentY(-30);
3491+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3492+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3493+ verifyItem(0, -70., 190., false, "Agressive", true);
3494+ verifyItem(1, 120., 240., false, "Regular", false);
3495+ verifyItem(2, 360., 390., false, "Mild", false);
3496+ verifyItem(3, 750., 390., true, "Bold", true);
3497+ QCOMPARE(lvwph->m_minYExtent, 0.);
3498+ QCOMPARE(lvwph->m_clipItem->y(), 120.);
3499+ QCOMPARE(lvwph->m_clipItem->clip(), true);
3500+ QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
3501+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3502+ QCOMPARE(lvwph->contentY(), 90.);
3503+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
3504+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3505+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
3506+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3507+ }
3508+
3509+ void testDragHeaderUpThenShowWithoutHidingTotally()
3510+ {
3511+ changeContentY(10);
3512+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3513+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3514+ verifyItem(0, 40., 190., false, "Agressive", false);
3515+ verifyItem(1, 230., 240., false, "Regular", false);
3516+ verifyItem(2, 470., 390., false, "Mild", false);
3517+ QCOMPARE(lvwph->m_minYExtent, 0.);
3518+ QCOMPARE(lvwph->m_clipItem->y(), 10.);
3519+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3520+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3521+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3522+ QCOMPARE(lvwph->contentY(), 10.);
3523+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3524+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3525+
3526+ changeContentY(-1);
3527+
3528+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3529+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3530+ verifyItem(0, 41., 190., false, "Agressive", false);
3531+ verifyItem(1, 231., 240., false, "Regular", false);
3532+ verifyItem(2, 471., 390., false, "Mild", false);
3533+ QCOMPARE(lvwph->m_minYExtent, 0.);
3534+ QCOMPARE(lvwph->m_clipItem->y(), 9.);
3535+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3536+ QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
3537+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3538+ QCOMPARE(lvwph->contentY(), 9.);
3539+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3540+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3541+ }
3542+
3543+ void testPositionAtBeginningIndex0Visible()
3544+ {
3545+ changeContentY(375);
3546+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3547+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3548+ verifyItem(0, -325., 190., true, "Agressive", true);
3549+ verifyItem(1, -135, 240., false, "Regular", true);
3550+ verifyItem(2, 105, 390., false, "Mild", false);
3551+ verifyItem(3, 495, 390., false, "Bold", false);
3552+ QCOMPARE(lvwph->m_minYExtent, 0.);
3553+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3554+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3555+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3556+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3557+ QCOMPARE(lvwph->contentY(), 375.);
3558+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3559+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3560+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
3561+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3562+
3563+ lvwph->positionAtBeginning();
3564+
3565+ verifyInitialTopPosition();
3566+ }
3567+
3568+ void testPositionAtBeginningIndex0NotVisible()
3569+ {
3570+ changeContentY(520);
3571+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3572+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
3573+ verifyItem(0, -280., 240., true, "Regular", true);
3574+ verifyItem(1, -40, 390., false, "Mild", true);
3575+ verifyItem(2, 350, 390., false, "Bold", false);
3576+ verifyItem(3, 740, 350., true, QString(), true);
3577+ QCOMPARE(lvwph->m_minYExtent, 152.5);
3578+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
3579+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3580+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3581+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3582+ QCOMPARE(lvwph->contentY(), 520.);
3583+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3584+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3585+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
3586+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3587+
3588+ lvwph->positionAtBeginning();
3589+
3590+ verifyInitialTopPosition();
3591+ }
3592+
3593+ void testIndex0GrowOnScreen()
3594+ {
3595+ model->setProperty(0, "size", 400);
3596+
3597+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3598+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3599+ verifyItem(0, 50., 440., false, "Agressive", false);
3600+ verifyItem(1, 490., 240., false, "Regular", false);
3601+ verifyItem(2, 730., 390., true, "Mild", true);
3602+ QCOMPARE(lvwph->m_minYExtent, 0.);
3603+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3604+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3605+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3606+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3607+ QCOMPARE(lvwph->contentY(), 0.);
3608+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3609+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3610+ }
3611+
3612+ void testIndex0GrowOffScreen()
3613+ {
3614+ changeContentY(375);
3615+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3616+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3617+ verifyItem(0, -325., 190., true, "Agressive", true);
3618+ verifyItem(1, -135, 240., false, "Regular", true);
3619+ verifyItem(2, 105, 390., false, "Mild", false);
3620+ verifyItem(3, 495, 390., false, "Bold", false);
3621+ QCOMPARE(lvwph->m_minYExtent, 0.);
3622+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3623+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3624+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3625+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3626+ QCOMPARE(lvwph->contentY(), 375.);
3627+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3628+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3629+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
3630+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3631+
3632+ model->setProperty(0, "size", 400);
3633+
3634+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3635+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3636+ verifyItem(0, -575., 440., true, "Agressive", true);
3637+ verifyItem(1, -135, 240., false, "Regular", true);
3638+ verifyItem(2, 105, 390., false, "Mild", false);
3639+ verifyItem(3, 495, 390., false, "Bold", false);
3640+ QCOMPARE(lvwph->m_minYExtent, 250.);
3641+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
3642+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3643+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3644+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3645+ QCOMPARE(lvwph->contentY(), 375.);
3646+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3647+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3648+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
3649+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3650+
3651+ scrollToTop();
3652+
3653+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3654+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3655+ verifyItem(0, 50., 440., false, "Agressive", false);
3656+ verifyItem(1, 490, 240., false, "Regular", false);
3657+ verifyItem(2, 730, 390., true, "Mild", true);
3658+ QCOMPARE(lvwph->m_minYExtent, 250.);
3659+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
3660+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3661+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
3662+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3663+ QCOMPARE(lvwph->contentY(), -250.);
3664+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3665+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3666+
3667+ changeContentY(30);
3668+
3669+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3670+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3671+ verifyItem(0, 20., 440., false, "Agressive", false);
3672+ verifyItem(1, 460, 240., false, "Regular", false);
3673+ verifyItem(2, 700, 390., true, "Mild", true);
3674+ QCOMPARE(lvwph->m_minYExtent, 250.);
3675+ QCOMPARE(lvwph->m_clipItem->y(), -220.);
3676+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3677+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
3678+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3679+ QCOMPARE(lvwph->contentY(), -220.);
3680+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3681+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3682+ }
3683+
3684+ void testIndex0GrowNotCreated()
3685+ {
3686+ changeContentY(520);
3687+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3688+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
3689+ verifyItem(0, -280., 240., true, "Regular", true);
3690+ verifyItem(1, -40, 390., false, "Mild", true);
3691+ verifyItem(2, 350, 390., false, "Bold", false);
3692+ verifyItem(3, 740, 350., true, QString(), true);
3693+ QCOMPARE(lvwph->m_minYExtent, 152.5);
3694+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
3695+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3696+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3697+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3698+ QCOMPARE(lvwph->contentY(), 520.);
3699+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3700+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3701+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
3702+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3703+
3704+ model->setProperty(0, "size", 400);
3705+
3706+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
3707+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
3708+ verifyItem(0, -280., 240., true, "Regular", true);
3709+ verifyItem(1, -40, 390., false, "Mild", true);
3710+ verifyItem(2, 350, 390., false, "Bold", false);
3711+ verifyItem(3, 740, 350., true, QString(), true);
3712+ QCOMPARE(lvwph->m_minYExtent, 152.5);
3713+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
3714+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3715+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3716+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3717+ QCOMPARE(lvwph->contentY(), 520.);
3718+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3719+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3720+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
3721+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3722+
3723+ scrollToTop();
3724+
3725+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3726+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3727+ verifyItem(0, 50., 440., false, "Agressive", false);
3728+ verifyItem(1, 490, 240., false, "Regular", false);
3729+ verifyItem(2, 730, 390., true, "Mild", true);
3730+ QCOMPARE(lvwph->m_minYExtent, 250.);
3731+ QCOMPARE(lvwph->m_clipItem->y(), -250.);
3732+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3733+ QCOMPARE(lvwph->m_headerItem->y(), -250.);
3734+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3735+ QCOMPARE(lvwph->contentY(), -250.);
3736+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3737+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3738+ }
3739+
3740+ void testShowHideShowHeaderAtBottom()
3741+ {
3742+ scrollToBottom();
3743+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3744+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3745+ verifyItem(0, -588., 390., true, "Bold", true);
3746+ verifyItem(1, -198, 350., false, QString(), true);
3747+ verifyItem(2, 152, 390., false, "Lazy", false);
3748+ QCOMPARE(lvwph->m_minYExtent, 310.);
3749+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3750+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3751+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3752+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3753+ QCOMPARE(lvwph->contentY(), 1458.);
3754+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3755+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3756+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3757+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3758+
3759+ changeContentY(-30);
3760+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3761+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3762+ verifyItem(0, -588., 390., true, "Bold", true);
3763+ verifyItem(1, -198, 350., false, QString(), true);
3764+ verifyItem(2, 152, 390., false, "Lazy", false);
3765+ QCOMPARE(lvwph->m_minYExtent, 310.);
3766+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3767+ QCOMPARE(lvwph->m_clipItem->clip(), true);
3768+ QCOMPARE(lvwph->m_headerItem->y(), 1408.);
3769+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3770+ QCOMPARE(lvwph->contentY(), 1428.);
3771+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
3772+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3773+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3774+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3775+
3776+ changeContentY(30);
3777+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3778+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3779+ verifyItem(0, -588., 390., true, "Bold", true);
3780+ verifyItem(1, -198, 350., false, QString(), true);
3781+ verifyItem(2, 152, 390., false, "Lazy", false);
3782+ QCOMPARE(lvwph->m_minYExtent, 310.);
3783+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3784+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3785+ QCOMPARE(lvwph->m_headerItem->y(), -310.);
3786+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3787+ QCOMPARE(lvwph->contentY(), 1458.);
3788+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3789+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3790+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3791+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3792+
3793+ changeContentY(-30);
3794+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3795+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3796+ verifyItem(0, -588., 390., true, "Bold", true);
3797+ verifyItem(1, -198, 350., false, QString(), true);
3798+ verifyItem(2, 152, 390., false, "Lazy", false);
3799+ QCOMPARE(lvwph->m_minYExtent, 310.);
3800+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3801+ QCOMPARE(lvwph->m_clipItem->clip(), true);
3802+ QCOMPARE(lvwph->m_headerItem->y(), 1408.);
3803+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3804+ QCOMPARE(lvwph->contentY(), 1428.);
3805+ QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
3806+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3807+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3808+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3809+ }
3810+
3811+ void testChangeDelegateAtBottom()
3812+ {
3813+ scrollToBottom();
3814+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3815+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3816+ verifyItem(0, -588., 390., true, "Bold", true);
3817+ verifyItem(1, -198, 350., false, QString(), true);
3818+ verifyItem(2, 152, 390., false, "Lazy", false);
3819+ QCOMPARE(lvwph->m_minYExtent, 310.);
3820+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3821+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3822+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3823+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3824+ QCOMPARE(lvwph->contentY(), 1458.);
3825+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3826+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3827+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3828+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3829+
3830+ lvwph->setDelegate(otherDelegate);
3831+
3832+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
3833+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3834+ verifyItem(0, 50., 75., false, "Agressive", false);
3835+ verifyItem(1, 125, 75., false, "Regular", false);
3836+ verifyItem(2, 200, 75., false, "Mild", false);
3837+ verifyItem(3, 275, 75., false, "Bold", false);
3838+ verifyItem(4, 350, 35., false, QString(), true);
3839+ verifyItem(5, 385, 75., false, "Lazy", false);
3840+ QCOMPARE(lvwph->m_minYExtent, 0.);
3841+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3842+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3843+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3844+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3845+ QCOMPARE(lvwph->contentY(), 0.);
3846+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3847+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3848+ }
3849+
3850+ void testSetEmptyHeaderAtTop()
3851+ {
3852+ lvwph->setHeader(nullptr);
3853+
3854+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3855+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3856+ verifyItem(0, 0., 190., false, "Agressive", false);
3857+ verifyItem(1, 190., 240., false, "Regular", false);
3858+ verifyItem(2, 430., 390., false, "Mild", false);
3859+ QCOMPARE(lvwph->m_minYExtent, 0.);
3860+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3861+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3862+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3863+ QCOMPARE(lvwph->contentY(), 0.);
3864+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3865+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3866+ }
3867+
3868+ void testSetEmptyHeaderAtBottom()
3869+ {
3870+ scrollToBottom();
3871+ lvwph->setHeader(nullptr);
3872+
3873+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3874+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3875+ verifyItem(0, -588., 390., true, "Bold", true);
3876+ verifyItem(1, -198, 350., false, QString(), true);
3877+ verifyItem(2, 152, 390., false, "Lazy", false);
3878+ QCOMPARE(lvwph->m_minYExtent, 260.);
3879+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
3880+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3881+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3882+ QCOMPARE(lvwph->contentY(), 1458.);
3883+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3884+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3885+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3886+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3887+
3888+ scrollToTop();
3889+
3890+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3891+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3892+ verifyItem(0, 0., 190., false, "Agressive", false);
3893+ verifyItem(1, 190., 240., false, "Regular", false);
3894+ verifyItem(2, 430., 390., false, "Mild", false);
3895+ QCOMPARE(lvwph->m_minYExtent, -50.);
3896+ QCOMPARE(lvwph->m_clipItem->y(), 50.);
3897+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3898+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3899+ QCOMPARE(lvwph->contentY(), 50.);
3900+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3901+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3902+ }
3903+
3904+ void testSetEmptyHeaderWhenPartlyShownClipped()
3905+ {
3906+ scrollToBottom();
3907+ changeContentY(-30);
3908+ lvwph->setHeader(nullptr);
3909+
3910+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3911+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
3912+ verifyItem(0, -588., 390., true, "Bold", true);
3913+ verifyItem(1, -198, 350., false, QString(), true);
3914+ verifyItem(2, 152, 390., false, "Lazy", false);
3915+ QCOMPARE(lvwph->m_minYExtent, 290.);
3916+ QCOMPARE(lvwph->m_clipItem->y(), 1428.);
3917+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3918+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3919+ QCOMPARE(lvwph->contentY(), 1428.);
3920+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3921+ QTRY_VERIFY(lvwph->isAtYEnd());
3922+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3923+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
3924+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3925+
3926+ scrollToTop();
3927+
3928+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3929+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3930+ verifyItem(0, 0., 190., false, "Agressive", false);
3931+ verifyItem(1, 190., 240., false, "Regular", false);
3932+ verifyItem(2, 430., 390., false, "Mild", false);
3933+ QCOMPARE(lvwph->m_minYExtent, -20.);
3934+ QCOMPARE(lvwph->m_clipItem->y(), 20.);
3935+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3936+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3937+ QCOMPARE(lvwph->contentY(), 20.);
3938+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3939+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3940+ }
3941+
3942+ void testSetEmptyHeaderWhenPartlyShownNotClipped()
3943+ {
3944+ changeContentY(30);
3945+ lvwph->setHeader(nullptr);
3946+
3947+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
3948+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3949+ verifyItem(0, -30., 190., false, "Agressive", true);
3950+ verifyItem(1, 160., 240., false, "Regular", false);
3951+ verifyItem(2, 400., 390., false, "Mild", false);
3952+ QCOMPARE(lvwph->m_minYExtent, 0.);
3953+ QCOMPARE(lvwph->m_clipItem->y(), 30.);
3954+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3955+ QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
3956+ QCOMPARE(lvwph->contentY(), 30.);
3957+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3958+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3959+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
3960+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
3961+ }
3962+
3963+ void testSetNullDelegate()
3964+ {
3965+ lvwph->setDelegate(nullptr);
3966+
3967+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
3968+ QCOMPARE(lvwph->m_firstVisibleIndex, -1);
3969+ QCOMPARE(lvwph->m_minYExtent, 0.);
3970+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3971+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3972+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3973+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3974+ QCOMPARE(lvwph->contentY(), 0.);
3975+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
3976+ QTRY_COMPARE(lvwph->contentHeight(), 50.);
3977+ QVERIFY(lvwph->isAtYBeginning());
3978+ QVERIFY(lvwph->isAtYEnd());
3979+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
3980+ }
3981+
3982+ void testInsertItems()
3983+ {
3984+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
3985+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
3986+
3987+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
3988+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
3989+ verifyItem(0, 50., 190., false, "Agressive", false);
3990+ verifyItem(1, 240., 165., false, "Regular", false);
3991+ verifyItem(2, 405., 140., false, "Agressive", false);
3992+ verifyItem(3, 545., 240., true, "Regular", true);
3993+ verifyItem(4, 785., 390., true, "Mild", true);
3994+ QCOMPARE(lvwph->m_minYExtent, 0.);
3995+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
3996+ QCOMPARE(lvwph->m_clipItem->clip(), false);
3997+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
3998+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
3999+ QCOMPARE(lvwph->contentY(), 0.);
4000+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4001+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4002+ }
4003+
4004+ void testInsertItemsStealSectionItem()
4005+ {
4006+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4007+
4008+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4009+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4010+ verifyItem(0, 50., 190., false, "Agressive", false);
4011+ verifyItem(1, 240., 165., false, "Regular", false);
4012+ verifyItem(2, 405., 200., false, QString(), true);
4013+ verifyItem(3, 605., 390., true, "Mild", true);
4014+ QCOMPARE(lvwph->m_minYExtent, 0.);
4015+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4016+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4017+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4018+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4019+ QCOMPARE(lvwph->contentY(), 0.);
4020+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4021+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4022+ }
4023+
4024+
4025+ void testInsertItemsOnNotShownPosition()
4026+ {
4027+ changeContentY(800);
4028+
4029+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4030+ QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
4031+ verifyItem(0, -320., 390., false, "Mild", true);
4032+ verifyItem(1, 70, 390., false, "Bold", false);
4033+ verifyItem(2, 460, 350., false, QString(), false);
4034+ verifyItem(3, 810, 390., true, "Lazy", true);
4035+ QCOMPARE(lvwph->m_minYExtent, 330.);
4036+ QCOMPARE(lvwph->m_clipItem->y(), 800.);
4037+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4038+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4039+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4040+ QCOMPARE(lvwph->contentY(), 800.);
4041+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4042+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4043+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4044+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4045+
4046+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
4047+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4048+
4049+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4050+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
4051+ verifyItem(0, -320., 390., false, "Mild", true);
4052+ verifyItem(1, 70, 390., false, "Bold", false);
4053+ verifyItem(2, 460, 350., false, QString(), false);
4054+ verifyItem(3, 810, 390., true, "Lazy", true);
4055+ QCOMPARE(lvwph->m_minYExtent, 1090.);
4056+ QCOMPARE(lvwph->m_clipItem->y(), 800.);
4057+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4058+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4059+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4060+ QCOMPARE(lvwph->contentY(), 800.);
4061+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4062+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4063+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4064+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4065+
4066+ scrollToTop();
4067+
4068+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4069+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4070+ verifyItem(0, 50., 190., false, "Agressive", false);
4071+ verifyItem(1, 240., 165., false, "Regular", false);
4072+ verifyItem(2, 405., 140., false, "Agressive", false);
4073+ verifyItem(3, 545., 240., true, "Regular", true);
4074+ verifyItem(4, 785., 390., true, "Mild", true);
4075+ QCOMPARE(lvwph->m_minYExtent, 305.);
4076+ QCOMPARE(lvwph->m_clipItem->y(), -305.);
4077+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4078+ QCOMPARE(lvwph->m_headerItem->y(), -305.);
4079+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4080+ QCOMPARE(lvwph->contentY(), -305.);
4081+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4082+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4083+ }
4084+
4085+ void testInsertItemsAtEndOfViewport()
4086+ {
4087+ changeContentY(60);
4088+
4089+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4090+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4091+ verifyItem(0, -10., 190., false, "Agressive", true);
4092+ verifyItem(1, 180., 240., false, "Regular", false);
4093+ verifyItem(2, 420., 390., false, "Mild", false);
4094+ verifyItem(3, 810., 390., true, "Bold", true);
4095+ QCOMPARE(lvwph->m_minYExtent, 0.);
4096+ QCOMPARE(lvwph->m_clipItem->y(), 60.);
4097+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4098+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4099+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4100+ QCOMPARE(lvwph->contentY(), 60.);
4101+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4102+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4103+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
4104+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4105+
4106+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
4107+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4108+
4109+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4110+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4111+ verifyItem(0, -10., 190., false, "Agressive", true);
4112+ verifyItem(1, 180., 240., false, "Regular", false);
4113+ verifyItem(2, 420., 390., false, "Mild", false);
4114+ verifyItem(3, 810., 165., true, "Regular", true);
4115+ QCOMPARE(lvwph->m_minYExtent, 0.);
4116+ QCOMPARE(lvwph->m_clipItem->y(), 60.);
4117+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4118+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4119+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4120+ QCOMPARE(lvwph->contentY(), 60.);
4121+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4122+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4123+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
4124+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4125+ }
4126+
4127+ void testInsertItemsBeforeValidIndex()
4128+ {
4129+ changeContentY(520);
4130+
4131+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4132+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4133+ verifyItem(0, -280., 240., true, "Regular", true);
4134+ verifyItem(1, -40, 390., false, "Mild", true);
4135+ verifyItem(2, 350, 390., false, "Bold", false);
4136+ verifyItem(3, 740, 350., true, QString(), true);
4137+ QCOMPARE(lvwph->m_minYExtent, 152.5);
4138+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4139+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4140+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4141+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4142+ QCOMPARE(lvwph->contentY(), 520.);
4143+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4144+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4145+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4146+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4147+
4148+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
4149+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4150+
4151+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4152+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
4153+ verifyItem(0, -280., 240., true, "Regular", true);
4154+ verifyItem(1, -40, 390., false, "Mild", true);
4155+ verifyItem(2, 350, 390., false, "Bold", false);
4156+ verifyItem(3, 740, 350., true, QString(), true);
4157+ QCOMPARE(lvwph->m_minYExtent, 837.5);
4158+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4159+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4160+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4161+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4162+ QCOMPARE(lvwph->contentY(), 520.);
4163+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4164+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4165+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4166+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4167+ }
4168+
4169+ void testInsertItemsBeforeViewport()
4170+ {
4171+ changeContentY(375);
4172+
4173+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4174+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4175+ verifyItem(0, -325., 190., true, "Agressive", true);
4176+ verifyItem(1, -135, 240., false, "Regular", true);
4177+ verifyItem(2, 105, 390., false, "Mild", false);
4178+ verifyItem(3, 495, 390., false, "Bold", false);
4179+ QCOMPARE(lvwph->m_minYExtent, 0.);
4180+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
4181+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4182+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4183+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4184+ QCOMPARE(lvwph->contentY(), 375.);
4185+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4186+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4187+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
4188+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4189+
4190+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
4191+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4192+
4193+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4194+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
4195+ verifyItem(0, -275., 140., true, "Agressive", true);
4196+ verifyItem(1, -135., 240., false, "Regular", true);
4197+ verifyItem(2, 105, 390., false, "Mild", false);
4198+ verifyItem(3, 495, 390., false, "Bold", false);
4199+ QCOMPARE(lvwph->m_minYExtent, 530.);
4200+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
4201+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4202+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4203+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4204+ QCOMPARE(lvwph->contentY(), 375.);
4205+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4206+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4207+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
4208+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4209+
4210+ scrollToTop();
4211+
4212+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4213+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4214+ verifyItem(0, 50., 190., false, "Agressive", false);
4215+ verifyItem(1, 240., 165., false, "Regular", false);
4216+ verifyItem(2, 405., 140., false, "Agressive", false);
4217+ verifyItem(3, 545., 240., true, "Regular", true);
4218+ verifyItem(4, 785., 390., true, "Mild", true);
4219+ QCOMPARE(lvwph->m_minYExtent, 305.);
4220+ QCOMPARE(lvwph->m_clipItem->y(), -305.);
4221+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4222+ QCOMPARE(lvwph->m_headerItem->y(), -305.);
4223+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4224+ QCOMPARE(lvwph->contentY(), -305.);
4225+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4226+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4227+ }
4228+
4229+ void testInsertItemsAtBottom()
4230+ {
4231+ scrollToBottom();
4232+
4233+ QVERIFY(lvwph->isAtYEnd());
4234+
4235+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
4236+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
4237+
4238+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4239+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
4240+ verifyItem(0, -588., 390., true, "Bold", true);
4241+ verifyItem(1, -198, 350., false, QString(), true);
4242+ verifyItem(2, 152, 390., false, "Lazy", false);
4243+ verifyItem(3, 542, 165., true, "Regular", true);
4244+ verifyItem(4, 707, 140., true, "Agressive", true);
4245+ QCOMPARE(lvwph->m_minYExtent, 41.);
4246+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
4247+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4248+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4249+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4250+ QCOMPARE(lvwph->contentY(), 1458.);
4251+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4252+ QVERIFY(!lvwph->isAtYEnd());
4253+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4254+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
4255+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4256+
4257+ scrollToBottom();
4258+
4259+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4260+ QCOMPARE(lvwph->m_firstVisibleIndex, 4);
4261+ verifyItem(0, -503., 350., true, QString(), true);
4262+ verifyItem(1, -153, 390., false, "Lazy", true);
4263+ verifyItem(2, 237, 165., false, "Regular", false);
4264+ verifyItem(3, 402, 140., false, "Agressive", false);
4265+ QCOMPARE(lvwph->m_minYExtent, -165.);
4266+ QCOMPARE(lvwph->m_clipItem->y(), 1763.);
4267+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4268+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4269+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4270+ QCOMPARE(lvwph->contentY(), 1763.);
4271+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4272+ QVERIFY(lvwph->isAtYEnd());
4273+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4274+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Lazy"));
4275+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4276+ }
4277+
4278+ void testInsertItemAtTop()
4279+ {
4280+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4281+
4282+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4283+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4284+ verifyItem(0, 50., 115., false, "Agressive", false);
4285+ verifyItem(1, 165., 150., false, QString(), false);
4286+ verifyItem(2, 315., 240., false, "Regular", false);
4287+ verifyItem(3, 555., 390., true, "Mild", true);
4288+ QCOMPARE(lvwph->m_minYExtent, 0.);
4289+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4290+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4291+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4292+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4293+ QCOMPARE(lvwph->contentY(), 0.);
4294+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4295+ QVERIFY(lvwph->isAtYBeginning());
4296+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4297+ }
4298+
4299+ void testInsertItem10SmallItemsAtTopWhenAtBottom()
4300+ {
4301+ scrollToBottom();
4302+
4303+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4304+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4305+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4306+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
4307+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
4308+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
4309+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4310+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4311+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
4312+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
4313+
4314+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4315+ QCOMPARE(lvwph->m_firstVisibleIndex, 13);
4316+ verifyItem(0, -588., 390., true, "Bold", true);
4317+ verifyItem(1, -198, 350., false, QString(), true);
4318+ verifyItem(2, 152, 390., false, "Lazy", false);
4319+ QCOMPARE(lvwph->m_minYExtent, 12230./3.);
4320+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
4321+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4322+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4323+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4324+ QCOMPARE(lvwph->contentY(), 1458.);
4325+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4326+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4327+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
4328+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4329+
4330+ changeContentY(-1700);
4331+
4332+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 10);
4333+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
4334+ verifyItem(0, -323., 75., true, QString(), true);
4335+ verifyItem(1, -248., 115., true, "Regular", true);
4336+ verifyItem(2, -133., 75., true, QString(), true);
4337+ verifyItem(3, -58., 75., false, QString(), true);
4338+ verifyItem(4, 17., 115., false, "Agressive", false);
4339+ verifyItem(5, 132., 75., false, QString(), true);
4340+ verifyItem(6, 207., 75., false, QString(), true);
4341+ verifyItem(7, 282., 150., false, QString(), true);
4342+ verifyItem(8, 432., 240., false, "Regular", false);
4343+ QCOMPARE(lvwph->m_minYExtent, 980.5);
4344+ QCOMPARE(lvwph->m_clipItem->y(), -192.);
4345+ QCOMPARE(lvwph->m_clipItem->clip(), true);
4346+ QCOMPARE(lvwph->m_headerItem->y(), -242.);
4347+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4348+ QCOMPARE(lvwph->contentY(), -242.);
4349+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
4350+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4351+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
4352+ QCOMPARE(lvwph->m_topSectionItem->y(), -23.);
4353+ }
4354+
4355+ void testRemoveItemsAtTop()
4356+ {
4357+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
4358+
4359+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 2);
4360+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4361+ verifyItem(0, 50., 390., false, "Mild", false);
4362+ verifyItem(1, 440., 390., false, "Bold", false);
4363+ QCOMPARE(lvwph->m_minYExtent, 0.);
4364+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4365+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4366+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4367+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4368+ QCOMPARE(lvwph->contentY(), 0.);
4369+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4370+ QVERIFY(lvwph->isAtYBeginning());
4371+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4372+ }
4373+
4374+ void testRemoveNonCreatedItemsAtTopWhenAtBottom()
4375+ {
4376+ scrollToBottom();
4377+
4378+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
4379+
4380+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4381+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4382+ verifyItem(0, -588., 390., true, "Bold", true);
4383+ verifyItem(1, -198, 350., false, QString(), true);
4384+ verifyItem(2, 152, 390., false, "Lazy", false);
4385+ QCOMPARE(lvwph->m_minYExtent, -1330./3.);
4386+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
4387+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4388+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4389+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4390+ QCOMPARE(lvwph->contentY(), 1458.);
4391+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4392+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4393+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
4394+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4395+ }
4396+
4397+ void testRemoveLastItemsAtBottom()
4398+ {
4399+ scrollToBottom();
4400+
4401+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
4402+
4403+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4404+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4405+ verifyItem(0, -478., 240., true, "Regular", true);
4406+ verifyItem(1, -238, 390., false, "Mild", true);
4407+ verifyItem(2, 152, 390., false, "Bold", false);
4408+ QCOMPARE(lvwph->m_minYExtent, 150.);
4409+ QCOMPARE(lvwph->m_clipItem->y(), 718.);
4410+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4411+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4412+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4413+ QCOMPARE(lvwph->contentY(), 718.);
4414+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4415+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4416+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4417+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4418+ }
4419+
4420+ void testRemoveItemOutOfViewport()
4421+ {
4422+ changeContentY(520);
4423+
4424+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4425+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4426+ verifyItem(0, -280., 240., true, "Regular", true);
4427+ verifyItem(1, -40, 390., false, "Mild", true);
4428+ verifyItem(2, 350, 390., false, "Bold", false);
4429+ verifyItem(3, 740, 350., true, QString(), true);
4430+ QCOMPARE(lvwph->m_minYExtent, 152.5);
4431+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4432+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4433+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4434+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4435+ QCOMPARE(lvwph->contentY(), 520.);
4436+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4437+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4438+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4439+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4440+
4441+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
4442+
4443+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4444+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4445+ verifyItem(0, -230., 190., true, "Agressive", true);
4446+ verifyItem(1, -40, 390., false, "Mild", true);
4447+ verifyItem(2, 350, 390., false, "Bold", false);
4448+ verifyItem(3, 740, 350., true, QString(), true);
4449+ QCOMPARE(lvwph->m_minYExtent, -240.);
4450+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4451+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4452+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4453+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4454+ QCOMPARE(lvwph->contentY(), 520.);
4455+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4456+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4457+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4458+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4459+ }
4460+
4461+ void testRemoveFirstOfCategory()
4462+ {
4463+ changeContentY(520);
4464+
4465+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4466+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4467+ verifyItem(0, -280., 240., true, "Regular", true);
4468+ verifyItem(1, -40, 390., false, "Mild", true);
4469+ verifyItem(2, 350, 390., false, "Bold", false);
4470+ verifyItem(3, 740, 350., true, QString(), true);
4471+ QCOMPARE(lvwph->m_minYExtent, 152.5);
4472+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4473+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4474+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4475+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4476+ QCOMPARE(lvwph->contentY(), 520.);
4477+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4478+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4479+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4480+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4481+
4482+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 3), Q_ARG(QVariant, 1));
4483+
4484+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4485+ QCOMPARE(lvwph->m_firstVisibleIndex, 1);
4486+ verifyItem(0, -280., 240., true, "Regular", true);
4487+ verifyItem(1, -40, 390., false, "Mild", true);
4488+ verifyItem(2, 350, 390., false, "Bold", false);
4489+ verifyItem(3, 740, 390., true, "Lazy", true);
4490+ QCOMPARE(lvwph->m_minYExtent, 162.5);
4491+ QCOMPARE(lvwph->m_clipItem->y(), 520.);
4492+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4493+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4494+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4495+ QCOMPARE(lvwph->contentY(), 520.);
4496+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4497+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4498+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4499+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4500+ }
4501+
4502+ void testAddSecondToCulledCategoryOfTwo()
4503+ {
4504+ // Do some setup
4505+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
4506+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
4507+
4508+ changeContentY(200);
4509+
4510+ // Very the items are culled
4511+ verifyItem(0, -150., 65., true, "Agressive", true);
4512+ verifyItem(1, -85, 25., true, QString(), true);
4513+
4514+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
4515+
4516+ // Very the new item is there correctly
4517+ verifyItem(0, -175., 65., true, "Agressive", true);
4518+ verifyItem(1, -110, 25., true, QString(), true);
4519+ verifyItem(2, -85, 25., true, QString(), true);
4520+ }
4521+
4522+ void testMoveFirstItems()
4523+ {
4524+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
4525+
4526+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4527+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4528+ verifyItem(0, 50., 240., false, "Regular", false);
4529+ verifyItem(1, 290., 190., false, "Agressive", false);
4530+ verifyItem(2, 480., 390., false, "Mild", false);
4531+ QCOMPARE(lvwph->m_minYExtent, 0.);
4532+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4533+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4534+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4535+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4536+ QCOMPARE(lvwph->contentY(), 0.);
4537+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4538+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4539+ }
4540+
4541+ void testMoveFirstOutOfVisibleItems()
4542+ {
4543+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
4544+
4545+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4546+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4547+ verifyItem(0, 50., 240., false, "Regular", false);
4548+ verifyItem(1, 290., 390., false, "Mild", false);
4549+ verifyItem(2, 680., 390., true, "Bold", true);
4550+ QCOMPARE(lvwph->m_minYExtent, 0.);
4551+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4552+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4553+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4554+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4555+ QCOMPARE(lvwph->contentY(), 0.);
4556+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4557+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4558+ }
4559+
4560+ void testMoveFirstToLastAtBottom()
4561+ {
4562+ scrollToBottom();
4563+
4564+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4565+ QCOMPARE(lvwph->m_firstVisibleIndex, 3);
4566+ verifyItem(0, -588., 390., true, "Bold", true);
4567+ verifyItem(1, -198, 350., false, QString(), true);
4568+ verifyItem(2, 152, 390., false, "Lazy", false);
4569+ QCOMPARE(lvwph->m_minYExtent, 310.);
4570+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
4571+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4572+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4573+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4574+ QCOMPARE(lvwph->contentY(), 1458.);
4575+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4576+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4577+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
4578+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4579+
4580+ QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
4581+
4582+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4583+ QCOMPARE(lvwph->m_firstVisibleIndex, 2);
4584+ verifyItem(0, -588., 390., true, "Bold", true);
4585+ verifyItem(1, -198, 350., false, QString(), true);
4586+ verifyItem(2, 152, 390., false, "Lazy", false);
4587+ verifyItem(3, 542, 190., true, "Agressive", true);
4588+ QCOMPARE(lvwph->m_minYExtent, -160.);
4589+ QCOMPARE(lvwph->m_clipItem->y(), 1458.);
4590+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4591+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4592+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4593+ QCOMPARE(lvwph->contentY(), 1458.);
4594+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4595+ QVERIFY(!lvwph->isAtYEnd());
4596+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4597+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
4598+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4599+ }
4600+
4601+ void testChangeSizeVisibleItemNotOnViewport()
4602+ {
4603+ changeContentY(490);
4604+
4605+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4606+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4607+ verifyItem(0, -440., 190., true, "Agressive", true);
4608+ verifyItem(1, -250., 240., true, "Regular", true);
4609+ verifyItem(2, -10, 390., false, "Mild", true);
4610+ verifyItem(3, 380, 390., false, "Bold", false);
4611+ verifyItem(4, 770, 350., true, QString(), true);
4612+ QCOMPARE(lvwph->m_minYExtent, 0.);
4613+ QCOMPARE(lvwph->m_clipItem->y(), 490.);
4614+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4615+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4616+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4617+ QCOMPARE(lvwph->contentY(), 490.);
4618+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4619+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4620+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4621+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4622+
4623+ model->setProperty(1, "size", 100);
4624+
4625+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4626+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4627+ verifyItem(0, -340., 190., true, "Agressive", true);
4628+ verifyItem(1, -150., 140., true, "Regular", true);
4629+ verifyItem(2, -10, 390., false, "Mild", true);
4630+ verifyItem(3, 380, 390., false, "Bold", false);
4631+ verifyItem(4, 770, 350., true, QString(), true);
4632+ QCOMPARE(lvwph->m_minYExtent, -100.);
4633+ QCOMPARE(lvwph->m_clipItem->y(), 490.);
4634+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4635+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4636+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4637+ QCOMPARE(lvwph->contentY(), 490.);
4638+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4639+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4640+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
4641+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4642+ }
4643+
4644+ void testShowHeaderCloseToTheTop()
4645+ {
4646+ changeContentY(375);
4647+
4648+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4649+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4650+ verifyItem(0, -325., 190., true, "Agressive", true);
4651+ verifyItem(1, -135, 240., false, "Regular", true);
4652+ verifyItem(2, 105, 390., false, "Mild", false);
4653+ verifyItem(3, 495, 390., false, "Bold", false);
4654+ QCOMPARE(lvwph->m_minYExtent, 0.);
4655+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
4656+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4657+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4658+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4659+ QCOMPARE(lvwph->contentY(), 375.);
4660+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
4661+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4662+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
4663+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4664+
4665+ lvwph->showHeader();
4666+
4667+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
4668+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
4669+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4670+ verifyItem(0, -375., 190., true, "Agressive", true);
4671+ verifyItem(1, -185, 240., false, "Regular", true);
4672+ verifyItem(2, 55, 390., false, "Mild", false);
4673+ verifyItem(3, 445, 390., false, "Bold", false);
4674+ QCOMPARE(lvwph->m_minYExtent, 50.);
4675+ QCOMPARE(lvwph->m_clipItem->y(), 375.);
4676+ QCOMPARE(lvwph->m_clipItem->clip(), true);
4677+ QCOMPARE(lvwph->m_headerItem->y(), 325.);
4678+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4679+ QCOMPARE(lvwph->contentY(), 325.);
4680+ QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
4681+ QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4682+ QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
4683+ QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
4684+
4685+ scrollToTop();
4686+
4687+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
4688+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4689+ verifyItem(0, 50., 190., false, "Agressive", false);
4690+ verifyItem(1, 240., 240., false, "Regular", false);
4691+ verifyItem(2, 480., 390., false, "Mild", false);
4692+ QCOMPARE(lvwph->m_minYExtent, 50.);
4693+ QCOMPARE(lvwph->m_clipItem->y(), -50.);
4694+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4695+ QCOMPARE(lvwph->m_headerItem->y(), -50.);
4696+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4697+ QCOMPARE(lvwph->contentY(), -50.);
4698+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4699+ QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
4700+ }
4701+
4702+ void testShowHeaderAtBottom()
4703+ {
4704+ scrollToBottom();
4705+
4706+ lvwph->showHeader();
4707+
4708+ QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
4709+ QTRY_VERIFY (lvwph->isAtYEnd());
4710+ }
4711+
4712+ void growWindow()
4713+ {
4714+ view->rootObject()->setHeight(850);
4715+
4716+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
4717+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
4718+ verifyItem(0, 50., 190., false, "Agressive", false);
4719+ verifyItem(1, 240., 240., false, "Regular", false);
4720+ verifyItem(2, 480., 390., false, "Mild", false);
4721+ verifyItem(3, 870., 390., true, "Bold", true);
4722+ verifyItem(4, 1260., 350., true, QString(), true);
4723+ QCOMPARE(lvwph->m_minYExtent, 0.);
4724+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
4725+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4726+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4727+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4728+ QCOMPARE(lvwph->contentY(), 0.);
4729+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4730+ }
4731+
4732+ void growWindowAtBottom()
4733+ {
4734+ // Need a bunch small items at the bottom to trigger the problem
4735+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4736+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4737+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4738+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4739+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4740+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4741+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4742+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4743+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4744+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4745+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4746+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
4747+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4748+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4749+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4750+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4751+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4752+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4753+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4754+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
4755+
4756+ scrollToBottom();
4757+
4758+ view->rootObject()->setHeight(850);
4759+
4760+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 21);
4761+ QCOMPARE(lvwph->m_firstVisibleIndex, 5);
4762+ verifyItem(0, -660., 390., true, "Lazy", true);
4763+ verifyItem(1, -270., 90., true, "Agressive", true);
4764+ verifyItem(2, -180, 50., true, QString(), true);
4765+ verifyItem(3, -130, 50., true, QString(), true);
4766+ verifyItem(4, -80, 50., true, QString(), true);
4767+ verifyItem(5, -30, 50., false, QString(), true);
4768+ verifyItem(6, 20, 50., false, QString(), true);
4769+ verifyItem(7, 70, 50., false, QString(), true);
4770+ verifyItem(8, 120, 50., false, QString(), true);
4771+ verifyItem(9, 170, 90., false, "Regular", false);
4772+ verifyItem(10, 260, 50., false, QString(), true);
4773+ verifyItem(11, 310, 50., false, QString(), true);
4774+ verifyItem(12, 360, 50., false, QString(), true);
4775+ verifyItem(13, 410, 50., false, QString(), true);
4776+ verifyItem(14, 460, 50., false, QString(), true);
4777+ verifyItem(15, 510, 90., false, "Agressive", false);
4778+ verifyItem(16, 600, 50., false, QString(), true);
4779+ verifyItem(17, 650, 50., false, QString(), true);
4780+ verifyItem(18, 700, 50., false, QString(), true);
4781+ verifyItem(19, 750, 50., false, QString(), true);
4782+ verifyItem(20, 800, 50., false, QString(), true);
4783+ QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - 1970 + 50);
4784+ QCOMPARE(lvwph->m_clipItem->y(), 1970.);
4785+ QCOMPARE(lvwph->m_clipItem->clip(), false);
4786+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
4787+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
4788+ QCOMPARE(lvwph->contentY(), 1970.);
4789+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
4790+ }
4791+
4792+private:
4793+ QQuickView *view;
4794+ ListViewWithPageHeader *lvwph;
4795+ QQuickListModel *model;
4796+ QQmlComponent *otherDelegate;
4797+};
4798+
4799+QTEST_MAIN(ListViewWithPageHeaderTestSection)
4800+
4801+#include "listviewwithpageheadertestsection.moc"
4802
4803=== added file 'tests/plugins/ListViewWithPageHeader/test.qml'
4804--- tests/plugins/ListViewWithPageHeader/test.qml 1970-01-01 00:00:00 +0000
4805+++ tests/plugins/ListViewWithPageHeader/test.qml 2013-07-01 11:23:32 +0000
4806@@ -0,0 +1,96 @@
4807+/*
4808+ * Copyright (C) 2013 Canonical, Ltd.
4809+ *
4810+ * This program is free software; you can redistribute it and/or modify
4811+ * it under the terms of the GNU General Public License as published by
4812+ * the Free Software Foundation; version 3.
4813+ *
4814+ * This program is distributed in the hope that it will be useful,
4815+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4816+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4817+ * GNU General Public License for more details.
4818+ *
4819+ * You should have received a copy of the GNU General Public License
4820+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4821+ */
4822+
4823+import QtQuick 2.0
4824+import ListViewWithPageHeader 0.1
4825+
4826+Rectangle {
4827+ width: 300
4828+ height: 542
4829+ color: "lightblue"
4830+
4831+ ListModel {
4832+ id: model
4833+
4834+ function insertItem(index, size) {
4835+ insert(index, { size: size });
4836+ }
4837+
4838+ function removeItems(index, count) {
4839+ remove(index, count);
4840+ }
4841+
4842+ function moveItems(indexFrom, indexTo, count) {
4843+ move(indexFrom, indexTo, count);
4844+ }
4845+
4846+ ListElement { size: 150 }
4847+ ListElement { size: 200 }
4848+ ListElement { size: 350 }
4849+ ListElement { size: 350 }
4850+ ListElement { size: 350 }
4851+ ListElement { size: 350 }
4852+ }
4853+
4854+ Component {
4855+ id: otherRect
4856+ Rectangle {
4857+ height: 35
4858+ width: parent.width
4859+ color: index % 2 == 0 ? "yellow" : "purple"
4860+ }
4861+ }
4862+
4863+ ListViewWithPageHeader {
4864+ id: listView
4865+ width: parent.width
4866+ anchors.top: parent.top
4867+ anchors.bottom: parent.bottom
4868+ model: model
4869+ delegate: Rectangle {
4870+ property bool timerDone: false
4871+ width: parent.width - 20
4872+ x: 10
4873+ color: index % 2 == 0 ? "red" : "blue"
4874+ height: timerDone ? size : 350
4875+ Text {
4876+ text: index
4877+ }
4878+ Timer {
4879+ id: sizeTimer
4880+ interval: 10;
4881+ onTriggered: {
4882+ timerDone = true
4883+ }
4884+ }
4885+ Component.onCompleted: {
4886+ sizeTimer.start()
4887+ }
4888+ }
4889+
4890+ pageHeader: Rectangle {
4891+ color: "transparent"
4892+ width: parent.width
4893+ height: 50
4894+ implicitHeight: 50
4895+ Text {
4896+ anchors.fill: parent
4897+ text: "APPS"
4898+ font.pixelSize: 40
4899+ }
4900+ }
4901+ }
4902+}
4903
4904=== added file 'tests/plugins/ListViewWithPageHeader/test_section.qml'
4905--- tests/plugins/ListViewWithPageHeader/test_section.qml 1970-01-01 00:00:00 +0000
4906+++ tests/plugins/ListViewWithPageHeader/test_section.qml 2013-07-01 11:23:32 +0000
4907@@ -0,0 +1,106 @@
4908+/*
4909+ * Copyright (C) 2013 Canonical, Ltd.
4910+ *
4911+ * This program is free software; you can redistribute it and/or modify
4912+ * it under the terms of the GNU General Public License as published by
4913+ * the Free Software Foundation; version 3.
4914+ *
4915+ * This program is distributed in the hope that it will be useful,
4916+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4917+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4918+ * GNU General Public License for more details.
4919+ *
4920+ * You should have received a copy of the GNU General Public License
4921+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4922+ */
4923+
4924+import QtQuick 2.0
4925+import ListViewWithPageHeader 0.1
4926+
4927+Rectangle {
4928+ width: 300
4929+ height: 542
4930+ color: "lightblue"
4931+
4932+ ListModel {
4933+ id: model
4934+
4935+ function insertItem(index, size, type) {
4936+ insert(index, { size: size, type: type });
4937+ }
4938+
4939+ function removeItems(index, count) {
4940+ remove(index, count);
4941+ }
4942+
4943+ function moveItems(indexFrom, indexTo, count) {
4944+ move(indexFrom, indexTo, count);
4945+ }
4946+
4947+ ListElement { type: "Agressive"; size: 150 }
4948+ ListElement { type: "Regular"; size: 200 }
4949+ ListElement { type: "Mild"; size: 350 }
4950+ ListElement { type: "Bold"; size: 350 }
4951+ ListElement { type: "Bold"; size: 350 }
4952+ ListElement { type: "Lazy"; size: 350 }
4953+ }
4954+
4955+ Component {
4956+ id: otherRect
4957+ Rectangle {
4958+ height: 35
4959+ width: parent.width
4960+ color: index % 2 == 0 ? "yellow" : "purple"
4961+ }
4962+ }
4963+
4964+ ListViewWithPageHeader {
4965+ id: listView
4966+ width: parent.width
4967+ anchors.top: parent.top
4968+ anchors.bottom: parent.bottom
4969+ model: model
4970+ delegate: Rectangle {
4971+ property bool timerDone: false
4972+ width: parent.width - 20
4973+ x: 10
4974+ color: index % 2 == 0 ? "red" : "blue"
4975+ height: timerDone ? size : 350
4976+ Text {
4977+ text: index
4978+ }
4979+ Timer {
4980+ id: sizeTimer
4981+ interval: 10;
4982+ onTriggered: {
4983+ timerDone = true
4984+ }
4985+ }
4986+ Component.onCompleted: {
4987+ sizeTimer.start()
4988+ }
4989+ }
4990+
4991+ pageHeader: Rectangle {
4992+ color: "transparent"
4993+ width: parent.width
4994+ height: 50
4995+ implicitHeight: 50
4996+ Text {
4997+ anchors.fill: parent
4998+ text: "APPS"
4999+ font.pixelSize: 40
5000+ }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: