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
=== modified file 'Components/Carousel.qml'
--- Components/Carousel.qml 2013-06-06 07:57:05 +0000
+++ Components/Carousel.qml 2013-07-01 11:23:32 +0000
@@ -24,6 +24,8 @@
24Item {24Item {
25 id: carousel25 id: carousel
2626
27 clip: true // Don't leak horizontally to other dashes
28
27 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate29 /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
28 property Component itemComponent30 property Component itemComponent
29 /// Model for the Carousel, which has to be a model usable by a ListView31 /// Model for the Carousel, which has to be a model usable by a ListView
3032
=== modified file 'Components/ListItems/Base.qml'
--- Components/ListItems/Base.qml 2013-06-05 22:03:08 +0000
+++ Components/ListItems/Base.qml 2013-07-01 11:23:32 +0000
@@ -54,35 +54,33 @@
54 */54 */
55 function __showDivider() {55 function __showDivider() {
56 // if we're not in ListView, always show a thin dividing line at the bottom56 // if we're not in ListView, always show a thin dividing line at the bottom
57 if (ListView.view !== null) {57 var model = null;
5858 if (typeof ListViewWithPageHeader !== 'undefined') {
59 if (typeof ListViewWithPageHeader.model !== 'undefined') {
60 model = ListViewWithPageHeader.model;
61 }
62 } else if (ListView.view !== null) {
63 model = ListView.view.model;
64 }
59 // if we're last item in ListView don't show divider65 // if we're last item in ListView don't show divider
60 if (index === ListView.view.model.count - 1) return false;66 if (model && index === model.count - 1) return false;
61 }67
62 return true;68 return true;
63 }69 }
6470
65 property bool __clippingRequired: ListView.view !== null71 /* Relevant really only for ListViewWithPageHeader case: specify how many pixels we can overlap with the section header */
66 && ListView.view.section.labelPositioning & ViewSection.CurrentLabelAtStart72 readonly property int allowedOverlap: units.dp(1)
67
68 property real __yPositionRelativeToListView: ListView.view ? y - ListView.view.contentY : y
6973
70 property real __heightToClip: {74 property real __heightToClip: {
71 // Check this is in position where clipping is needed75 // Check this is in position where clipping is needed
72 if (__clippingRequired && __yPositionRelativeToListView <= __sectionDelegateHeight76 if (typeof ListViewWithPageHeader !== 'undefined') {
73 && __yPositionRelativeToListView > -height) {77 if (typeof heightToClip !== 'undefined') {
74 return Math.min(__sectionDelegateHeight - __yPositionRelativeToListView, height);78 if (heightToClip >= allowedOverlap) {
75 } else {79 return heightToClip - allowedOverlap;
76 return 0;80 }
77 }81 }
78 }82 }
7983 return 0;
80 property int __sectionDelegateHeight: {
81 if (__clippingRequired && ListView.view.hasOwnProperty("__sectionDelegateHeight")) {
82 return ListView.view.__sectionDelegateHeight;
83 } else {
84 return 0;
85 }
86 }84 }
8785
88 /*!86 /*!
8987
=== modified file 'Components/ListItems/Header.qml'
--- Components/ListItems/Header.qml 2013-06-05 22:03:08 +0000
+++ Components/ListItems/Header.qml 2013-07-01 11:23:32 +0000
@@ -25,12 +25,6 @@
25 */25 */
26 property alias text: label.text26 property alias text: label.text
2727
28 /* When this header is used in a ListViewWithPageHeader with CurrentLabelAtStart mode, to stop
29 the list's delegates contents being visible through this header, they clip themselves
30 depending on their position. However to allow delegates underlap the bottom X pixels of this
31 header (so delegate appears through ThinDivider say) we set an offset property here. */
32 readonly property int bottomBorderAllowedOverlap: bottomBorder.allowedBottomOverlap
33
34 height: units.gu(4.5)28 height: units.gu(4.5)
3529
36 Item {30 Item {
3731
=== modified file 'Components/ListItems/ThinDivider.qml'
--- Components/ListItems/ThinDivider.qml 2013-06-05 22:03:08 +0000
+++ Components/ListItems/ThinDivider.qml 2013-07-01 11:23:32 +0000
@@ -23,9 +23,5 @@
23 }23 }
24 height: (visible) ? units.dp(2) : 024 height: (visible) ? units.dp(2) : 0
2525
26 /* Relevant really only for ListViewWithPageHeader with Base delegates: should this image
27 be overlapping content, specify how many pixels from the bottom should be transparent */
28 readonly property int allowedBottomOverlap: units.dp(1)
29
30 source: "graphics/ListItemDividerHorizontal.png"26 source: "graphics/ListItemDividerHorizontal.png"
31}27}
3228
=== removed file 'Components/ListViewWithPageHeader.qml'
--- Components/ListViewWithPageHeader.qml 2013-06-05 22:03:08 +0000
+++ Components/ListViewWithPageHeader.qml 1970-01-01 00:00:00 +0000
@@ -1,189 +0,0 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import "Math.js" as MathLocal
19
20Item {
21 id: root
22 property Item pageHeader: null
23 property Component sectionDelegate: null
24 property string sectionProperty: ""
25 property alias model: listView.model
26 property alias delegate: listView.delegate
27 property ListView view: listView
28 property alias moving: flicker.moving
29 property alias atYEnd: flicker.atYEnd
30 property bool clipListView: true
31
32 readonly property real __headerHeight: (pageHeader) ? pageHeader.implicitHeight : 0
33 property real __headerVisibleHeight: __headerHeight
34 readonly property real __overshootHeight: (flicker.contentY < 0) ? -flicker.contentY : 0
35
36
37 // TODO move to AnimationController
38 ParallelAnimation {
39 id: headerAnimation
40 property real targetContentY
41 NumberAnimation {
42 target: root
43 property: "__headerVisibleHeight"
44 to: root.__headerHeight
45 duration: 200
46 easing.type: Easing.OutQuad
47 }
48 NumberAnimation {
49 target: listView
50 property: "contentY"
51 to: headerAnimation.targetContentY
52 duration: 200
53 easing.type: Easing.OutQuad
54 }
55 }
56
57 function positionAtBeginning() {
58 __headerVisibleHeight = __headerHeight
59 flicker.contentY = 0
60 }
61
62 function showHeader() {
63 headerAnimation.targetContentY = listView.contentY + (__headerHeight - __headerVisibleHeight)
64 headerAnimation.start()
65 }
66
67 function flick(xVelocity, yVelocity) {
68 flicker.flick(xVelocity, yVelocity);
69 }
70
71 onPageHeaderChanged: {
72 pageHeader.parent = pageHeaderContainer;
73 pageHeader.anchors.fill = pageHeaderContainer;
74 }
75
76 Item {
77 id: pageHeaderClipper
78 parent: flicker // parent to Flickable so mouse click events passed through to the header component
79 anchors {
80 top: parent.top
81 left: parent.left
82 right: parent.right
83 }
84 height: __headerVisibleHeight + __overshootHeight
85
86 Item {
87 id: pageHeaderContainer
88 anchors {
89 left: parent.left
90 right: parent.right
91 bottom: parent.bottom
92 }
93 height: __headerHeight + __overshootHeight
94 }
95 }
96
97 ListView {
98 id: listView
99 parent: flicker // parent to Flickable so mouse click events passed through to List delegates
100 anchors {
101 left: parent.left
102 right: parent.right
103 top: parent.top
104 topMargin: __headerVisibleHeight
105 }
106 height: root.height
107
108 // FIXME scrolling workaround, see below
109 cacheBuffer: height*10
110
111 section.property: sectionProperty
112 section.criteria: ViewSection.FullString
113 section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
114 section.delegate: sectionDelegate
115
116 interactive: false
117 clip: root.clipListView
118
119 property int __sectionDelegateHeight: __getHeight(section.delegate)
120 function __getHeight(component) {
121 // want height (minus allowed overlap) of the section delegate as is needed for clipping
122 if (component === null) return 0;
123 var object = component.createObject(); //FIXME: throws 'section' not defined error
124 var value = object.height - object.bottomBorderAllowedOverlap;
125 object.destroy();
126 return value;
127 }
128
129 property real previousOriginY: 0
130 onOriginYChanged: {
131 var deltaOriginY = originY - previousOriginY;
132 previousOriginY = originY;
133 /* When originY changes, it causes the top of the flicker and the top of the list to fall
134 out of sync - and thus the contentY positioning will be broken. To correct for this we
135 manually change the flickable's contentY here */
136 flicker.contentY -= deltaOriginY;
137 }
138
139 /* For case when list content greater than container height and list scrolled down so header
140 hidden. If content shrinks to be smaller than the container height, we want the header to
141 re-appear */
142 property real __previousContentHeight: 0
143 onContentHeightChanged: {
144 var deltaContentHeight = contentHeight - __previousContentHeight;
145 __previousContentHeight = contentHeight;
146 if (contentHeight < height && deltaContentHeight < 0 && __headerVisibleHeight < height - contentHeight) {
147 __headerVisibleHeight = Math.min(height - contentHeight, __headerHeight);
148 }
149 }
150 }
151
152 Flickable {
153 id: flicker
154 anchors.fill: parent
155 contentHeight: listView.contentHeight + __headerHeight
156 maximumFlickVelocity: height * 10
157 flickDeceleration: height * 2
158 onContentYChanged: {
159 var deltaContentY = contentY - __previousContentY;
160 __previousContentY = contentY;
161
162 // first decide if movement will prompt the page header to change height
163 if ((deltaContentY < 0 && __headerVisibleHeight >= 0) ||
164 (deltaContentY > 0 && __headerVisibleHeight <= __headerHeight)) {
165
166 // calculate header height - but prevent bounce from changing it
167 if (contentY > 0 && contentY < contentHeight - height) {
168 __headerVisibleHeight = MathLocal.clamp(__headerVisibleHeight - deltaContentY, 0, __headerHeight);
169 }
170 }
171
172 // now we move list position, taking into account page header height
173
174 // BUG: With section headers enabled, the values of originY and contentY appear not
175 // correct at the exact point originY changes. originY changes when the ListView
176 // deletes/creates hidden delegates which are above the visible delegates.
177 // As a result of this bug, you experience jittering scrolling when rapidly moving
178 // around in large lists. See https://bugreports.qt-project.org/browse/QTBUG-27997
179 // A workaround is to use a large enough cacheBuffer to prevent deletions/creations
180 // so effectively originY is always zero.
181 var newContentY = flicker.contentY + listView.originY - __headerHeight + __headerVisibleHeight
182 if (newContentY < listView.contentHeight) {
183 listView.contentY = newContentY;
184 }
185 }
186
187 property real __previousContentY: 0
188 }
189}
1900
=== modified file 'Dash/DashApps.qml'
--- Dash/DashApps.qml 2013-06-12 12:52:35 +0000
+++ Dash/DashApps.qml 2013-07-01 11:23:32 +0000
@@ -99,7 +99,7 @@
99 When using Loader to load external QML file in the list deelgate, the ListView has99 When using Loader to load external QML file in the list deelgate, the ListView has
100 a bug where it can position the delegate content to overlap the section header100 a bug where it can position the delegate content to overlap the section header
101 of the ListView - a workaround is to use sourceComponent of Loader instead */101 of the ListView - a workaround is to use sourceComponent of Loader instead */
102 Component { id: runningApplicationsGrid; RunningApplicationsGrid {} }102 Component { id: runningApplicationsGrid; RunningApplicationsGrid { clip: true } }
103 Component {103 Component {
104 id: applicationsFilterGrid;104 id: applicationsFilterGrid;
105 ApplicationsFilterGrid {105 ApplicationsFilterGrid {
@@ -112,7 +112,7 @@
112 "ApplicationsFilterGrid": applicationsFilterGrid,112 "ApplicationsFilterGrid": applicationsFilterGrid,
113 }113 }
114114
115 ListViewWithPageHeader {115 ScopeListView {
116 id: categoryView116 id: categoryView
117 anchors.fill: parent117 anchors.fill: parent
118 model: SortFilterProxyModel {118 model: SortFilterProxyModel {
@@ -150,7 +150,6 @@
150 item.model = categoryModels[modelName]150 item.model = categoryModels[modelName]
151 }151 }
152 }152 }
153 asynchronous: true
154 }153 }
155154
156 ListView.onRemove: SequentialAnimation {155 ListView.onRemove: SequentialAnimation {
157156
=== modified file 'Dash/DashHome.qml'
--- Dash/DashHome.qml 2013-06-14 13:54:08 +0000
+++ Dash/DashHome.qml 2013-07-01 11:23:32 +0000
@@ -130,7 +130,7 @@
130 "VideosGrid": videosGrid,130 "VideosGrid": videosGrid,
131 }131 }
132132
133 ListViewWithPageHeader {133 ScopeListView {
134 id: listView134 id: listView
135 anchors.fill: parent135 anchors.fill: parent
136 model: categoryListModel136 model: categoryListModel
@@ -161,7 +161,6 @@
161 item.clicked.connect(playVideo);161 item.clicked.connect(playVideo);
162 }162 }
163 }163 }
164 asynchronous: true
165 }164 }
166 }165 }
167166
168167
=== modified file 'Dash/DashMusic.qml'
--- Dash/DashMusic.qml 2013-06-14 20:57:40 +0000
+++ Dash/DashMusic.qml 2013-07-01 11:23:32 +0000
@@ -64,7 +64,7 @@
64 }64 }
65 }65 }
6666
67 ListViewWithPageHeader {67 ScopeListView {
68 id: categoryView68 id: categoryView
69 anchors.fill: parent69 anchors.fill: parent
70 model: scopeView.categories70 model: scopeView.categories
@@ -84,7 +84,6 @@
84 onLoaded: {84 onLoaded: {
85 item.model = results85 item.model = results
86 }86 }
87 asynchronous: true
88 }87 }
89 }88 }
9089
9190
=== modified file 'Dash/DashVideos.qml'
--- Dash/DashVideos.qml 2013-06-14 20:57:40 +0000
+++ Dash/DashVideos.qml 2013-07-01 11:23:32 +0000
@@ -97,11 +97,11 @@
97 }97 }
98 }98 }
9999
100 ListViewWithPageHeader {100 ScopeListView {
101 id: categoryView101 id: categoryView
102 anchors.fill: parent102 anchors.fill: parent
103 model: scopeView.categories103 model: scopeView.categories
104 clipListView: !previewLoader.onScreen104 forceNoClip: previewLoader.onScreen
105105
106 onAtYEndChanged: if (atYEnd) endReached()106 onAtYEndChanged: if (atYEnd) endReached()
107 onMovingChanged: if (moving && atYEnd) endReached()107 onMovingChanged: if (moving && atYEnd) endReached()
@@ -119,7 +119,6 @@
119 onLoaded: {119 onLoaded: {
120 item.model = results120 item.model = results
121 }121 }
122 asynchronous: true
123122
124 Connections {123 Connections {
125 target: loader.item124 target: loader.item
126125
=== modified file 'Dash/GenericScopeView.qml'
--- Dash/GenericScopeView.qml 2013-06-12 12:52:35 +0000
+++ Dash/GenericScopeView.qml 2013-07-01 11:23:32 +0000
@@ -43,7 +43,7 @@
43 }43 }
44 }44 }
4545
46 ListViewWithPageHeader {46 ScopeListView {
47 id: categoryView47 id: categoryView
48 anchors.fill: parent48 anchors.fill: parent
49 model: scopeView.categories49 model: scopeView.categories
5050
=== added file 'Dash/ScopeListView.qml'
--- Dash/ScopeListView.qml 1970-01-01 00:00:00 +0000
+++ Dash/ScopeListView.qml 2013-07-01 11:23:32 +0000
@@ -0,0 +1,23 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import ListViewWithPageHeader 0.1
19
20ListViewWithPageHeader {
21 maximumFlickVelocity: height * 10
22 flickDeceleration: height * 2
23}
024
=== modified file 'debian/control'
--- debian/control 2013-06-20 00:01:16 +0000
+++ debian/control 2013-07-01 11:23:32 +0000
@@ -11,6 +11,7 @@
11 libgles2-mesa-dev[armhf],11 libgles2-mesa-dev[armhf],
12 libhud-client2-dev,12 libhud-client2-dev,
13 libpulse-dev,13 libpulse-dev,
14 libqt5v8-5-private-dev,
14 libunity-core-6.0-dev (<< 7.80),15 libunity-core-6.0-dev (<< 7.80),
15 libxcb1-dev,16 libxcb1-dev,
16 pkg-config,17 pkg-config,
@@ -20,6 +21,7 @@
20 qtbase5-private-dev,21 qtbase5-private-dev,
21 qtdeclarative5-dev,22 qtdeclarative5-dev,
22 qtdeclarative5-dev-tools,23 qtdeclarative5-dev-tools,
24 qtdeclarative5-private-dev,
23 qtdeclarative5-qtquick2-plugin,25 qtdeclarative5-qtquick2-plugin,
24 qtdeclarative5-test-plugin,26 qtdeclarative5-test-plugin,
25 qtdeclarative5-ubuntu-ui-toolkit-plugin,27 qtdeclarative5-ubuntu-ui-toolkit-plugin,
2628
=== modified file 'debian/unity8.install'
--- debian/unity8.install 2013-06-05 22:03:08 +0000
+++ debian/unity8.install 2013-07-01 11:23:32 +0000
@@ -15,6 +15,7 @@
15/usr/share/unity8/graphics/*15/usr/share/unity8/graphics/*
16/usr/share/unity8/plugins/HudClient/*16/usr/share/unity8/plugins/HudClient/*
17/usr/share/unity8/plugins/LightDM/*17/usr/share/unity8/plugins/LightDM/*
18/usr/share/unity8/plugins/ListViewWithPageHeader/*
18/usr/share/unity8/plugins/Ubuntu/*19/usr/share/unity8/plugins/Ubuntu/*
19/usr/share/unity8/plugins/Unity/*20/usr/share/unity8/plugins/Unity/*
20/usr/share/unity8/plugins/Utils/*21/usr/share/unity8/plugins/Utils/*
2122
=== modified file 'plugins/CMakeLists.txt'
--- plugins/CMakeLists.txt 2013-06-05 22:03:08 +0000
+++ plugins/CMakeLists.txt 2013-07-01 11:23:32 +0000
@@ -3,3 +3,4 @@
3add_subdirectory(Unity)3add_subdirectory(Unity)
4add_subdirectory(HudClient)4add_subdirectory(HudClient)
5add_subdirectory(LightDM)5add_subdirectory(LightDM)
6add_subdirectory(ListViewWithPageHeader)
67
=== added directory 'plugins/ListViewWithPageHeader'
=== added file 'plugins/ListViewWithPageHeader/CMakeLists.txt'
--- plugins/ListViewWithPageHeader/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/CMakeLists.txt 2013-07-01 11:23:32 +0000
@@ -0,0 +1,44 @@
1# There's no cmake var for v8 include path :-/
2# so create one
3LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
4SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
5
6include_directories(
7 ${CMAKE_CURRENT_SOURCE_DIR}
8 ${CMAKE_CURRENT_BINARY_DIR}
9 ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
10 ${Qt5Quick_INCLUDE_DIRS}
11 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
12 ${Qt5V8_PRIVATE_INCLUDE_DIR}
13)
14
15remove_definitions(-DQT_NO_KEYWORDS)
16
17set(QMLPLUGIN_SRC
18 plugin.cpp
19 listviewwithpageheader.cpp
20 )
21
22add_library(ListViewWithPageHeader-qml MODULE
23 ${QMLPLUGIN_SRC}
24 )
25
26target_link_libraries(ListViewWithPageHeader-qml
27 ${Qt5Gui_LIBRARIES}
28 ${Qt5Quick_LIBRARIES}
29 )
30
31qt5_use_modules(ListViewWithPageHeader-qml Qml Quick)
32
33# copy qmldir file into build directory for shadow builds
34file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
35 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
36 )
37
38install(TARGETS ListViewWithPageHeader-qml
39 DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
40 )
41
42install(FILES qmldir
43 DESTINATION ${SHELL_APP_DIR}/plugins/ListViewWithPageHeader
44 )
045
=== added file 'plugins/ListViewWithPageHeader/listviewwithpageheader.cpp'
--- plugins/ListViewWithPageHeader/listviewwithpageheader.cpp 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/listviewwithpageheader.cpp 2013-07-01 11:23:32 +0000
@@ -0,0 +1,1059 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Some documentation on how this thing works:
19 *
20 * A flickable has two very important concepts that define the top and
21 * height of the flickable area.
22 * The top is returned in minYExtent()
23 * The height is set using setContentHeight()
24 * By changing those two values we can make the list grow up or down
25 * as needed. e.g. if we are in the middle of the list
26 * and something that is above the viewport grows, since we do not
27 * want to change the viewport because of that we just adjust the
28 * minYExtent so that the list grows up.
29 *
30 * The implementation on the list relies on the delegateModel doing
31 * most of the instantiation work. You call createItem() when you
32 * need to create an item asking for it async or not. If returns null
33 * it means the item will be created async and the model will call the
34 * itemCreated slot with the item.
35 *
36 * updatePolish is the central point of dispatch for the work of the
37 * class. It is called by the scene graph just before drawing the class.
38 * In it we:
39 * * Make sure all items are positioned correctly
40 * * Add/Remove items if needed
41 * * Update the content height if it was dirty
42 *
43 * m_visibleItems contains all the items we have created at the moment.
44 * Actually not all of them are visible since it includes the ones
45 * in the cache area we create asynchronously to help performance.
46 * The first item in m_visibleItems has the m_firstVisibleIndex in
47 * the model. If you actually want to know what is the first
48 * item in the viewport you have to find the first non culled element
49 * in m_visibleItems
50 *
51 * All the items (except the header) are childs of m_clipItem which
52 * is a child of the contentItem() of the flickable (The contentItem()
53 * is what actually 'moves' in a a flickable). This way
54 * we can implement the clipping needed so we can have the header
55 * shown in the middle of the list over the items without the items
56 * leaking under the header in case it is transparent.
57 *
58 * The first item of m_visibleItems is the one that defines the
59 * positions of all the rest of items (see updatePolish()) and
60 * this is why sometimes we move it even if it's not the item
61 * that has triggered the function (i.e. in itemGeometryChanged())
62 *
63 * m_visibleItems is a list of ListItem. Each ListItem
64 * will contain a item and potentially a sectionItem. The sectionItem
65 * is only there when the list is using sectionDelegate+sectionProperty
66 * and this is the first item of the section. Each ListItem is vertically
67 * layouted with the sectionItem first and then the item.
68 *
69 * For sectioning we also have a section item alone (m_topSectionItem)
70 * that is used for the cases we need to show the sticky section item at
71 * the top of the view.
72 *
73 * Each delegate item has a context property called heightToClip that is
74 * used to communicate to the delegate implementation in case it has to
75 * clip itself because of overlapping with the top sticky section item.
76 * This is an implementation decision since it has been agreed it
77 * is easier to implement the clipping in QML with this info than to
78 * do it at the C++ level.
79 *
80 * Note that minYExtent and height are not always totally accurate, since
81 * we don't have the items created we can't guess their heights
82 * so we can only guarantee the values are correct when the first/last
83 * items of the list are visible, otherwise we just live with good enough
84 * values that make the list scrollable
85 *
86 * There are a few things that are not really implemented or tested properly
87 * which we don't use at the moment like changing the model, changing
88 * the section delegate, having a section delegate that changes its size, etc.
89 * The known missing features are marked with TODOs along the code.
90 */
91
92#include "listviewwithpageheader.h"
93
94#include <QDebug>
95#include <qqmlinfo.h>
96#include <qqmlengine.h>
97#include <private/qquickvisualdatamodel_p.h>
98#include <private/qqmlglobal_p.h>
99#include <private/qquickitem_p.h>
100#include <private/qquickanimation_p.h>
101// #include <private/qquickrectangle_p.h>
102
103static const qreal bufferRatio = 0.5;
104
105qreal ListViewWithPageHeader::ListItem::height() const
106{
107 return m_item->height() + (m_sectionItem ? m_sectionItem->height() : 0);
108}
109
110qreal ListViewWithPageHeader::ListItem::y() const
111{
112 return m_item->y() - (m_sectionItem ? m_sectionItem->height() : 0);
113}
114
115void ListViewWithPageHeader::ListItem::setY(qreal newY)
116{
117 if (m_sectionItem) {
118 m_sectionItem->setY(newY);
119 m_item->setY(newY + m_sectionItem->height());
120 } else {
121 m_item->setY(newY);
122 }
123}
124
125bool ListViewWithPageHeader::ListItem::culled() const
126{
127 return QQuickItemPrivate::get(m_item)->culled;
128}
129
130void ListViewWithPageHeader::ListItem::setCulled(bool culled)
131{
132 QQuickItemPrivate::get(m_item)->setCulled(culled);
133 if (m_sectionItem)
134 QQuickItemPrivate::get(m_sectionItem)->setCulled(culled);
135}
136
137ListViewWithPageHeader::ListViewWithPageHeader()
138 : m_delegateModel(nullptr)
139 , m_asyncRequestedIndex(-1)
140 , m_delegateValidated(false)
141 , m_firstVisibleIndex(-1)
142 , m_minYExtent(0)
143 , m_contentHeightDirty(false)
144 , m_headerItem(nullptr)
145 , m_previousContentY(0)
146 , m_headerItemShownHeight(0)
147 , m_sectionDelegate(nullptr)
148 , m_topSectionItem(nullptr)
149 , m_forceNoClip(false)
150 , m_inLayout(false)
151{
152 m_clipItem = new QQuickItem(contentItem());
153// m_clipItem = new QQuickRectangle(contentItem());
154// ((QQuickRectangle*)m_clipItem)->setColor(Qt::gray);
155
156 m_headerShowAnimation = new QQuickNumberAnimation(this);
157 m_headerShowAnimation->setEasing(QEasingCurve::OutQuad);
158 m_headerShowAnimation->setProperty("contentY");
159 m_headerShowAnimation->setDuration(200);
160 m_headerShowAnimation->setTargetObject(this);
161
162 connect(this, SIGNAL(contentWidthChanged()), this, SLOT(onContentWidthChanged()));
163 connect(this, SIGNAL(contentHeightChanged()), this, SLOT(onContentHeightChanged()));
164 connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
165 connect(m_headerShowAnimation, SIGNAL(stopped()), this, SLOT(onShowHeaderAnimationFinished()));
166}
167
168ListViewWithPageHeader::~ListViewWithPageHeader()
169{
170}
171
172QAbstractItemModel *ListViewWithPageHeader::model() const
173{
174 return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
175}
176
177void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
178{
179 if (model != this->model()) {
180 if (!m_delegateModel) {
181 createDelegateModel();
182 } else {
183 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
184 }
185 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
186 connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
187 Q_EMIT modelChanged();
188 // TODO?
189// Q_EMIT contentHeightChanged();
190// Q_EMIT contentYChanged();
191 }
192}
193
194QQmlComponent *ListViewWithPageHeader::delegate() const
195{
196 return m_delegateModel ? m_delegateModel->delegate() : nullptr;
197}
198
199void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
200{
201 if (delegate != this->delegate()) {
202 if (!m_delegateModel) {
203 createDelegateModel();
204 }
205
206 // Cleanup the existing items
207 Q_FOREACH(ListItem *item, m_visibleItems)
208 releaseItem(item);
209 m_visibleItems.clear();
210 m_firstVisibleIndex = -1;
211 adjustMinYExtent();
212 setContentY(0);
213 m_clipItem->setY(0);
214 if (m_topSectionItem) {
215 QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
216 }
217
218 m_delegateModel->setDelegate(delegate);
219
220 Q_EMIT delegateChanged();
221 m_delegateValidated = false;
222 m_contentHeightDirty = true;
223 polish();
224 }
225}
226
227QQuickItem *ListViewWithPageHeader::header() const
228{
229 return m_headerItem;
230}
231
232void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
233{
234 if (m_headerItem != headerItem) {
235 qreal oldHeaderHeight = 0;
236 qreal oldHeaderY = 0;
237 if (m_headerItem) {
238 oldHeaderHeight = m_headerItem->height();
239 oldHeaderY = m_headerItem->y();
240 m_headerItem->setParentItem(nullptr);
241 }
242 m_headerItem = headerItem;
243 if (m_headerItem) {
244 m_headerItem->setParentItem(contentItem());
245 m_headerItem->setZ(1);
246 }
247 qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
248 if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
249 headerHeightChanged(newHeaderHeight, oldHeaderHeight, oldHeaderY);
250 polish();
251 m_contentHeightDirty = true;
252 }
253 Q_EMIT headerChanged();
254 }
255}
256
257QQmlComponent *ListViewWithPageHeader::sectionDelegate() const
258{
259 return m_sectionDelegate;
260}
261
262void ListViewWithPageHeader::setSectionDelegate(QQmlComponent *delegate)
263{
264 if (delegate != m_sectionDelegate) {
265 // TODO clean existing sections
266
267 m_sectionDelegate = delegate;
268
269 m_topSectionItem = getSectionItem(QString());
270 m_topSectionItem->setZ(3);
271 QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
272
273 // TODO create sections for existing items
274
275 Q_EMIT sectionDelegateChanged();
276 }
277}
278
279QString ListViewWithPageHeader::sectionProperty() const
280{
281 return m_sectionProperty;
282}
283
284void ListViewWithPageHeader::setSectionProperty(const QString &property)
285{
286 if (property != m_sectionProperty) {
287 m_sectionProperty = property;
288
289 updateWatchedRoles();
290
291 // TODO recreate sections
292
293 Q_EMIT sectionPropertyChanged();
294 }
295}
296
297bool ListViewWithPageHeader::forceNoClip() const
298{
299 return m_forceNoClip;
300}
301
302void ListViewWithPageHeader::setForceNoClip(bool noClip)
303{
304 if (noClip != m_forceNoClip) {
305 m_forceNoClip = noClip;
306 updateClipItem();
307 Q_EMIT forceNoClipChanged();
308 }
309}
310
311void ListViewWithPageHeader::positionAtBeginning()
312{
313 qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
314 if (m_firstVisibleIndex != 0) {
315 // TODO This could be optimized by trying to reuse the interesection
316 // of items that may end up intersecting between the existing
317 // m_visibleItems and the items we are creating in the next loop
318 Q_FOREACH(ListItem *item, m_visibleItems)
319 releaseItem(item);
320 m_visibleItems.clear();
321 m_firstVisibleIndex = -1;
322
323 // Create the item 0, it will be already correctly positioned at createItem()
324 m_clipItem->setY(0);
325 ListItem *item = createItem(0, false);
326 // Create the subsequent items
327 int modelIndex = 1;
328 qreal pos = item->y() + item->height();
329 const qreal buffer = height() * bufferRatio;
330 const qreal bufferTo = height() + buffer;
331 while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
332 if (!(item = createItem(modelIndex, false)))
333 break;
334 pos += item->height();
335 ++modelIndex;
336 }
337
338 m_previousContentY = m_visibleItems.first()->y() - headerHeight;
339 }
340 setContentY(m_visibleItems.first()->y() + m_clipItem->y() - headerHeight);
341}
342
343void ListViewWithPageHeader::showHeader()
344{
345 auto to = qMax(-minYExtent(), contentY() - m_headerItem->height() + m_headerItemShownHeight);
346 if (to != contentY()) {
347 m_headerShowAnimation->setTo(to);
348 m_headerShowAnimation->start();
349 }
350}
351
352qreal ListViewWithPageHeader::minYExtent() const
353{
354// qDebug() << "ListViewWithPageHeader::minYExtent" << m_minYExtent;
355 return m_minYExtent;
356}
357
358void ListViewWithPageHeader::componentComplete()
359{
360 if (m_delegateModel)
361 m_delegateModel->componentComplete();
362
363 QQuickFlickable::componentComplete();
364
365 polish();
366}
367
368void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
369{
370 QQuickFlickable::viewportMoved(orient);
371// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
372 qreal diff = m_previousContentY - contentY();
373 if (m_headerItem) {
374 auto oldHeaderItemShownHeight = m_headerItemShownHeight;
375 if (contentY() < -m_minYExtent) {
376 // Stick the header item to the top when dragging down
377 m_headerItem->setY(contentY());
378 m_headerItem->setHeight(m_headerItem->implicitHeight() + (-m_minYExtent - contentY()));
379 } else {
380 m_headerItem->setHeight(m_headerItem->implicitHeight());
381 // We are going down (but it's not because of the rebound at the end)
382 // (but the header was not shown by it's own position)
383 // or the header is partially shown
384 const bool scrolledUp = m_previousContentY > contentY();
385 const bool notRebounding = contentY() + height() < contentHeight();
386 const bool notShownByItsOwn = contentY() + diff > m_headerItem->y() + m_headerItem->height();
387
388 if (!scrolledUp && contentY() == -m_minYExtent) {
389 m_headerItemShownHeight = 0;
390 m_headerItem->setY(contentY());
391 } else if ((scrolledUp && notRebounding && notShownByItsOwn) || (m_headerItemShownHeight > 0)) {
392 m_headerItemShownHeight += diff;
393 if (contentY() == -m_minYExtent) {
394 m_headerItemShownHeight = 0;
395 } else {
396 m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, m_headerItem->height());
397 }
398 if (m_headerItemShownHeight > 0) {
399 m_headerItem->setY(contentY() - m_headerItem->height() + m_headerItemShownHeight);
400 } else {
401 m_headerItem->setY(-m_minYExtent);
402 }
403 }
404 }
405 // We will be changing the clip item, need to accomadate for it
406 // otherwise we move the firstItem down/up twice (unless the
407 // show header animation is running, where we want to keep the viewport stable)
408 if (!m_headerShowAnimation->isRunning()) {
409 diff += oldHeaderItemShownHeight - m_headerItemShownHeight;
410 } else {
411 diff = -diff;
412 }
413 }
414 if (!m_visibleItems.isEmpty()) {
415 updateClipItem();
416 ListItem *firstItem = m_visibleItems.first();
417 firstItem->setY(firstItem->y() + diff);
418 if (m_headerShowAnimation->isRunning()) {
419 adjustMinYExtent();
420 }
421 }
422
423 m_previousContentY = contentY();
424 layout();
425 polish();
426}
427
428void ListViewWithPageHeader::createDelegateModel()
429{
430 m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
431 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
432 if (isComponentComplete())
433 m_delegateModel->componentComplete();
434 updateWatchedRoles();
435}
436
437void ListViewWithPageHeader::refill()
438{
439 if (m_inLayout) {
440 return;
441 }
442 if (!isComponentComplete()) {
443 return;
444 }
445
446 const qreal buffer = height() * bufferRatio;
447 const qreal from = contentY();
448 const qreal to = from + height();
449 const qreal bufferFrom = from - buffer;
450 const qreal bufferTo = to + buffer;
451
452 bool added = addVisibleItems(from, to, false);
453 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
454 added |= addVisibleItems(bufferFrom, bufferTo, true);
455
456 if (added || removed) {
457 m_contentHeightDirty = true;
458 }
459}
460
461bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
462{
463 if (!delegate())
464 return false;
465
466 if (m_delegateModel->count() == 0)
467 return false;
468
469 ListItem *item;
470// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
471
472 int modelIndex = 0;
473 qreal pos = 0;
474 if (!m_visibleItems.isEmpty()) {
475 modelIndex = m_firstVisibleIndex + m_visibleItems.count();
476 item = m_visibleItems.last();
477 pos = item->y() + item->height() + m_clipItem->y();
478 }
479 bool changed = false;
480// qDebug() << (modelIndex < m_delegateModel->count()) << pos << fillTo;
481 while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
482// qDebug() << "refill: append item" << modelIndex << "pos" << pos << "asynchronous" << asynchronous;
483 if (!(item = createItem(modelIndex, asynchronous)))
484 break;
485 pos += item->height();
486 ++modelIndex;
487 changed = true;
488 }
489
490 modelIndex = 0;
491 pos = 0;
492 if (!m_visibleItems.isEmpty()) {
493 modelIndex = m_firstVisibleIndex - 1;
494 item = m_visibleItems.first();
495 pos = item->y() + m_clipItem->y();
496 }
497 while (modelIndex >= 0 && pos > fillFrom) {
498// qDebug() << "refill: prepend item" << modelIndex << "pos" << pos << "fillFrom" << fillFrom << "asynchronous" << asynchronous;
499 if (!(item = createItem(modelIndex, asynchronous)))
500 break;
501 pos -= item->height();
502 --modelIndex;
503 changed = true;
504 }
505
506 return changed;
507}
508
509void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
510{
511 QQuickItem *item = listItem->m_item;
512 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
513 if (flags & QQuickVisualModel::Destroyed) {
514 item->setParentItem(nullptr);
515 }
516 delete listItem->m_sectionItem;
517 delete listItem;
518}
519
520void ListViewWithPageHeader::releaseItem(ListItem *listItem)
521{
522 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(listItem->m_item);
523 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
524 m_itemsToRelease << listItem;
525}
526
527void ListViewWithPageHeader::updateWatchedRoles()
528{
529 if (m_delegateModel) {
530 QList<QByteArray> roles;
531 if (!m_sectionProperty.isEmpty())
532 roles << m_sectionProperty.toUtf8();
533 m_delegateModel->setWatchedRoles(roles);
534 }
535}
536
537QQuickItem *ListViewWithPageHeader::getSectionItem(int modelIndex, bool alreadyInserted)
538{
539 if (!m_sectionDelegate)
540 return nullptr;
541
542 const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
543 if (modelIndex > 0) {
544 const QString prevSection = m_delegateModel->stringValue(modelIndex - 1, m_sectionProperty);
545 if (section == prevSection)
546 return nullptr;
547 }
548 if (modelIndex + 1 < model()->rowCount() && !alreadyInserted) {
549 // Already inserted items can't steal next section header
550 const QString nextSection = m_delegateModel->stringValue(modelIndex + 1, m_sectionProperty);
551 if (section == nextSection) {
552 // Steal the section header
553 ListItem *nextItem = itemAtIndex(modelIndex); // Not +1 since not yet inserted into m_visibleItems
554 if (nextItem) {
555 QQuickItem *sectionItem = nextItem->m_sectionItem;
556 nextItem->m_sectionItem = nullptr;
557 return sectionItem;
558 }
559 }
560 }
561
562 return getSectionItem(section);
563}
564
565QQuickItem *ListViewWithPageHeader::getSectionItem(const QString &sectionText)
566{
567 QQuickItem *sectionItem = nullptr;
568
569 QQmlContext *creationContext = m_sectionDelegate->creationContext();
570 QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this));
571 context->setContextProperty(QLatin1String("section"), sectionText);
572 QObject *nobj = m_sectionDelegate->beginCreate(context);
573 if (nobj) {
574 QQml_setParent_noEvent(context, nobj);
575 sectionItem = qobject_cast<QQuickItem *>(nobj);
576 if (!sectionItem) {
577 delete nobj;
578 } else {
579 sectionItem->setZ(2);
580 QQml_setParent_noEvent(sectionItem, m_clipItem);
581 sectionItem->setParentItem(m_clipItem);
582 }
583 } else {
584 delete context;
585 }
586 m_sectionDelegate->completeCreate();
587
588 // TODO attach to sectionItem so we can accomodate to it changing its height
589
590 return sectionItem;
591}
592
593bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
594{
595// qDebug() << "ListViewWithPageHeader::removeNonVisibleItems" << bufferFrom << bufferTo;
596 // Do not remove items if we are overshooting up or down, since we'll come back
597 // to the "stable" position and delete/create items without any reason
598 if (contentY() < -m_minYExtent) {
599 return false;
600 } else if (contentY() + height() > contentHeight()) {
601 return false;
602 }
603 bool changed = false;
604
605 bool foundVisible = false;
606 int i = 0;
607 int removedItems = 0;
608 const auto oldFirstVisibleIndex = m_firstVisibleIndex;
609 while (i < m_visibleItems.count()) {
610 ListItem *item = m_visibleItems[i];
611 const qreal pos = item->y() + m_clipItem->y();
612// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
613 if (pos + item->height() < bufferFrom || pos > bufferTo) {
614// qDebug() << "Releasing" << i << (pos + item->height() < bufferFrom) << pos + item->height() << bufferFrom << (pos > bufferTo) << pos << bufferTo;
615 releaseItem(item);
616 m_visibleItems.removeAt(i);
617 changed = true;
618 ++removedItems;
619 } else {
620 if (!foundVisible) {
621 foundVisible = true;
622 const int itemIndex = m_firstVisibleIndex + removedItems + i;
623 m_firstVisibleIndex = itemIndex;
624 }
625 ++i;
626 }
627 }
628 if (m_firstVisibleIndex != oldFirstVisibleIndex) {
629 adjustMinYExtent();
630 }
631
632 return changed;
633}
634
635ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
636{
637// qDebug() << "CREATE ITEM" << modelIndex;
638 if (asynchronous && m_asyncRequestedIndex != -1)
639 return nullptr;
640
641 m_asyncRequestedIndex = -1;
642 QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
643 if (!item) {
644 m_asyncRequestedIndex = modelIndex;
645 return 0;
646 } else {
647// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
648 ListItem *listItem = new ListItem;
649 listItem->m_item = item;
650 listItem->m_sectionItem = getSectionItem(modelIndex, false /*Not yet inserted into m_visibleItems*/);
651 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
652 ListItem *prevItem = itemAtIndex(modelIndex - 1);
653 bool lostItem = false; // Is an item that we requested async but because of model changes
654 // it is no longer attached to any of the existing items (has no prev nor next item)
655 // nor is the first item
656 if (prevItem) {
657 listItem->setY(prevItem->y() + prevItem->height());
658 } else {
659 ListItem *currItem = itemAtIndex(modelIndex);
660 if (currItem) {
661 // There's something already in m_visibleItems at out index, meaning this is an insert, so attach to its top
662 listItem->setY(currItem->y() - listItem->height());
663 } else {
664 ListItem *nextItem = itemAtIndex(modelIndex + 1);
665 if (nextItem) {
666 listItem->setY(nextItem->y() - listItem->height());
667 } else if (modelIndex == 0 && m_headerItem) {
668 listItem->setY(m_headerItem->height());
669 } else if (!m_visibleItems.isEmpty()) {
670 lostItem = true;
671 }
672 }
673 }
674 if (lostItem) {
675 releaseItem(listItem);
676 listItem = nullptr;
677 } else {
678 listItem->setCulled(listItem->y() + listItem->height() + m_clipItem->y() < contentY() || listItem->y() + m_clipItem->y() >= contentY() + height());
679 if (m_visibleItems.isEmpty()) {
680 m_visibleItems << listItem;
681 } else {
682 m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
683 }
684 if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
685 m_firstVisibleIndex = modelIndex;
686 polish();
687 }
688 adjustMinYExtent();
689 m_contentHeightDirty = true;
690 }
691 return listItem;
692 }
693}
694
695void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
696{
697// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
698
699 item->setParentItem(m_clipItem);
700 QQmlContext *context = QQmlEngine::contextForObject(item)->parentContext();
701 context->setContextProperty(QLatin1String("ListViewWithPageHeader"), this);
702 context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
703 if (modelIndex == m_asyncRequestedIndex) {
704 createItem(modelIndex, false);
705 refill();
706 }
707}
708
709void ListViewWithPageHeader::updateClipItem()
710{
711 m_clipItem->setHeight(height() - m_headerItemShownHeight);
712 m_clipItem->setY(contentY() + m_headerItemShownHeight);
713 m_clipItem->setClip(!m_forceNoClip && m_headerItemShownHeight > 0);
714}
715
716void ListViewWithPageHeader::onContentHeightChanged()
717{
718 updateClipItem();
719}
720
721void ListViewWithPageHeader::onContentWidthChanged()
722{
723 m_clipItem->setWidth(contentItem()->width());
724}
725
726void ListViewWithPageHeader::onHeightChanged()
727{
728 polish();
729}
730
731void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
732{
733 // TODO Do something with reset
734// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
735 const auto oldFirstVisibleIndex = m_firstVisibleIndex;
736
737 Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
738// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
739 if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
740 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
741 // If all the items we are removing are either not created or culled
742 // we have to grow down to avoid viewport changing
743 bool growDown = true;
744 for (int i = 0; growDown && i < remove.count; ++i) {
745 const int modelIndex = remove.index + i;
746 ListItem *item = itemAtIndex(modelIndex);
747 if (item && !item->culled()) {
748 growDown = false;
749 }
750 }
751 for (int i = remove.count - 1; i >= 0; --i) {
752 const int visibleIndex = remove.index + i - m_firstVisibleIndex;
753 if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
754 ListItem *item = m_visibleItems[visibleIndex];
755 // Pass the section item down if needed
756 if (item->m_sectionItem && visibleIndex + 1 < m_visibleItems.count()) {
757 ListItem *nextItem = m_visibleItems[visibleIndex + 1];
758 if (!nextItem->m_sectionItem) {
759 nextItem->m_sectionItem = item->m_sectionItem;
760 item->m_sectionItem = nullptr;
761 }
762 }
763 releaseItem(item);
764 m_visibleItems.removeAt(visibleIndex);
765 }
766 }
767 if (growDown) {
768 adjustMinYExtent();
769 } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
770 // We removed the first item that is the one that positions the rest
771 // position the new first item correctly
772 m_visibleItems.first()->setY(oldFirstValidIndexPos);
773 }
774 } else if (remove.index + remove.count <= m_firstVisibleIndex) {
775 m_firstVisibleIndex -= remove.count;
776 }
777 for (int i = remove.count - 1; i >= 0; --i) {
778 const int modelIndex = remove.index + i;
779 if (modelIndex == m_asyncRequestedIndex) {
780 m_asyncRequestedIndex = -1;
781 } else if (modelIndex < m_asyncRequestedIndex) {
782 m_asyncRequestedIndex--;
783 }
784 }
785 }
786
787 Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
788// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
789 const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
790 const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
791 if (insertingInValidIndexes || firstItemWithViewOnTop)
792 {
793 // If the items we are adding won't be really visible
794 // we grow up instead of down to not change the viewport
795 bool growUp = false;
796 if (!firstItemWithViewOnTop) {
797 for (int i = 0; i < m_visibleItems.count(); ++i) {
798 if (!m_visibleItems[i]->culled()) {
799 if (insert.index <= m_firstVisibleIndex + i) {
800 growUp = true;
801 }
802 break;
803 }
804 }
805 }
806
807 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
808 for (int i = insert.count - 1; i >= 0; --i) {
809 const int modelIndex = insert.index + i;
810 ListItem *item = createItem(modelIndex, false);
811 if (growUp) {
812 ListItem *firstItem = m_visibleItems.first();
813 firstItem->setY(firstItem->y() - item->height());
814 adjustMinYExtent();
815 }
816 // Adding an item may break a "same section" chain, so check
817 // if we need adding a new section item
818 if (m_sectionDelegate) {
819 ListItem *nextItem = itemAtIndex(modelIndex + 1);
820 if (nextItem && !nextItem->m_sectionItem) {
821 nextItem->m_sectionItem = getSectionItem(modelIndex + 1, true /* alredy inserted into m_visibleItems*/);
822 if (growUp && nextItem->m_sectionItem) {
823 ListItem *firstItem = m_visibleItems.first();
824 firstItem->setY(firstItem->y() - nextItem->m_sectionItem->height());
825 adjustMinYExtent();
826 }
827 }
828 }
829 }
830 if (firstItemWithViewOnTop) {
831 ListItem *firstItem = m_visibleItems.first();
832 firstItem->setY(oldFirstValidIndexPos);
833 }
834 } else if (insert.index <= m_firstVisibleIndex) {
835 m_firstVisibleIndex += insert.count;
836 }
837
838 for (int i = insert.count - 1; i >= 0; --i) {
839 const int modelIndex = insert.index + i;
840 if (modelIndex <= m_asyncRequestedIndex) {
841 m_asyncRequestedIndex++;
842 }
843 }
844 }
845
846 if (m_firstVisibleIndex != oldFirstVisibleIndex) {
847 adjustMinYExtent();
848 }
849
850 layout();
851 polish();
852 m_contentHeightDirty = true;
853}
854
855void ListViewWithPageHeader::onShowHeaderAnimationFinished()
856{
857 m_contentHeightDirty = true;
858 polish();
859}
860
861void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QRectF &newGeometry, const QRectF &oldGeometry)
862{
863 const qreal heightDiff = newGeometry.height() - oldGeometry.height();
864 if (heightDiff != 0) {
865 if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
866 ListItem *firstItem = m_visibleItems.first();
867 firstItem->setY(firstItem->y() - heightDiff);
868 adjustMinYExtent();
869 layout();
870 }
871 refill();
872 adjustMinYExtent();
873 polish();
874 m_contentHeightDirty = true;
875 }
876}
877
878void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
879{
880 const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
881 if (m_headerItemShownHeight > 0) {
882 // If the header is shown because of the clip
883 // Change its size
884 m_headerItemShownHeight += heightDiff;
885 m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, newHeaderHeight);
886 updateClipItem();
887 adjustMinYExtent();
888 } else {
889 if (oldHeaderY + oldHeaderHeight > contentY()) {
890 // If the header is shown because its position
891 // Change its size
892 ListItem *firstItem = m_visibleItems.first();
893 firstItem->setY(firstItem->y() + heightDiff);
894 layout();
895 } else {
896 // If the header is not on screen, just change the start of the list
897 // so the viewport is not changed
898 adjustMinYExtent();
899 }
900 }
901}
902
903
904void ListViewWithPageHeader::adjustMinYExtent()
905{
906 if (m_visibleItems.isEmpty()) {
907 m_minYExtent = 0;
908 } else {
909 qreal nonCreatedHeight = 0;
910 if (m_firstVisibleIndex != 0) {
911 // Calculate the average height of items to estimate the position of the list start
912 const int visibleItems = m_visibleItems.count();
913 qreal visibleItemsHeight = 0;
914 Q_FOREACH(ListItem *item, m_visibleItems) {
915 visibleItemsHeight += item->height();
916 }
917 nonCreatedHeight = m_firstVisibleIndex * visibleItemsHeight / visibleItems;
918// qDebug() << m_firstVisibleIndex << visibleItemsHeight << visibleItems << nonCreatedHeight;
919 }
920 m_minYExtent = nonCreatedHeight - m_visibleItems.first()->y() - m_clipItem->y() + (m_headerItem ? m_headerItem->implicitHeight() : 0);
921 }
922}
923
924ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
925{
926 const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
927 if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
928 return m_visibleItems[visibleIndexedModelIndex];
929
930 return nullptr;
931}
932
933void ListViewWithPageHeader::layout()
934{
935 if (m_inLayout)
936 return;
937
938 m_inLayout = true;
939 if (!m_visibleItems.isEmpty()) {
940 const qreal visibleFrom = contentY() - m_clipItem->y() + m_headerItemShownHeight;
941 const qreal visibleTo = contentY() + height() - m_clipItem->y();
942
943 qreal pos = m_visibleItems.first()->y();
944
945// qDebug() << "ListViewWithPageHeader::layout Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
946 int firstReallyVisibleItem = -1;
947 int modelIndex = m_firstVisibleIndex;
948 Q_FOREACH(ListItem *item, m_visibleItems) {
949 const bool cull = pos + item->height() < visibleFrom || pos >= visibleTo;
950 item->setCulled(cull);
951 item->setY(pos);
952 if (!cull && firstReallyVisibleItem == -1) {
953 firstReallyVisibleItem = modelIndex;
954 if (m_topSectionItem) {
955 // Update the top sticky section header
956 const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
957 QQmlContext *context = QQmlEngine::contextForObject(m_topSectionItem)->parentContext();
958 context->setContextProperty(QLatin1String("section"), section);
959
960 // Positing the top section sticky item is a two step process
961 // First we set it either we cull it (because it doesn't need to be sticked to the top)
962 // or stick it to the top
963 // Then after the loop we'll make sure that if there's another section just below it
964 // pushed the sticky section up to make it disappear
965 const qreal topSectionStickPos = m_headerItemShownHeight + contentY() - m_clipItem->y();
966 if (topSectionStickPos <= pos) {
967 QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
968 if (item->m_sectionItem) {
969 // This seems it should happen since why would we cull the top section
970 // if the first visible item has no section header? This only happens briefly
971 // when increasing the height of a list that is at the bottom, the m_topSectionItem
972 // gets shown shortly in the next polish call
973 QQuickItemPrivate::get(item->m_sectionItem)->setCulled(false);
974 }
975 } else {
976 QQuickItemPrivate::get(m_topSectionItem)->setCulled(false);
977 m_topSectionItem->setY(topSectionStickPos);
978 if (item->m_sectionItem) {
979 QQuickItemPrivate::get(item->m_sectionItem)->setCulled(true);
980 }
981 }
982 }
983 }
984 QQmlContext *context = QQmlEngine::contextForObject(item->m_item)->parentContext();
985 const qreal clipFrom = visibleFrom + (!item->m_sectionItem && m_topSectionItem && !QQuickItemPrivate::get(m_topSectionItem)->culled ? m_topSectionItem->height() : 0);
986 if (!cull && pos < clipFrom) {
987 context->setContextProperty(QLatin1String("heightToClip"), clipFrom - pos);
988 } else {
989 context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
990 }
991// qDebug() << "ListViewWithPageHeader::layout" << item->m_item;
992 pos += item->height();
993 ++modelIndex;
994 }
995
996 // Second step of section sticky item positioning
997 // Look at the next section header, check if it's pushing up the sticky one
998 if (m_topSectionItem) {
999 if (firstReallyVisibleItem >= 0) {
1000 for (int i = firstReallyVisibleItem - m_firstVisibleIndex + 1; i < m_visibleItems.count(); ++i) {
1001 ListItem *item = m_visibleItems[i];
1002 if (item->m_sectionItem) {
1003 if (m_topSectionItem->y() + m_topSectionItem->height() > item->y()) {
1004 m_topSectionItem->setY(item->y() - m_topSectionItem->height());
1005 }
1006 break;
1007 }
1008 }
1009 }
1010 }
1011 }
1012 m_inLayout = false;
1013}
1014
1015void ListViewWithPageHeader::updatePolish()
1016{
1017 Q_FOREACH(ListItem *item, m_itemsToRelease)
1018 reallyReleaseItem(item);
1019 m_itemsToRelease.clear();
1020
1021 if (!model())
1022 return;
1023
1024 layout();
1025
1026 refill();
1027
1028 if (m_contentHeightDirty) {
1029 qreal contentHeight;
1030 if (m_visibleItems.isEmpty()) {
1031 contentHeight = m_headerItem ? m_headerItem->height() : 0;
1032 } else {
1033 const int modelCount = model()->rowCount();
1034 const int visibleItems = m_visibleItems.count();
1035 const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
1036 qreal nonCreatedHeight = 0;
1037 if (lastValidIndex != modelCount - 1) {
1038 const int visibleItems = m_visibleItems.count();
1039 qreal visibleItemsHeight = 0;
1040 Q_FOREACH(ListItem *item, m_visibleItems) {
1041 visibleItemsHeight += item->height();
1042 }
1043 const int unknownSizes = modelCount - (m_firstVisibleIndex + visibleItems);
1044 nonCreatedHeight = unknownSizes * visibleItemsHeight / visibleItems;
1045 }
1046 ListItem *item = m_visibleItems.last();
1047 contentHeight = nonCreatedHeight + item->y() + item->height() + m_clipItem->y();
1048 if (m_firstVisibleIndex != 0) {
1049 // Make sure that if we are shrinking we tell the view we still fit
1050 m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
1051 }
1052 }
1053
1054 m_contentHeightDirty = false;
1055 setContentHeight(contentHeight);
1056 }
1057}
1058
1059#include "moc_listviewwithpageheader.cpp"
01060
=== added file 'plugins/ListViewWithPageHeader/listviewwithpageheader.h'
--- plugins/ListViewWithPageHeader/listviewwithpageheader.h 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/listviewwithpageheader.h 2013-07-01 11:23:32 +0000
@@ -0,0 +1,180 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#ifndef LISTVIEWWITHPAGEHEADER_H
18#define LISTVIEWWITHPAGEHEADER_H
19
20#include <private/qquickitemchangelistener_p.h>
21#include <private/qquickflickable_p.h>
22
23class QAbstractItemModel;
24class QQuickChangeSet;
25class QQuickNumberAnimation;
26class QQuickVisualDataModel;
27
28
29/**
30 Note for users of this class
31
32 ListViewWithPageHeader already loads delegates async when appropiate so if
33 your delegate uses a Loader you should not enable the asynchronous feature since
34 that will need to introduce sizing problems
35
36 With the double async it may happen what while we are scrolling down
37 we reach to a point where given the size of the just created delegate with loader not yet loaded (which will be very close to 0)
38 we are already "at the end" of the list, but then a few milliseconds later the loader finishes loading and we could
39 have kept scrolling. This is specially visible at the end of the list where you realize
40 that scrolling ended a bit before the end of the list but the speed of the flicking was good
41 to reach the end
42
43 By not having the second async we get a better sizing when the delegate is created and things work better
44*/
45
46class ListViewWithPageHeader : public QQuickFlickable, public QQuickItemChangeListener
47{
48 Q_OBJECT
49 Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
50 Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
51 Q_PROPERTY(QQuickItem *pageHeader READ header WRITE setHeader NOTIFY headerChanged)
52 Q_PROPERTY(QQmlComponent *sectionDelegate READ sectionDelegate WRITE setSectionDelegate NOTIFY sectionDelegateChanged)
53 Q_PROPERTY(QString sectionProperty READ sectionProperty WRITE setSectionProperty NOTIFY sectionPropertyChanged)
54 Q_PROPERTY(bool forceNoClip READ forceNoClip WRITE setForceNoClip NOTIFY forceNoClipChanged)
55
56 friend class ListViewWithPageHeaderTest;
57 friend class ListViewWithPageHeaderTestSection;
58
59public:
60 ListViewWithPageHeader();
61 ~ListViewWithPageHeader();
62
63 QAbstractItemModel *model() const;
64 void setModel(QAbstractItemModel *model);
65
66 QQmlComponent *delegate() const;
67 void setDelegate(QQmlComponent *delegate);
68
69 QQuickItem *header() const;
70 void setHeader(QQuickItem *header);
71
72 QQmlComponent *sectionDelegate() const;
73 void setSectionDelegate(QQmlComponent *delegate);
74
75 QString sectionProperty() const;
76 void setSectionProperty(const QString &property);
77
78 bool forceNoClip() const;
79 void setForceNoClip(bool noClip);
80
81 Q_INVOKABLE void positionAtBeginning();
82 Q_INVOKABLE void showHeader();
83
84Q_SIGNALS:
85 void modelChanged();
86 void delegateChanged();
87 void headerChanged();
88 void sectionDelegateChanged();
89 void sectionPropertyChanged();
90 void forceNoClipChanged();
91
92protected:
93 void componentComplete();
94 void viewportMoved(Qt::Orientations orient);
95 qreal minYExtent() const;
96 void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry);
97 void updatePolish();
98
99private Q_SLOTS:
100 void itemCreated(int modelIndex, QQuickItem *item);
101 void onContentHeightChanged();
102 void onContentWidthChanged();
103 void onHeightChanged();
104 void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
105 void onShowHeaderAnimationFinished();
106
107private:
108 class ListItem
109 {
110 public:
111 qreal height() const;
112
113 qreal y() const;
114 void setY(qreal newY);
115
116 bool culled() const;
117 void setCulled(bool culled);
118
119 QQuickItem *m_item;
120 QQuickItem *m_sectionItem;
121 };
122
123 void createDelegateModel();
124
125 void layout();
126 void refill();
127 bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
128 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
129 ListItem *createItem(int modelIndex, bool asynchronous);
130
131 void adjustMinYExtent();
132 void updateClipItem();
133 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
134 ListItem *itemAtIndex(int modelIndex) const; // Returns the item at modelIndex if has been created
135 void releaseItem(ListItem *item);
136 void reallyReleaseItem(ListItem *item);
137 void updateWatchedRoles();
138 QQuickItem *getSectionItem(int modelIndex, bool alreadyInserted);
139 QQuickItem *getSectionItem(const QString &sectionText);
140
141 QQuickVisualDataModel *m_delegateModel;
142
143 // Index we are waiting because we requested it asynchronously
144 int m_asyncRequestedIndex;
145
146 // Used to only give a warning once if the delegate does not return objects
147 bool m_delegateValidated;
148
149 // Visible indexes, [0] is m_firstValidIndex, [0+1] is m_firstValidIndex +1, ...
150 QList<ListItem *> m_visibleItems;
151 int m_firstVisibleIndex;
152
153 qreal m_minYExtent;
154
155 QQuickItem *m_clipItem;
156
157 // If any of the heights has changed
158 // or new items have been added/removed
159 bool m_contentHeightDirty;
160
161 QQuickItem *m_headerItem;
162 qreal m_previousContentY;
163 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
164 // i.e. it's being shown after dragging down in the middle of the list
165 QQuickNumberAnimation *m_headerShowAnimation;
166
167 QQmlComponent *m_sectionDelegate;
168 QString m_sectionProperty;
169 QQuickItem *m_topSectionItem;
170
171 bool m_forceNoClip;
172 bool m_inLayout;
173
174 // Qt 5.0 doesn't like releasing the items just after itemCreated
175 // so we delay the releasing until the next updatePolish
176 QList<ListItem *> m_itemsToRelease;
177};
178
179
180#endif
0181
=== added file 'plugins/ListViewWithPageHeader/plugin.cpp'
--- plugins/ListViewWithPageHeader/plugin.cpp 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/plugin.cpp 2013-07-01 11:23:32 +0000
@@ -0,0 +1,29 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#include "plugin.h"
19
20#include "listviewwithpageheader.h"
21
22#include <QAbstractItemModel>
23
24void ListViewWithPageHeaderPlugin::registerTypes(const char *uri)
25{
26 Q_ASSERT(uri == QLatin1String("ListViewWithPageHeader"));
27 qmlRegisterType<QAbstractItemModel>();
28 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
29}
030
=== added file 'plugins/ListViewWithPageHeader/plugin.h'
--- plugins/ListViewWithPageHeader/plugin.h 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/plugin.h 2013-07-01 11:23:32 +0000
@@ -0,0 +1,33 @@
1/*
2 * Copyright (C) 2012 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef LISTVIEWWITHPAGEHEADER_PLUGIN_H
19#define LISTVIEWWITHPAGEHEADER_PLUGIN_H
20
21#include <QtQml/QQmlEngine>
22#include <QtQml/QQmlExtensionPlugin>
23
24class ListViewWithPageHeaderPlugin : public QQmlExtensionPlugin
25{
26 Q_OBJECT
27 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
28
29public:
30 void registerTypes(const char *uri);
31};
32
33#endif
034
=== added file 'plugins/ListViewWithPageHeader/qmldir'
--- plugins/ListViewWithPageHeader/qmldir 1970-01-01 00:00:00 +0000
+++ plugins/ListViewWithPageHeader/qmldir 2013-07-01 11:23:32 +0000
@@ -0,0 +1,2 @@
1module ListViewWithPageHeader
2plugin ListViewWithPageHeader-qml
03
=== modified file 'run_on_device'
--- run_on_device 2013-06-24 11:08:38 +0000
+++ run_on_device 2013-07-01 11:23:32 +0000
@@ -75,7 +75,7 @@
75sync_code() {75sync_code() {
76 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR76 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
77 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/77 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
78 rsync -crlOzv -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/78 rsync -crlOzv --delete --exclude builddir -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
79 rm -rf /tmp/$CODE_DIR79 rm -rf /tmp/$CODE_DIR
80}80}
8181
8282
=== modified file 'tests/plugins/CMakeLists.txt'
--- tests/plugins/CMakeLists.txt 2013-06-05 22:03:08 +0000
+++ tests/plugins/CMakeLists.txt 2013-07-01 11:23:32 +0000
@@ -1,2 +1,3 @@
1add_subdirectory(Utils)1add_subdirectory(Utils)
2add_subdirectory(Ubuntu)2add_subdirectory(Ubuntu)
3add_subdirectory(ListViewWithPageHeader)
34
=== added directory 'tests/plugins/ListViewWithPageHeader'
=== added file 'tests/plugins/ListViewWithPageHeader/CMakeLists.txt'
--- tests/plugins/ListViewWithPageHeader/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/plugins/ListViewWithPageHeader/CMakeLists.txt 2013-07-01 11:23:32 +0000
@@ -0,0 +1,31 @@
1# There's no cmake var for v8 include path :-/
2# so create one
3LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
4SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
5
6include_directories(
7 ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
8 ${Qt5Quick_INCLUDE_DIRS}
9 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10 ${Qt5V8_PRIVATE_INCLUDE_DIR}
11 ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader
12 ${CMAKE_CURRENT_BINARY_DIR}
13 )
14
15remove_definitions(-DQT_NO_KEYWORDS)
16
17add_definitions(-DLISTVIEWWITHPAGEHEADER_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}")
18add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
19
20add_executable(listviewwithpageheadertestExec listviewwithpageheadertest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
21qt5_use_modules(listviewwithpageheadertestExec Test Core Qml)
22target_link_libraries(listviewwithpageheadertestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
23add_custom_target(testlistviewwithpageheader ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheader.xml,xunitxml -o -,txt)
24
25add_executable(listviewwithpageheadertestsectionExec listviewwithpageheadertestsection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/ListViewWithPageHeader/listviewwithpageheader.cpp)
26qt5_use_modules(listviewwithpageheadertestsectionExec Test Core Qml)
27target_link_libraries(listviewwithpageheadertestsectionExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
28add_custom_target(testlistviewwithpageheadersection ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersection.xml,xunitxml -o -,txt)
29
30add_dependencies(qmluitests testlistviewwithpageheader)
31add_dependencies(qmluitests testlistviewwithpageheadersection)
032
=== added file 'tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp'
--- tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp 1970-01-01 00:00:00 +0000
+++ tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp 2013-07-01 11:23:32 +0000
@@ -0,0 +1,1311 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "listviewwithpageheader.h"
18
19#include <QAbstractItemModel>
20#include <QQmlEngine>
21#include <QQuickView>
22#include <QtTestGui>
23#include <private/qquicklistmodel_p.h>
24#include <private/qquickanimation_p.h>
25#include <private/qquickitem_p.h>
26
27class ListViewWithPageHeaderTest : public QObject
28{
29 Q_OBJECT
30
31private:
32 void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled)
33 {
34 ListViewWithPageHeader::ListItem *item = lvwph->m_visibleItems[visibleIndex];
35 QTRY_COMPARE(item->y(), pos);
36 QTRY_COMPARE(item->height(), height);
37 QCOMPARE(QQuickItemPrivate::get(item->m_item)->culled, culled);
38 }
39
40 void changeContentY(qreal change)
41 {
42 const qreal dest = lvwph->contentY() + change;
43 if (dest > lvwph->contentY()) {
44 const qreal jump = 25;
45 while (lvwph->contentY() + jump < dest) {
46 lvwph->setContentY(lvwph->contentY() + jump);
47 QTest::qWait(1);
48 }
49 } else {
50 const qreal jump = -25;
51 while (lvwph->contentY() + jump > dest) {
52 lvwph->setContentY(lvwph->contentY() + jump);
53 QTest::qWait(1);
54 }
55 }
56 lvwph->setContentY(dest);
57 QTest::qWait(1);
58 }
59
60 void scrollToTop()
61 {
62 const qreal jump = -25;
63 while (!lvwph->isAtYBeginning()) {
64 if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
65 lvwph->setContentY(lvwph->contentY() + jump);
66 } else {
67 lvwph->setContentY(lvwph->contentY() - 1);
68 }
69 QTest::qWait(1);
70 }
71 }
72
73 void scrollToBottom()
74 {
75 const qreal jump = 25;
76 while (!lvwph->isAtYEnd()) {
77 if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
78 lvwph->setContentY(lvwph->contentY() + jump);
79 } else {
80 lvwph->setContentY(lvwph->contentY() + 1);
81 }
82 QTest::qWait(1);
83 }
84 }
85
86 void verifyInitialTopPosition()
87 {
88 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
89 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
90 verifyItem(0, 50., 150., false);
91 verifyItem(1, 200., 200., false);
92 verifyItem(2, 400., 350., false);
93 verifyItem(3, 750., 350., true);
94 QCOMPARE(lvwph->m_minYExtent, 0.);
95 QCOMPARE(lvwph->m_clipItem->y(), 0.);
96 QCOMPARE(lvwph->m_clipItem->clip(), false);
97 QCOMPARE(lvwph->m_headerItem->y(), 0.);
98 QCOMPARE(lvwph->m_headerItem->height(), 50.);
99 QCOMPARE(lvwph->contentY(), 0.);
100 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
101 }
102
103private Q_SLOTS:
104
105 void initTestCase()
106 {
107 }
108
109 void init()
110 {
111 view = new QQuickView();
112 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
113 view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test.qml"));
114 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
115 model = view->rootObject()->findChild<QQuickListModel*>();
116 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
117 QVERIFY(lvwph);
118 QVERIFY(model);
119 QVERIFY(otherDelegate);
120 view->show();
121 QTest::qWaitForWindowExposed(view);
122
123 verifyInitialTopPosition();
124 }
125
126 void cleanup()
127 {
128 delete view;
129 }
130
131 void testCreationDeletion()
132 {
133 // Nothing, init/cleanup already tests this
134 }
135
136 void testDrag1PixelUp()
137 {
138 lvwph->setContentY(lvwph->contentY() + 1);
139 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
140 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
141 verifyItem(0, 49., 150., false);
142 verifyItem(1, 199., 200., false);
143 verifyItem(2, 399., 350., false);
144 verifyItem(3, 749., 350., true);
145 QCOMPARE(lvwph->m_minYExtent, 0.);
146 QCOMPARE(lvwph->m_clipItem->y(), 1.);
147 QCOMPARE(lvwph->m_clipItem->clip(), false);
148 QCOMPARE(lvwph->m_headerItem->y(), 0.);
149 QCOMPARE(lvwph->m_headerItem->height(), 50.);
150 QCOMPARE(lvwph->contentY(), 1.);
151 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
152 }
153
154 void testHeaderDetachDragDown()
155 {
156 QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
157 QTest::qWait(100);
158 QTest::mouseMove(view, QPoint(0, 5));
159 QTest::qWait(100);
160 QTest::mouseMove(view, QPoint(0, 10));
161 QTest::qWait(100);
162 QTest::mouseMove(view, QPoint(0, 15));
163 QTest::qWait(100);
164 QTest::mouseMove(view, QPoint(0, 20));
165 QTest::qWait(100);
166 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
167 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
168 verifyItem(0, 55., 150., false);
169 verifyItem(1, 205., 200., false);
170 verifyItem(2, 405., 350., false);
171 verifyItem(3, 755., 350., true);
172 QCOMPARE(lvwph->m_minYExtent, 0.);
173 QCOMPARE(lvwph->m_clipItem->y(), -5.);
174 QCOMPARE(lvwph->m_clipItem->clip(), false);
175 QCOMPARE(lvwph->m_headerItem->y(), -5.);
176 QCOMPARE(lvwph->m_headerItem->height(), 55.);
177 QCOMPARE(lvwph->contentY(), -5.);
178 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
179
180 QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
181
182 verifyInitialTopPosition();
183 }
184
185 void testDrag375PixelUp()
186 {
187 changeContentY(375);
188 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
189 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
190 verifyItem(0, -325., 150., true);
191 verifyItem(1, -175, 200., false);
192 verifyItem(2, 25, 350., false);
193 verifyItem(3, 375, 350., false);
194 verifyItem(4, 725, 350., true);
195 QCOMPARE(lvwph->m_minYExtent, 0.);
196 QCOMPARE(lvwph->m_clipItem->y(), 375.);
197 QCOMPARE(lvwph->m_clipItem->clip(), false);
198 QCOMPARE(lvwph->m_headerItem->y(), 0.);
199 QCOMPARE(lvwph->m_headerItem->height(), 50.);
200 QCOMPARE(lvwph->contentY(), 375.);
201 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
202 }
203
204 void testDrag520PixelUp()
205 {
206 changeContentY(520);
207 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
208 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
209 verifyItem(0, -320., 200., true);
210 verifyItem(1, -120, 350., false);
211 verifyItem(2, 230, 350., false);
212 verifyItem(3, 580, 350., true);
213 // Just here as first check against m_minYExtent when m_firstVisibleIndex is not 0
214 // We as humans know that m_minYExtent will be 0 but since the first delegate is not there anymore
215 // we have to estimate its size, the average item height is 312.5 which is 162.5 more than the
216 // "real" size of 150 and that's why the m_minYExtent has that "peculiar" value
217 // It's fine since what it means is that we could scroll more up than the original position
218 // but we will recalculate m_minYExtent when the item 0 is created and set it correctly
219 QCOMPARE(lvwph->m_minYExtent, 162.5);
220 QCOMPARE(lvwph->m_clipItem->y(), 520.);
221 QCOMPARE(lvwph->m_clipItem->clip(), false);
222 QCOMPARE(lvwph->m_headerItem->y(), 0.);
223 QCOMPARE(lvwph->m_headerItem->height(), 50.);
224 QCOMPARE(lvwph->contentY(), 520.);
225 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
226 }
227
228 void testDragHeaderUpThenShow()
229 {
230 changeContentY(120);
231 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
232 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
233 verifyItem(0, -70., 150., false);
234 verifyItem(1, 80., 200., false);
235 verifyItem(2, 280., 350., false);
236 verifyItem(3, 630., 350., true);
237 QCOMPARE(lvwph->m_minYExtent, 0.);
238 QCOMPARE(lvwph->m_clipItem->y(), 120.);
239 QCOMPARE(lvwph->m_clipItem->clip(), false);
240 QCOMPARE(lvwph->m_headerItem->y(), 0.);
241 QCOMPARE(lvwph->m_headerItem->height(), 50.);
242 QCOMPARE(lvwph->contentY(), 120.);
243 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
244
245 changeContentY(-30);
246 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
247 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
248 verifyItem(0, -70., 150., false);
249 verifyItem(1, 80., 200., false);
250 verifyItem(2, 280., 350., false);
251 verifyItem(3, 630., 350., true);
252 QCOMPARE(lvwph->m_minYExtent, 0.);
253 QCOMPARE(lvwph->m_clipItem->y(), 120.);
254 QCOMPARE(lvwph->m_clipItem->clip(), true);
255 QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
256 QCOMPARE(lvwph->m_headerItem->height(), 50.);
257 QCOMPARE(lvwph->contentY(), 90.);
258 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
259 }
260
261 void testDragHeaderUpThenShowWithoutHidingTotally()
262 {
263 changeContentY(10);
264 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
265 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
266 verifyItem(0, 40., 150., false);
267 verifyItem(1, 190., 200., false);
268 verifyItem(2, 390., 350., false);
269 verifyItem(3, 740., 350., true);
270 QCOMPARE(lvwph->m_minYExtent, 0.);
271 QCOMPARE(lvwph->m_clipItem->y(), 10.);
272 QCOMPARE(lvwph->m_clipItem->clip(), false);
273 QCOMPARE(lvwph->m_headerItem->y(), 0.);
274 QCOMPARE(lvwph->m_headerItem->height(), 50.);
275 QCOMPARE(lvwph->contentY(), 10.);
276 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
277
278 changeContentY(-1);
279
280 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
281 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
282 verifyItem(0, 41., 150., false);
283 verifyItem(1, 191., 200., false);
284 verifyItem(2, 391., 350., false);
285 verifyItem(3, 741., 350., true);
286 QCOMPARE(lvwph->m_minYExtent, 0.);
287 QCOMPARE(lvwph->m_clipItem->y(), 9.);
288 QCOMPARE(lvwph->m_clipItem->clip(), false);
289 QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
290 QCOMPARE(lvwph->m_headerItem->height(), 50.);
291 QCOMPARE(lvwph->contentY(), 9.);
292 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
293 }
294
295 void testPositionAtBeginningIndex0Visible()
296 {
297 changeContentY(375);
298 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
299 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
300 verifyItem(0, -325., 150., true);
301 verifyItem(1, -175, 200., false);
302 verifyItem(2, 25, 350., false);
303 verifyItem(3, 375, 350., false);
304 verifyItem(4, 725, 350., true);
305 QCOMPARE(lvwph->m_minYExtent, 0.);
306 QCOMPARE(lvwph->m_clipItem->y(), 375.);
307 QCOMPARE(lvwph->m_clipItem->clip(), false);
308 QCOMPARE(lvwph->m_headerItem->y(), 0.);
309 QCOMPARE(lvwph->m_headerItem->height(), 50.);
310 QCOMPARE(lvwph->contentY(), 375.);
311 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
312
313 lvwph->positionAtBeginning();
314
315 verifyInitialTopPosition();
316 }
317
318 void testPositionAtBeginningIndex0NotVisible()
319 {
320 changeContentY(520);
321 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
322 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
323 verifyItem(0, -320., 200., true);
324 verifyItem(1, -120, 350., false);
325 verifyItem(2, 230, 350., false);
326 verifyItem(3, 580, 350., true);
327 QCOMPARE(lvwph->m_minYExtent, 162.5);
328 QCOMPARE(lvwph->m_clipItem->y(), 520.);
329 QCOMPARE(lvwph->m_clipItem->clip(), false);
330 QCOMPARE(lvwph->m_headerItem->y(), 0.);
331 QCOMPARE(lvwph->m_headerItem->height(), 50.);
332 QCOMPARE(lvwph->contentY(), 520.);
333 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
334
335 lvwph->positionAtBeginning();
336
337 verifyInitialTopPosition();
338 }
339
340 void testIndex0GrowOnScreen()
341 {
342 model->setProperty(0, "size", 400);
343
344 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
345 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
346 verifyItem(0, 50., 400., false);
347 verifyItem(1, 450., 200., false);
348 verifyItem(2, 650., 350., true);
349 QCOMPARE(lvwph->m_minYExtent, 0.);
350 QCOMPARE(lvwph->m_clipItem->y(), 0.);
351 QCOMPARE(lvwph->m_clipItem->clip(), false);
352 QCOMPARE(lvwph->m_headerItem->y(), 0.);
353 QCOMPARE(lvwph->m_headerItem->height(), 50.);
354 QCOMPARE(lvwph->contentY(), 0.);
355 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
356 }
357
358 void testIndex0GrowOffScreen()
359 {
360 changeContentY(375);
361 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
362 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
363 verifyItem(0, -325., 150., true);
364 verifyItem(1, -175, 200., false);
365 verifyItem(2, 25, 350., false);
366 verifyItem(3, 375, 350., false);
367 verifyItem(4, 725, 350., true);
368 QCOMPARE(lvwph->m_minYExtent, 0.);
369 QCOMPARE(lvwph->m_clipItem->y(), 375.);
370 QCOMPARE(lvwph->m_clipItem->clip(), false);
371 QCOMPARE(lvwph->m_headerItem->y(), 0.);
372 QCOMPARE(lvwph->m_headerItem->height(), 50.);
373 QCOMPARE(lvwph->contentY(), 375.);
374 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
375
376 model->setProperty(0, "size", 400);
377
378 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
379 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
380 verifyItem(0, -575., 400., true);
381 verifyItem(1, -175, 200., false);
382 verifyItem(2, 25, 350., false);
383 verifyItem(3, 375, 350., false);
384 verifyItem(4, 725, 350., true);
385 QCOMPARE(lvwph->m_minYExtent, 250.);
386 QCOMPARE(lvwph->m_clipItem->y(), 375.);
387 QCOMPARE(lvwph->m_clipItem->clip(), false);
388 QCOMPARE(lvwph->m_headerItem->y(), 0.);
389 QCOMPARE(lvwph->m_headerItem->height(), 50.);
390 QCOMPARE(lvwph->contentY(), 375.);
391 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
392
393 scrollToTop();
394
395 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
396 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
397 verifyItem(0, 50., 400., false);
398 verifyItem(1, 450, 200., false);
399 verifyItem(2, 650, 350., true);
400 QCOMPARE(lvwph->m_minYExtent, 250.);
401 QCOMPARE(lvwph->m_clipItem->y(), -250.);
402 QCOMPARE(lvwph->m_clipItem->clip(), false);
403 QCOMPARE(lvwph->m_headerItem->y(), -250.);
404 QCOMPARE(lvwph->m_headerItem->height(), 50.);
405 QCOMPARE(lvwph->contentY(), -250.);
406 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
407
408 changeContentY(30);
409
410 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
411 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
412 verifyItem(0, 20., 400., false);
413 verifyItem(1, 420, 200., false);
414 verifyItem(2, 620, 350., true);
415 QCOMPARE(lvwph->m_minYExtent, 250.);
416 QCOMPARE(lvwph->m_clipItem->y(), -220.);
417 QCOMPARE(lvwph->m_clipItem->clip(), false);
418 QCOMPARE(lvwph->m_headerItem->y(), -250.);
419 QCOMPARE(lvwph->m_headerItem->height(), 50.);
420 QCOMPARE(lvwph->contentY(), -220.);
421 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
422 }
423
424 void testIndex0GrowNotCreated()
425 {
426 changeContentY(520);
427 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
428 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
429 verifyItem(0, -320., 200., true);
430 verifyItem(1, -120, 350., false);
431 verifyItem(2, 230, 350., false);
432 verifyItem(3, 580, 350., true);
433 QCOMPARE(lvwph->m_minYExtent, 162.5);
434 QCOMPARE(lvwph->m_clipItem->y(), 520.);
435 QCOMPARE(lvwph->m_clipItem->clip(), false);
436 QCOMPARE(lvwph->m_headerItem->y(), 0.);
437 QCOMPARE(lvwph->m_headerItem->height(), 50.);
438 QCOMPARE(lvwph->contentY(), 520.);
439 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
440
441 model->setProperty(0, "size", 400);
442
443 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
444 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
445 verifyItem(0, -320., 200., true);
446 verifyItem(1, -120, 350., false);
447 verifyItem(2, 230, 350., false);
448 verifyItem(3, 580, 350., true);
449 QCOMPARE(lvwph->m_minYExtent, 162.5);
450 QCOMPARE(lvwph->m_clipItem->y(), 520.);
451 QCOMPARE(lvwph->m_clipItem->clip(), false);
452 QCOMPARE(lvwph->m_headerItem->y(), 0.);
453 QCOMPARE(lvwph->m_headerItem->height(), 50.);
454 QCOMPARE(lvwph->contentY(), 520.);
455 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
456
457 scrollToTop();
458
459 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
460 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
461 verifyItem(0, 50., 400., false);
462 verifyItem(1, 450, 200., false);
463 verifyItem(2, 650, 350., true);
464 QCOMPARE(lvwph->m_minYExtent, 250.);
465 QCOMPARE(lvwph->m_clipItem->y(), -250.);
466 QCOMPARE(lvwph->m_clipItem->clip(), false);
467 QCOMPARE(lvwph->m_headerItem->y(), -250.);
468 QCOMPARE(lvwph->m_headerItem->height(), 50.);
469 QCOMPARE(lvwph->contentY(), -250.);
470 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
471 }
472
473 void testShowHideShowHeaderAtBottom()
474 {
475 scrollToBottom();
476 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
477 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
478 verifyItem(0, -508., 350., true);
479 verifyItem(1, -158, 350., false);
480 verifyItem(2, 192, 350., false);
481 QCOMPARE(lvwph->m_minYExtent, 350.);
482 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
483 QCOMPARE(lvwph->m_clipItem->clip(), false);
484 QCOMPARE(lvwph->m_headerItem->y(), 0.);
485 QCOMPARE(lvwph->m_headerItem->height(), 50.);
486 QCOMPARE(lvwph->contentY(), 1258.);
487 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
488
489 changeContentY(-30);
490 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
491 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
492 verifyItem(0, -508., 350., true);
493 verifyItem(1, -158, 350., false);
494 verifyItem(2, 192, 350., false);
495 QCOMPARE(lvwph->m_minYExtent, 350.);
496 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
497 QCOMPARE(lvwph->m_clipItem->clip(), true);
498 QCOMPARE(lvwph->m_headerItem->y(), 1208.);
499 QCOMPARE(lvwph->m_headerItem->height(), 50.);
500 QCOMPARE(lvwph->contentY(), 1228.);
501 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
502
503 changeContentY(30);
504 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
505 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
506 verifyItem(0, -508., 350., true);
507 verifyItem(1, -158, 350., false);
508 verifyItem(2, 192, 350., false);
509 QCOMPARE(lvwph->m_minYExtent, 350.);
510 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
511 QCOMPARE(lvwph->m_clipItem->clip(), false);
512 QCOMPARE(lvwph->m_headerItem->y(), -350.);
513 QCOMPARE(lvwph->m_headerItem->height(), 50.);
514 QCOMPARE(lvwph->contentY(), 1258.);
515 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
516
517 changeContentY(-30);
518 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
519 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
520 verifyItem(0, -508., 350., true);
521 verifyItem(1, -158, 350., false);
522 verifyItem(2, 192, 350., false);
523 QCOMPARE(lvwph->m_minYExtent, 350.);
524 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
525 QCOMPARE(lvwph->m_clipItem->clip(), true);
526 QCOMPARE(lvwph->m_headerItem->y(), 1208.);
527 QCOMPARE(lvwph->m_headerItem->height(), 50.);
528 QCOMPARE(lvwph->contentY(), 1228.);
529 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
530 }
531
532 void testChangeDelegateAtBottom()
533 {
534 scrollToBottom();
535 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
536 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
537 verifyItem(0, -508., 350., true);
538 verifyItem(1, -158, 350., false);
539 verifyItem(2, 192, 350., false);
540 QCOMPARE(lvwph->m_minYExtent, 350.);
541 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
542 QCOMPARE(lvwph->m_clipItem->clip(), false);
543 QCOMPARE(lvwph->m_headerItem->y(), 0.);
544 QCOMPARE(lvwph->m_headerItem->height(), 50.);
545 QCOMPARE(lvwph->contentY(), 1258.);
546 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
547
548 lvwph->setDelegate(otherDelegate);
549
550 QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
551 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
552 verifyItem(0, 50., 35., false);
553 verifyItem(1, 85, 35., false);
554 verifyItem(2, 120, 35., false);
555 verifyItem(3, 155, 35., false);
556 verifyItem(4, 190, 35., false);
557 verifyItem(5, 225, 35., false);
558 QCOMPARE(lvwph->m_minYExtent, 0.);
559 QCOMPARE(lvwph->m_clipItem->y(), 0.);
560 QCOMPARE(lvwph->m_clipItem->clip(), false);
561 QCOMPARE(lvwph->m_headerItem->y(), 0.);
562 QCOMPARE(lvwph->m_headerItem->height(), 50.);
563 QCOMPARE(lvwph->contentY(), 0.);
564 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
565 }
566
567 void testSetEmptyHeaderAtTop()
568 {
569 lvwph->setHeader(nullptr);
570
571 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
572 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
573 verifyItem(0, 0., 150., false);
574 verifyItem(1, 150., 200., false);
575 verifyItem(2, 350., 350., false);
576 verifyItem(3, 700., 350., true);
577 QCOMPARE(lvwph->m_minYExtent, 0.);
578 QCOMPARE(lvwph->m_clipItem->y(), 0.);
579 QCOMPARE(lvwph->m_clipItem->clip(), false);
580 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
581 QCOMPARE(lvwph->contentY(), 0.);
582 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
583 }
584
585 void testSetEmptyHeaderAtBottom()
586 {
587 scrollToBottom();
588 lvwph->setHeader(nullptr);
589
590 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
591 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
592 verifyItem(0, -508., 350., true);
593 verifyItem(1, -158, 350., false);
594 verifyItem(2, 192, 350., false);
595 QCOMPARE(lvwph->m_minYExtent, 300.);
596 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
597 QCOMPARE(lvwph->m_clipItem->clip(), false);
598 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
599 QCOMPARE(lvwph->contentY(), 1258.);
600 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
601
602 scrollToTop();
603
604 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
605 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
606 verifyItem(0, 0., 150., false);
607 verifyItem(1, 150., 200., false);
608 verifyItem(2, 350., 350., false);
609 verifyItem(3, 700., 350., true);
610 QCOMPARE(lvwph->m_minYExtent, -50.);
611 QCOMPARE(lvwph->m_clipItem->y(), 50.);
612 QCOMPARE(lvwph->m_clipItem->clip(), false);
613 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
614 QCOMPARE(lvwph->contentY(), 50.);
615 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
616 }
617
618 void testSetEmptyHeaderWhenPartlyShownClipped()
619 {
620 scrollToBottom();
621 changeContentY(-30);
622 lvwph->setHeader(nullptr);
623
624 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
625 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
626 verifyItem(0, -508., 350., true);
627 verifyItem(1, -158, 350., false);
628 verifyItem(2, 192, 350., false);
629 QCOMPARE(lvwph->m_minYExtent, 330.);
630 QCOMPARE(lvwph->m_clipItem->y(), 1228.);
631 QCOMPARE(lvwph->m_clipItem->clip(), false);
632 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
633 QCOMPARE(lvwph->contentY(), 1228.);
634 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
635 QTRY_VERIFY(lvwph->isAtYEnd());
636
637 scrollToTop();
638
639 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
640 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
641 verifyItem(0, 0., 150., false);
642 verifyItem(1, 150., 200., false);
643 verifyItem(2, 350., 350., false);
644 verifyItem(3, 700., 350., true);
645 QCOMPARE(lvwph->m_minYExtent, -20.);
646 QCOMPARE(lvwph->m_clipItem->y(), 20.);
647 QCOMPARE(lvwph->m_clipItem->clip(), false);
648 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
649 QCOMPARE(lvwph->contentY(), 20.);
650 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
651 }
652
653 void testSetEmptyHeaderWhenPartlyShownNotClipped()
654 {
655 changeContentY(30);
656 lvwph->setHeader(nullptr);
657
658 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
659 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
660 verifyItem(0, -30., 150., false);
661 verifyItem(1, 120., 200., false);
662 verifyItem(2, 320., 350., false);
663 verifyItem(3, 670., 350., true);
664 QCOMPARE(lvwph->m_minYExtent, 0.);
665 QCOMPARE(lvwph->m_clipItem->y(), 30.);
666 QCOMPARE(lvwph->m_clipItem->clip(), false);
667 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
668 QCOMPARE(lvwph->contentY(), 30.);
669 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
670 }
671
672 void testSetNullDelegate()
673 {
674 lvwph->setDelegate(nullptr);
675
676 QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
677 QCOMPARE(lvwph->m_firstVisibleIndex, -1);
678 QCOMPARE(lvwph->m_minYExtent, 0.);
679 QCOMPARE(lvwph->m_clipItem->y(), 0.);
680 QCOMPARE(lvwph->m_clipItem->clip(), false);
681 QCOMPARE(lvwph->m_headerItem->y(), 0.);
682 QCOMPARE(lvwph->m_headerItem->height(), 50.);
683 QCOMPARE(lvwph->contentY(), 0.);
684 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
685 QTRY_COMPARE(lvwph->contentHeight(), 50.);
686 QVERIFY(lvwph->isAtYBeginning());
687 QVERIFY(lvwph->isAtYEnd());
688 }
689
690 void testInsertItems()
691 {
692 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
693 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
694
695 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
696 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
697 verifyItem(0, 50., 150., false);
698 verifyItem(1, 200., 125., false);
699 verifyItem(2, 325., 100., false);
700 verifyItem(3, 425., 200., false);
701 verifyItem(4, 625., 350., true);
702 QCOMPARE(lvwph->m_minYExtent, 0.);
703 QCOMPARE(lvwph->m_clipItem->y(), 0.);
704 QCOMPARE(lvwph->m_clipItem->clip(), false);
705 QCOMPARE(lvwph->m_headerItem->y(), 0.);
706 QCOMPARE(lvwph->m_headerItem->height(), 50.);
707 QCOMPARE(lvwph->contentY(), 0.);
708 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
709 }
710
711 void testInsertItemsOnNotShownPosition()
712 {
713 changeContentY(700);
714
715 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
716 QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
717 verifyItem(0, -300., 350., false);
718 verifyItem(1, 50, 350., false);
719 verifyItem(2, 400, 350., false);
720 verifyItem(3, 750, 350., true);
721 QCOMPARE(lvwph->m_minYExtent, 350.);
722 QCOMPARE(lvwph->m_clipItem->y(), 700.);
723 QCOMPARE(lvwph->m_clipItem->clip(), false);
724 QCOMPARE(lvwph->m_headerItem->y(), 0.);
725 QCOMPARE(lvwph->m_headerItem->height(), 50.);
726 QCOMPARE(lvwph->contentY(), 700.);
727 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
728
729 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
730 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
731
732 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
733 QCOMPARE(lvwph->m_firstVisibleIndex, 4);
734 verifyItem(0, -300., 350., false);
735 verifyItem(1, 50, 350., false);
736 verifyItem(2, 400, 350., false);
737 verifyItem(3, 750, 350., true);
738 QCOMPARE(lvwph->m_minYExtent, 1050.);
739 QCOMPARE(lvwph->m_clipItem->y(), 700.);
740 QCOMPARE(lvwph->m_clipItem->clip(), false);
741 QCOMPARE(lvwph->m_headerItem->y(), 0.);
742 QCOMPARE(lvwph->m_headerItem->height(), 50.);
743 QCOMPARE(lvwph->contentY(), 700.);
744 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
745
746 scrollToTop();
747
748 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
749 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
750 verifyItem(0, 50., 150., false);
751 verifyItem(1, 200., 125., false);
752 verifyItem(2, 325., 100., false);
753 verifyItem(3, 425., 200., false);
754 verifyItem(4, 625., 350., true);
755 QCOMPARE(lvwph->m_minYExtent, 225.);
756 QCOMPARE(lvwph->m_clipItem->y(), -225.);
757 QCOMPARE(lvwph->m_clipItem->clip(), false);
758 QCOMPARE(lvwph->m_headerItem->y(), -225.);
759 QCOMPARE(lvwph->m_headerItem->height(), 50.);
760 QCOMPARE(lvwph->contentY(), -225.);
761 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
762 }
763
764 void testInsertItemsAtEndOfViewport()
765 {
766 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100));
767 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125));
768
769 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
770 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
771 verifyItem(0, 50., 150., false);
772 verifyItem(1, 200., 200., false);
773 verifyItem(2, 400., 350., false);
774 verifyItem(3, 750., 125., true);
775 QCOMPARE(lvwph->m_minYExtent, 0.);
776 QCOMPARE(lvwph->m_clipItem->y(), 0.);
777 QCOMPARE(lvwph->m_clipItem->clip(), false);
778 QCOMPARE(lvwph->m_headerItem->y(), 0.);
779 QCOMPARE(lvwph->m_headerItem->height(), 50.);
780 QCOMPARE(lvwph->contentY(), 0.);
781 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
782 }
783
784 void testInsertItemsBeforeValidIndex()
785 {
786 changeContentY(520);
787
788 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
789 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
790 verifyItem(0, -320., 200., true);
791 verifyItem(1, -120, 350., false);
792 verifyItem(2, 230, 350., false);
793 verifyItem(3, 580, 350., true);
794 QCOMPARE(lvwph->m_minYExtent, 162.5);
795 QCOMPARE(lvwph->m_clipItem->y(), 520.);
796 QCOMPARE(lvwph->m_clipItem->clip(), false);
797 QCOMPARE(lvwph->m_headerItem->y(), 0.);
798 QCOMPARE(lvwph->m_headerItem->height(), 50.);
799 QCOMPARE(lvwph->contentY(), 520.);
800 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
801
802 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
803 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
804
805 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
806 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
807 verifyItem(0, -320., 200., true);
808 verifyItem(1, -120, 350., false);
809 verifyItem(2, 230, 350., false);
810 verifyItem(3, 580, 350., true);
811 QCOMPARE(lvwph->m_minYExtent, 787.5);
812 QCOMPARE(lvwph->m_clipItem->y(), 520.);
813 QCOMPARE(lvwph->m_clipItem->clip(), false);
814 QCOMPARE(lvwph->m_headerItem->y(), 0.);
815 QCOMPARE(lvwph->m_headerItem->height(), 50.);
816 QCOMPARE(lvwph->contentY(), 520.);
817 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
818 }
819
820 void testInsertItemsBeforeViewport()
821 {
822 changeContentY(375);
823
824 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
825 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
826 verifyItem(0, -325., 150., true);
827 verifyItem(1, -175, 200., false);
828 verifyItem(2, 25, 350., false);
829 verifyItem(3, 375, 350., false);
830 verifyItem(4, 725, 350., true);
831 QCOMPARE(lvwph->m_minYExtent, 0.);
832 QCOMPARE(lvwph->m_clipItem->y(), 375.);
833 QCOMPARE(lvwph->m_clipItem->clip(), false);
834 QCOMPARE(lvwph->m_headerItem->y(), 0.);
835 QCOMPARE(lvwph->m_headerItem->height(), 50.);
836 QCOMPARE(lvwph->contentY(), 375.);
837 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
838
839 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100));
840 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125));
841
842 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
843 QCOMPARE(lvwph->m_firstVisibleIndex, 2);
844 verifyItem(0, -275., 100., true);
845 verifyItem(1, -175, 200., false);
846 verifyItem(2, 25, 350., false);
847 verifyItem(3, 375, 350., false);
848 verifyItem(4, 725, 350., true);
849 QCOMPARE(lvwph->m_minYExtent, 490.);
850 QCOMPARE(lvwph->m_clipItem->y(), 375.);
851 QCOMPARE(lvwph->m_clipItem->clip(), false);
852 QCOMPARE(lvwph->m_headerItem->y(), 0.);
853 QCOMPARE(lvwph->m_headerItem->height(), 50.);
854 QCOMPARE(lvwph->contentY(), 375.);
855 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
856
857 scrollToTop();
858
859 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
860 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
861 verifyItem(0, 50., 150., false);
862 verifyItem(1, 200., 125., false);
863 verifyItem(2, 325., 100., false);
864 verifyItem(3, 425., 200., false);
865 verifyItem(4, 625., 350., true);
866 QCOMPARE(lvwph->m_minYExtent, 225.);
867 QCOMPARE(lvwph->m_clipItem->y(), -225.);
868 QCOMPARE(lvwph->m_clipItem->clip(), false);
869 QCOMPARE(lvwph->m_headerItem->y(), -225.);
870 QCOMPARE(lvwph->m_headerItem->height(), 50.);
871 QCOMPARE(lvwph->contentY(), -225.);
872 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
873 }
874
875 void testInsertItemsAtBottom()
876 {
877 scrollToBottom();
878
879 QVERIFY(lvwph->isAtYEnd());
880
881 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100));
882 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125));
883
884 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
885 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
886 verifyItem(0, -508., 350., true);
887 verifyItem(1, -158, 350., false);
888 verifyItem(2, 192, 350., false);
889 verifyItem(3, 542, 125., true);
890 verifyItem(4, 667, 100., true);
891 QCOMPARE(lvwph->m_minYExtent, 65.);
892 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
893 QCOMPARE(lvwph->m_clipItem->clip(), false);
894 QCOMPARE(lvwph->m_headerItem->y(), 0.);
895 QCOMPARE(lvwph->m_headerItem->height(), 50.);
896 QCOMPARE(lvwph->contentY(), 1258.);
897 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
898 QVERIFY(!lvwph->isAtYEnd());
899
900 scrollToBottom();
901
902 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
903 QCOMPARE(lvwph->m_firstVisibleIndex, 4);
904 verifyItem(0, -383., 350., true);
905 verifyItem(1, -33, 350., false);
906 verifyItem(2, 317, 125., false);
907 verifyItem(3, 442, 100., false);
908 QCOMPARE(lvwph->m_minYExtent, -125.);
909 QCOMPARE(lvwph->m_clipItem->y(), 1483.);
910 QCOMPARE(lvwph->m_clipItem->clip(), false);
911 QCOMPARE(lvwph->m_headerItem->y(), 0.);
912 QCOMPARE(lvwph->m_headerItem->height(), 50.);
913 QCOMPARE(lvwph->contentY(), 1483.);
914 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
915 QVERIFY(lvwph->isAtYEnd());
916 }
917
918 void testInsertItemAtTop()
919 {
920 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
921
922 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
923 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
924 verifyItem(0, 50., 75., false);
925 verifyItem(1, 125., 150., false);
926 verifyItem(2, 275., 200., false);
927 verifyItem(3, 475., 350., false);
928 QCOMPARE(lvwph->m_minYExtent, 0.);
929 QCOMPARE(lvwph->m_clipItem->y(), 0.);
930 QCOMPARE(lvwph->m_clipItem->clip(), false);
931 QCOMPARE(lvwph->m_headerItem->y(), 0.);
932 QCOMPARE(lvwph->m_headerItem->height(), 50.);
933 QCOMPARE(lvwph->contentY(), 0.);
934 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
935 QVERIFY(lvwph->isAtYBeginning());
936 }
937
938 void testInsertItem10SmallItemsAtTopWhenAtBottom()
939 {
940 scrollToBottom();
941
942 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
943 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
944 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
945 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
946 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
947 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
948 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
949 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
950 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
951 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75));
952
953 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
954 QCOMPARE(lvwph->m_firstVisibleIndex, 13);
955 verifyItem(0, -508., 350., true);
956 verifyItem(1, -158, 350., false);
957 verifyItem(2, 192, 350., false);
958 QCOMPARE(lvwph->m_minYExtent, 3850.);
959 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
960 QCOMPARE(lvwph->m_clipItem->clip(), false);
961 QCOMPARE(lvwph->m_headerItem->y(), 0.);
962 QCOMPARE(lvwph->m_headerItem->height(), 50.);
963 QCOMPARE(lvwph->contentY(), 1258.);
964 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
965
966 changeContentY(-1700);
967
968 QTRY_COMPARE(lvwph->m_visibleItems.count(), 12);
969 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
970 verifyItem(0, -308., 75., true);
971 verifyItem(1, -233., 75., true);
972 verifyItem(2, -158., 75., true);
973 verifyItem(3, -83., 75., true);
974 verifyItem(4, -8., 75., false);
975 verifyItem(5, 67., 75., false);
976 verifyItem(6, 142., 75., false);
977 verifyItem(7, 217., 75., false);
978 verifyItem(8, 292., 75., false);
979 verifyItem(9, 367., 75., false);
980 verifyItem(10, 442., 150., false);
981 verifyItem(11, 592., 200., true);
982 QCOMPARE(lvwph->m_minYExtent, 750.);
983 QCOMPARE(lvwph->m_clipItem->y(), -392.);
984 QCOMPARE(lvwph->m_clipItem->clip(), true);
985 QCOMPARE(lvwph->m_headerItem->y(), -442.);
986 QCOMPARE(lvwph->m_headerItem->height(), 50.);
987 QCOMPARE(lvwph->contentY(), -442.);
988 QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
989 }
990
991 void testRemoveItemsAtTop()
992 {
993 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
994
995 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
996 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
997 verifyItem(0, 50., 350., false);
998 verifyItem(1, 400., 350., false);
999 verifyItem(2, 750., 350., true);
1000 QCOMPARE(lvwph->m_minYExtent, 0.);
1001 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1002 QCOMPARE(lvwph->m_clipItem->clip(), false);
1003 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1004 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1005 QCOMPARE(lvwph->contentY(), 0.);
1006 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1007 QVERIFY(lvwph->isAtYBeginning());
1008 }
1009
1010 void testRemoveNonCreatedItemsAtTopWhenAtBottom()
1011 {
1012 scrollToBottom();
1013
1014 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1015
1016 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1017 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1018 verifyItem(0, -508., 350., true);
1019 verifyItem(1, -158, 350., false);
1020 verifyItem(2, 192, 350., false);
1021 QCOMPARE(lvwph->m_minYExtent, -350.);
1022 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1023 QCOMPARE(lvwph->m_clipItem->clip(), false);
1024 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1025 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1026 QCOMPARE(lvwph->contentY(), 1258.);
1027 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1028 }
1029
1030 void testRemoveLastItemsAtBottom()
1031 {
1032 scrollToBottom();
1033
1034 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
1035
1036 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1037 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1038 verifyItem(0, -358., 200., true);
1039 verifyItem(1, -158, 350., false);
1040 verifyItem(2, 192, 350., false);
1041 QCOMPARE(lvwph->m_minYExtent, 150.);
1042 QCOMPARE(lvwph->m_clipItem->y(), 558.);
1043 QCOMPARE(lvwph->m_clipItem->clip(), false);
1044 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1045 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1046 QCOMPARE(lvwph->contentY(), 558.);
1047 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1048 }
1049
1050 void testRemoveItemOutOfViewport()
1051 {
1052 changeContentY(520);
1053
1054 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1055 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1056 verifyItem(0, -320., 200., true);
1057 verifyItem(1, -120, 350., false);
1058 verifyItem(2, 230, 350., false);
1059 verifyItem(3, 580, 350., true);
1060 QCOMPARE(lvwph->m_minYExtent, 162.5);
1061 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1062 QCOMPARE(lvwph->m_clipItem->clip(), false);
1063 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1064 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1065 QCOMPARE(lvwph->contentY(), 520.);
1066 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1067
1068
1069 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1070
1071 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1072 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1073 verifyItem(0, -270., 150., true);
1074 verifyItem(1, -120., 350., false);
1075 verifyItem(2, 230, 350., false);
1076 verifyItem(3, 580, 350., true);
1077 QCOMPARE(lvwph->m_minYExtent, -200.);
1078 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1079 QCOMPARE(lvwph->m_clipItem->clip(), false);
1080 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1081 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1082 QCOMPARE(lvwph->contentY(), 520.);
1083 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1084 }
1085
1086 void testMoveFirstItems()
1087 {
1088 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1089
1090 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1091 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1092 verifyItem(0, 50., 200., false);
1093 verifyItem(1, 250., 150., false);
1094 verifyItem(2, 400., 350., false);
1095 verifyItem(3, 750., 350., true);
1096 QCOMPARE(lvwph->m_minYExtent, 0.);
1097 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1098 QCOMPARE(lvwph->m_clipItem->clip(), false);
1099 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1100 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1101 QCOMPARE(lvwph->contentY(), 0.);
1102 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1103
1104 }
1105
1106 void testMoveFirstOutOfVisibleItems()
1107 {
1108 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
1109
1110 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1111 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1112 verifyItem(0, 50., 200., false);
1113 verifyItem(1, 250., 350., false);
1114 verifyItem(2, 600., 350., true);
1115 QCOMPARE(lvwph->m_minYExtent, 0.);
1116 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1117 QCOMPARE(lvwph->m_clipItem->clip(), false);
1118 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1119 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1120 QCOMPARE(lvwph->contentY(), 0.);
1121 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1122
1123 }
1124
1125 void testMoveFirstToLastAtBottom()
1126 {
1127 scrollToBottom();
1128
1129 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1130 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1131 verifyItem(0, -508., 350., true);
1132 verifyItem(1, -158, 350., false);
1133 verifyItem(2, 192, 350., false);
1134 QCOMPARE(lvwph->m_minYExtent, 350.);
1135 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1136 QCOMPARE(lvwph->m_clipItem->clip(), false);
1137 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1138 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1139 QCOMPARE(lvwph->contentY(), 1258.);
1140 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1141
1142 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
1143
1144 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1145 QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1146 verifyItem(0, -508., 350., true);
1147 verifyItem(1, -158, 350., false);
1148 verifyItem(2, 192, 350., false);
1149 verifyItem(3, 542, 150., true);
1150 QCOMPARE(lvwph->m_minYExtent, -100.);
1151 QCOMPARE(lvwph->m_clipItem->y(), 1258.);
1152 QCOMPARE(lvwph->m_clipItem->clip(), false);
1153 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1154 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1155 QCOMPARE(lvwph->contentY(), 1258.);
1156 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1157 QVERIFY(!lvwph->isAtYEnd());
1158 }
1159
1160 void testChangeSizeVisibleItemNotOnViewport()
1161 {
1162 changeContentY(440);
1163
1164 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1165 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1166 verifyItem(0, -390., 150., true);
1167 verifyItem(1, -240., 200., true);
1168 verifyItem(2, -40, 350., false);
1169 verifyItem(3, 310, 350., false);
1170 verifyItem(4, 660, 350., true);
1171 QCOMPARE(lvwph->m_minYExtent, 0.);
1172 QCOMPARE(lvwph->m_clipItem->y(), 440.);
1173 QCOMPARE(lvwph->m_clipItem->clip(), false);
1174 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1175 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1176 QCOMPARE(lvwph->contentY(), 440.);
1177 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1178
1179 model->setProperty(1, "size", 100);
1180
1181 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1182 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1183 verifyItem(0, -290., 150., true);
1184 verifyItem(1, -140., 100., true);
1185 verifyItem(2, -40, 350., false);
1186 verifyItem(3, 310, 350., false);
1187 verifyItem(4, 660, 350., true);
1188 QCOMPARE(lvwph->m_minYExtent, -100.);
1189 QCOMPARE(lvwph->m_clipItem->y(), 440.);
1190 QCOMPARE(lvwph->m_clipItem->clip(), false);
1191 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1192 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1193 QCOMPARE(lvwph->contentY(), 440.);
1194 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1195 }
1196
1197 void testShowHeaderCloseToTheTop()
1198 {
1199 changeContentY(375);
1200
1201 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1202 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1203 verifyItem(0, -325., 150., true);
1204 verifyItem(1, -175, 200., false);
1205 verifyItem(2, 25, 350., false);
1206 verifyItem(3, 375, 350., false);
1207 verifyItem(4, 725, 350., true);
1208 QCOMPARE(lvwph->m_minYExtent, 0.);
1209 QCOMPARE(lvwph->m_clipItem->y(), 375.);
1210 QCOMPARE(lvwph->m_clipItem->clip(), false);
1211 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1212 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1213 QCOMPARE(lvwph->contentY(), 375.);
1214 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
1215
1216 lvwph->showHeader();
1217
1218 QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1219 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1220 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1221 verifyItem(0, -375., 150., true);
1222 verifyItem(1, -225, 200., true);
1223 verifyItem(2, -25, 350., false);
1224 verifyItem(3, 325, 350., false);
1225 verifyItem(4, 675, 350., true);
1226 QCOMPARE(lvwph->m_minYExtent, 50.);
1227 QCOMPARE(lvwph->m_clipItem->y(), 375.);
1228 QCOMPARE(lvwph->m_clipItem->clip(), true);
1229 QCOMPARE(lvwph->m_headerItem->y(), 325.);
1230 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1231 QCOMPARE(lvwph->contentY(), 325.);
1232 QCOMPARE(lvwph->m_headerItemShownHeight, 50.);;
1233
1234 scrollToTop();
1235
1236 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1237 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1238 verifyItem(0, 50., 150., false);
1239 verifyItem(1, 200., 200., false);
1240 verifyItem(2, 400., 350., false);
1241 verifyItem(3, 750., 350., true);
1242 QCOMPARE(lvwph->m_minYExtent, 50.);
1243 QCOMPARE(lvwph->m_clipItem->y(), -50.);
1244 QCOMPARE(lvwph->m_clipItem->clip(), false);
1245 QCOMPARE(lvwph->m_headerItem->y(), -50.);
1246 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1247 QCOMPARE(lvwph->contentY(), -50.);
1248 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1249 }
1250
1251 void testShowHeaderAtBottom()
1252 {
1253 scrollToBottom();
1254
1255 lvwph->showHeader();
1256
1257 QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1258 QTRY_VERIFY (lvwph->isAtYEnd());
1259 }
1260
1261 void growWindow()
1262 {
1263 view->rootObject()->setHeight(850);
1264
1265 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1266 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1267 verifyItem(0, 50., 150., false);
1268 verifyItem(1, 200., 200., false);
1269 verifyItem(2, 400., 350., false);
1270 verifyItem(3, 750., 350., false);
1271 verifyItem(4, 1100., 350., true);
1272 QCOMPARE(lvwph->m_minYExtent, 0.);
1273 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1274 QCOMPARE(lvwph->m_clipItem->clip(), false);
1275 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1276 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1277 QCOMPARE(lvwph->contentY(), 0.);
1278 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1279 }
1280
1281 void growWindowAtBottom()
1282 {
1283 scrollToBottom();
1284
1285 view->rootObject()->setHeight(850);
1286
1287 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1288 QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1289 verifyItem(0, -550., 350., true);
1290 verifyItem(1, -200., 350., false);
1291 verifyItem(2, 150, 350., false);
1292 verifyItem(3, 500, 350., false);
1293 QCOMPARE(lvwph->m_minYExtent, 350.);
1294 QCOMPARE(lvwph->m_clipItem->y(), 950.);
1295 QCOMPARE(lvwph->m_clipItem->clip(), false);
1296 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1297 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1298 QCOMPARE(lvwph->contentY(), 950.);
1299 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1300 }
1301
1302private:
1303 QQuickView *view;
1304 ListViewWithPageHeader *lvwph;
1305 QQuickListModel *model;
1306 QQmlComponent *otherDelegate;
1307};
1308
1309QTEST_MAIN(ListViewWithPageHeaderTest)
1310
1311#include "listviewwithpageheadertest.moc"
01312
=== added file 'tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp'
--- tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp 1970-01-01 00:00:00 +0000
+++ tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp 2013-07-01 11:23:32 +0000
@@ -0,0 +1,1567 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "listviewwithpageheader.h"
18
19#include <QAbstractItemModel>
20#include <QQmlEngine>
21#include <QQuickView>
22#include <QtTestGui>
23#include <private/qquicklistmodel_p.h>
24#include <private/qquickanimation_p.h>
25#include <private/qquickitem_p.h>
26
27class ListViewWithPageHeaderTestSection : public QObject
28{
29 Q_OBJECT
30
31private:
32 void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled, const QString &sectionHeader, bool sectionHeaderCulled)
33 {
34 QTRY_VERIFY(visibleIndex < lvwph->m_visibleItems.count());
35 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->y(), pos);
36 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
37 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
38 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->m_sectionItem), sectionHeader);
39 if (!sectionHeader.isNull()) {
40 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_sectionItem)->culled, sectionHeaderCulled);
41 }
42 }
43
44 void changeContentY(qreal change)
45 {
46 const qreal dest = lvwph->contentY() + change;
47 if (dest > lvwph->contentY()) {
48 const qreal jump = 25;
49 while (lvwph->contentY() + jump < dest) {
50 lvwph->setContentY(lvwph->contentY() + jump);
51 QTest::qWait(1);
52 }
53 } else {
54 const qreal jump = -25;
55 while (lvwph->contentY() + jump > dest) {
56 lvwph->setContentY(lvwph->contentY() + jump);
57 QTest::qWait(1);
58 }
59 }
60 lvwph->setContentY(dest);
61 QTest::qWait(1);
62 }
63
64 void scrollToTop()
65 {
66 const qreal jump = -25;
67 while (!lvwph->isAtYBeginning()) {
68 if (lvwph->contentY() + jump > -lvwph->minYExtent()) {
69 lvwph->setContentY(lvwph->contentY() + jump);
70 } else {
71 lvwph->setContentY(lvwph->contentY() - 1);
72 }
73 QTest::qWait(1);
74 }
75 }
76
77 void scrollToBottom()
78 {
79 const qreal jump = 25;
80 while (!lvwph->isAtYEnd()) {
81 if (lvwph->contentY() + lvwph->height() + jump < lvwph->contentHeight()) {
82 lvwph->setContentY(lvwph->contentY() + jump);
83 } else {
84 lvwph->setContentY(lvwph->contentY() + 1);
85 }
86 QTest::qWait(1);
87 }
88 }
89
90 void verifyInitialTopPosition()
91 {
92 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
93 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
94 verifyItem(0, 50., 190., false, "Agressive", false);
95 verifyItem(1, 240., 240., false, "Regular", false);
96 verifyItem(2, 480., 390., false, "Mild", false);
97 QCOMPARE(lvwph->m_minYExtent, 0.);
98 QCOMPARE(lvwph->m_clipItem->y(), 0.);
99 QCOMPARE(lvwph->m_clipItem->clip(), false);
100 QCOMPARE(lvwph->m_headerItem->y(), 0.);
101 QCOMPARE(lvwph->m_headerItem->height(), 50.);
102 QCOMPARE(lvwph->contentY(), 0.);
103 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
104 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
105 }
106
107 QString section(QQuickItem *item)
108 {
109 return item ? QQmlEngine::contextForObject(item)->parentContext()->contextProperty(QLatin1String("section")).toString() : QString();
110 }
111
112private Q_SLOTS:
113
114 void initTestCase()
115 {
116 }
117
118 void init()
119 {
120 view = new QQuickView();
121 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
122 view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test_section.qml"));
123 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
124 model = view->rootObject()->findChild<QQuickListModel*>();
125 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
126 QVERIFY(lvwph);
127 QVERIFY(model);
128 QVERIFY(otherDelegate);
129 view->show();
130 QTest::qWaitForWindowExposed(view);
131
132 verifyInitialTopPosition();
133 }
134
135 void cleanup()
136 {
137 delete view;
138 }
139
140 void testCreationDeletion()
141 {
142 // Nothing, init/cleanup already tests this
143 }
144
145 void testDrag1PixelUp()
146 {
147 lvwph->setContentY(lvwph->contentY() + 1);
148 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
149 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
150 verifyItem(0, 49., 190., false, "Agressive", false);
151 verifyItem(1, 239., 240., false, "Regular", false);
152 verifyItem(2, 479., 390., false, "Mild", false);
153 QCOMPARE(lvwph->m_minYExtent, 0.);
154 QCOMPARE(lvwph->m_clipItem->y(), 1.);
155 QCOMPARE(lvwph->m_clipItem->clip(), false);
156 QCOMPARE(lvwph->m_headerItem->y(), 0.);
157 QCOMPARE(lvwph->m_headerItem->height(), 50.);
158 QCOMPARE(lvwph->contentY(), 1.);
159 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
160 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
161 }
162
163 void testHeaderDetachDragDown()
164 {
165 QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 0));
166 QTest::qWait(100);
167 QTest::mouseMove(view, QPoint(0, 5));
168 QTest::qWait(100);
169 QTest::mouseMove(view, QPoint(0, 10));
170 QTest::qWait(100);
171 QTest::mouseMove(view, QPoint(0, 15));
172 QTest::qWait(100);
173 QTest::mouseMove(view, QPoint(0, 20));
174 QTest::qWait(100);
175 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
176 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
177 verifyItem(0, 55., 190., false, "Agressive", false);
178 verifyItem(1, 245., 240., false, "Regular", false);
179 verifyItem(2, 485., 390., false, "Mild", false);
180 QCOMPARE(lvwph->m_minYExtent, 0.);
181 QCOMPARE(lvwph->m_clipItem->y(), -5.);
182 QCOMPARE(lvwph->m_clipItem->clip(), false);
183 QCOMPARE(lvwph->m_headerItem->y(), -5.);
184 QCOMPARE(lvwph->m_headerItem->height(), 55.);
185 QCOMPARE(lvwph->contentY(), -5.);
186 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
187 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
188
189 QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(0, 15));
190
191 verifyInitialTopPosition();
192 }
193
194 void testDrag375PixelUp()
195 {
196 changeContentY(375);
197 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
198 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
199 verifyItem(0, -325., 190., true, "Agressive", true);
200 verifyItem(1, -135, 240., false, "Regular", true);
201 verifyItem(2, 105, 390., false, "Mild", false);
202 verifyItem(3, 495, 390., false, "Bold", false);
203 QCOMPARE(lvwph->m_minYExtent, 0.);
204 QCOMPARE(lvwph->m_clipItem->y(), 375.);
205 QCOMPARE(lvwph->m_clipItem->clip(), false);
206 QCOMPARE(lvwph->m_headerItem->y(), 0.);
207 QCOMPARE(lvwph->m_headerItem->height(), 50.);
208 QCOMPARE(lvwph->contentY(), 375.);
209 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
210 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
211 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
212 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
213 }
214
215 void testDrag520PixelUp()
216 {
217 changeContentY(520);
218 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
219 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
220 verifyItem(0, -280., 240., true, "Regular", true);
221 verifyItem(1, -40, 390., false, "Mild", true);
222 verifyItem(2, 350, 390., false, "Bold", false);
223 verifyItem(3, 740, 350., true, QString(), true);
224 QCOMPARE(lvwph->m_minYExtent, 152.5);
225 QCOMPARE(lvwph->m_clipItem->y(), 520.);
226 QCOMPARE(lvwph->m_clipItem->clip(), false);
227 QCOMPARE(lvwph->m_headerItem->y(), 0.);
228 QCOMPARE(lvwph->m_headerItem->height(), 50.);
229 QCOMPARE(lvwph->contentY(), 520.);
230 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
231 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
232 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
233 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
234 }
235
236 void testDragHeaderUpThenShow()
237 {
238 changeContentY(120);
239 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
240 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
241 verifyItem(0, -70., 190., false, "Agressive", true);
242 verifyItem(1, 120., 240., false, "Regular", false);
243 verifyItem(2, 360., 390., false, "Mild", false);
244 verifyItem(3, 750., 390., true, "Bold", true);
245 QCOMPARE(lvwph->m_minYExtent, 0.);
246 QCOMPARE(lvwph->m_clipItem->y(), 120.);
247 QCOMPARE(lvwph->m_clipItem->clip(), false);
248 QCOMPARE(lvwph->m_headerItem->y(), 0.);
249 QCOMPARE(lvwph->m_headerItem->height(), 50.);
250 QCOMPARE(lvwph->contentY(), 120.);
251 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
252 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
253 QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
254 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
255
256 changeContentY(-30);
257 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
258 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
259 verifyItem(0, -70., 190., false, "Agressive", true);
260 verifyItem(1, 120., 240., false, "Regular", false);
261 verifyItem(2, 360., 390., false, "Mild", false);
262 verifyItem(3, 750., 390., true, "Bold", true);
263 QCOMPARE(lvwph->m_minYExtent, 0.);
264 QCOMPARE(lvwph->m_clipItem->y(), 120.);
265 QCOMPARE(lvwph->m_clipItem->clip(), true);
266 QTRY_COMPARE(lvwph->m_headerItem->y(), 70.);
267 QCOMPARE(lvwph->m_headerItem->height(), 50.);
268 QCOMPARE(lvwph->contentY(), 90.);
269 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
270 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
271 QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
272 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
273 }
274
275 void testDragHeaderUpThenShowWithoutHidingTotally()
276 {
277 changeContentY(10);
278 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
279 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
280 verifyItem(0, 40., 190., false, "Agressive", false);
281 verifyItem(1, 230., 240., false, "Regular", false);
282 verifyItem(2, 470., 390., false, "Mild", false);
283 QCOMPARE(lvwph->m_minYExtent, 0.);
284 QCOMPARE(lvwph->m_clipItem->y(), 10.);
285 QCOMPARE(lvwph->m_clipItem->clip(), false);
286 QCOMPARE(lvwph->m_headerItem->y(), 0.);
287 QCOMPARE(lvwph->m_headerItem->height(), 50.);
288 QCOMPARE(lvwph->contentY(), 10.);
289 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
290 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
291
292 changeContentY(-1);
293
294 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
295 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
296 verifyItem(0, 41., 190., false, "Agressive", false);
297 verifyItem(1, 231., 240., false, "Regular", false);
298 verifyItem(2, 471., 390., false, "Mild", false);
299 QCOMPARE(lvwph->m_minYExtent, 0.);
300 QCOMPARE(lvwph->m_clipItem->y(), 9.);
301 QCOMPARE(lvwph->m_clipItem->clip(), false);
302 QTRY_COMPARE(lvwph->m_headerItem->y(), 0.);
303 QCOMPARE(lvwph->m_headerItem->height(), 50.);
304 QCOMPARE(lvwph->contentY(), 9.);
305 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
306 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
307 }
308
309 void testPositionAtBeginningIndex0Visible()
310 {
311 changeContentY(375);
312 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
313 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
314 verifyItem(0, -325., 190., true, "Agressive", true);
315 verifyItem(1, -135, 240., false, "Regular", true);
316 verifyItem(2, 105, 390., false, "Mild", false);
317 verifyItem(3, 495, 390., false, "Bold", false);
318 QCOMPARE(lvwph->m_minYExtent, 0.);
319 QCOMPARE(lvwph->m_clipItem->y(), 375.);
320 QCOMPARE(lvwph->m_clipItem->clip(), false);
321 QCOMPARE(lvwph->m_headerItem->y(), 0.);
322 QCOMPARE(lvwph->m_headerItem->height(), 50.);
323 QCOMPARE(lvwph->contentY(), 375.);
324 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
325 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
326 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
327 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
328
329 lvwph->positionAtBeginning();
330
331 verifyInitialTopPosition();
332 }
333
334 void testPositionAtBeginningIndex0NotVisible()
335 {
336 changeContentY(520);
337 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
338 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
339 verifyItem(0, -280., 240., true, "Regular", true);
340 verifyItem(1, -40, 390., false, "Mild", true);
341 verifyItem(2, 350, 390., false, "Bold", false);
342 verifyItem(3, 740, 350., true, QString(), true);
343 QCOMPARE(lvwph->m_minYExtent, 152.5);
344 QCOMPARE(lvwph->m_clipItem->y(), 520.);
345 QCOMPARE(lvwph->m_clipItem->clip(), false);
346 QCOMPARE(lvwph->m_headerItem->y(), 0.);
347 QCOMPARE(lvwph->m_headerItem->height(), 50.);
348 QCOMPARE(lvwph->contentY(), 520.);
349 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
350 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
351 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
352 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
353
354 lvwph->positionAtBeginning();
355
356 verifyInitialTopPosition();
357 }
358
359 void testIndex0GrowOnScreen()
360 {
361 model->setProperty(0, "size", 400);
362
363 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
364 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
365 verifyItem(0, 50., 440., false, "Agressive", false);
366 verifyItem(1, 490., 240., false, "Regular", false);
367 verifyItem(2, 730., 390., true, "Mild", true);
368 QCOMPARE(lvwph->m_minYExtent, 0.);
369 QCOMPARE(lvwph->m_clipItem->y(), 0.);
370 QCOMPARE(lvwph->m_clipItem->clip(), false);
371 QCOMPARE(lvwph->m_headerItem->y(), 0.);
372 QCOMPARE(lvwph->m_headerItem->height(), 50.);
373 QCOMPARE(lvwph->contentY(), 0.);
374 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
375 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
376 }
377
378 void testIndex0GrowOffScreen()
379 {
380 changeContentY(375);
381 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
382 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
383 verifyItem(0, -325., 190., true, "Agressive", true);
384 verifyItem(1, -135, 240., false, "Regular", true);
385 verifyItem(2, 105, 390., false, "Mild", false);
386 verifyItem(3, 495, 390., false, "Bold", false);
387 QCOMPARE(lvwph->m_minYExtent, 0.);
388 QCOMPARE(lvwph->m_clipItem->y(), 375.);
389 QCOMPARE(lvwph->m_clipItem->clip(), false);
390 QCOMPARE(lvwph->m_headerItem->y(), 0.);
391 QCOMPARE(lvwph->m_headerItem->height(), 50.);
392 QCOMPARE(lvwph->contentY(), 375.);
393 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
394 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
395 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
396 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
397
398 model->setProperty(0, "size", 400);
399
400 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
401 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
402 verifyItem(0, -575., 440., true, "Agressive", true);
403 verifyItem(1, -135, 240., false, "Regular", true);
404 verifyItem(2, 105, 390., false, "Mild", false);
405 verifyItem(3, 495, 390., false, "Bold", false);
406 QCOMPARE(lvwph->m_minYExtent, 250.);
407 QCOMPARE(lvwph->m_clipItem->y(), 375.);
408 QCOMPARE(lvwph->m_clipItem->clip(), false);
409 QCOMPARE(lvwph->m_headerItem->y(), 0.);
410 QCOMPARE(lvwph->m_headerItem->height(), 50.);
411 QCOMPARE(lvwph->contentY(), 375.);
412 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
413 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
414 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
415 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
416
417 scrollToTop();
418
419 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
420 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
421 verifyItem(0, 50., 440., false, "Agressive", false);
422 verifyItem(1, 490, 240., false, "Regular", false);
423 verifyItem(2, 730, 390., true, "Mild", true);
424 QCOMPARE(lvwph->m_minYExtent, 250.);
425 QCOMPARE(lvwph->m_clipItem->y(), -250.);
426 QCOMPARE(lvwph->m_clipItem->clip(), false);
427 QCOMPARE(lvwph->m_headerItem->y(), -250.);
428 QCOMPARE(lvwph->m_headerItem->height(), 50.);
429 QCOMPARE(lvwph->contentY(), -250.);
430 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
431 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
432
433 changeContentY(30);
434
435 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
436 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
437 verifyItem(0, 20., 440., false, "Agressive", false);
438 verifyItem(1, 460, 240., false, "Regular", false);
439 verifyItem(2, 700, 390., true, "Mild", true);
440 QCOMPARE(lvwph->m_minYExtent, 250.);
441 QCOMPARE(lvwph->m_clipItem->y(), -220.);
442 QCOMPARE(lvwph->m_clipItem->clip(), false);
443 QCOMPARE(lvwph->m_headerItem->y(), -250.);
444 QCOMPARE(lvwph->m_headerItem->height(), 50.);
445 QCOMPARE(lvwph->contentY(), -220.);
446 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
447 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
448 }
449
450 void testIndex0GrowNotCreated()
451 {
452 changeContentY(520);
453 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
454 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
455 verifyItem(0, -280., 240., true, "Regular", true);
456 verifyItem(1, -40, 390., false, "Mild", true);
457 verifyItem(2, 350, 390., false, "Bold", false);
458 verifyItem(3, 740, 350., true, QString(), true);
459 QCOMPARE(lvwph->m_minYExtent, 152.5);
460 QCOMPARE(lvwph->m_clipItem->y(), 520.);
461 QCOMPARE(lvwph->m_clipItem->clip(), false);
462 QCOMPARE(lvwph->m_headerItem->y(), 0.);
463 QCOMPARE(lvwph->m_headerItem->height(), 50.);
464 QCOMPARE(lvwph->contentY(), 520.);
465 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
466 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
467 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
468 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
469
470 model->setProperty(0, "size", 400);
471
472 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
473 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
474 verifyItem(0, -280., 240., true, "Regular", true);
475 verifyItem(1, -40, 390., false, "Mild", true);
476 verifyItem(2, 350, 390., false, "Bold", false);
477 verifyItem(3, 740, 350., true, QString(), true);
478 QCOMPARE(lvwph->m_minYExtent, 152.5);
479 QCOMPARE(lvwph->m_clipItem->y(), 520.);
480 QCOMPARE(lvwph->m_clipItem->clip(), false);
481 QCOMPARE(lvwph->m_headerItem->y(), 0.);
482 QCOMPARE(lvwph->m_headerItem->height(), 50.);
483 QCOMPARE(lvwph->contentY(), 520.);
484 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
485 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
486 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
487 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
488
489 scrollToTop();
490
491 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
492 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
493 verifyItem(0, 50., 440., false, "Agressive", false);
494 verifyItem(1, 490, 240., false, "Regular", false);
495 verifyItem(2, 730, 390., true, "Mild", true);
496 QCOMPARE(lvwph->m_minYExtent, 250.);
497 QCOMPARE(lvwph->m_clipItem->y(), -250.);
498 QCOMPARE(lvwph->m_clipItem->clip(), false);
499 QCOMPARE(lvwph->m_headerItem->y(), -250.);
500 QCOMPARE(lvwph->m_headerItem->height(), 50.);
501 QCOMPARE(lvwph->contentY(), -250.);
502 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
503 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
504 }
505
506 void testShowHideShowHeaderAtBottom()
507 {
508 scrollToBottom();
509 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
510 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
511 verifyItem(0, -588., 390., true, "Bold", true);
512 verifyItem(1, -198, 350., false, QString(), true);
513 verifyItem(2, 152, 390., false, "Lazy", false);
514 QCOMPARE(lvwph->m_minYExtent, 310.);
515 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
516 QCOMPARE(lvwph->m_clipItem->clip(), false);
517 QCOMPARE(lvwph->m_headerItem->y(), 0.);
518 QCOMPARE(lvwph->m_headerItem->height(), 50.);
519 QCOMPARE(lvwph->contentY(), 1458.);
520 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
521 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
522 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
523 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
524
525 changeContentY(-30);
526 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
527 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
528 verifyItem(0, -588., 390., true, "Bold", true);
529 verifyItem(1, -198, 350., false, QString(), true);
530 verifyItem(2, 152, 390., false, "Lazy", false);
531 QCOMPARE(lvwph->m_minYExtent, 310.);
532 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
533 QCOMPARE(lvwph->m_clipItem->clip(), true);
534 QCOMPARE(lvwph->m_headerItem->y(), 1408.);
535 QCOMPARE(lvwph->m_headerItem->height(), 50.);
536 QCOMPARE(lvwph->contentY(), 1428.);
537 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
538 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
539 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
540 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
541
542 changeContentY(30);
543 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
544 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
545 verifyItem(0, -588., 390., true, "Bold", true);
546 verifyItem(1, -198, 350., false, QString(), true);
547 verifyItem(2, 152, 390., false, "Lazy", false);
548 QCOMPARE(lvwph->m_minYExtent, 310.);
549 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
550 QCOMPARE(lvwph->m_clipItem->clip(), false);
551 QCOMPARE(lvwph->m_headerItem->y(), -310.);
552 QCOMPARE(lvwph->m_headerItem->height(), 50.);
553 QCOMPARE(lvwph->contentY(), 1458.);
554 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
555 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
556 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
557 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
558
559 changeContentY(-30);
560 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
561 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
562 verifyItem(0, -588., 390., true, "Bold", true);
563 verifyItem(1, -198, 350., false, QString(), true);
564 verifyItem(2, 152, 390., false, "Lazy", false);
565 QCOMPARE(lvwph->m_minYExtent, 310.);
566 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
567 QCOMPARE(lvwph->m_clipItem->clip(), true);
568 QCOMPARE(lvwph->m_headerItem->y(), 1408.);
569 QCOMPARE(lvwph->m_headerItem->height(), 50.);
570 QCOMPARE(lvwph->contentY(), 1428.);
571 QCOMPARE(lvwph->m_headerItemShownHeight, 30.);
572 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
573 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
574 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
575 }
576
577 void testChangeDelegateAtBottom()
578 {
579 scrollToBottom();
580 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
581 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
582 verifyItem(0, -588., 390., true, "Bold", true);
583 verifyItem(1, -198, 350., false, QString(), true);
584 verifyItem(2, 152, 390., false, "Lazy", false);
585 QCOMPARE(lvwph->m_minYExtent, 310.);
586 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
587 QCOMPARE(lvwph->m_clipItem->clip(), false);
588 QCOMPARE(lvwph->m_headerItem->y(), 0.);
589 QCOMPARE(lvwph->m_headerItem->height(), 50.);
590 QCOMPARE(lvwph->contentY(), 1458.);
591 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
592 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
593 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
594 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
595
596 lvwph->setDelegate(otherDelegate);
597
598 QTRY_COMPARE(lvwph->m_visibleItems.count(), 6);
599 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
600 verifyItem(0, 50., 75., false, "Agressive", false);
601 verifyItem(1, 125, 75., false, "Regular", false);
602 verifyItem(2, 200, 75., false, "Mild", false);
603 verifyItem(3, 275, 75., false, "Bold", false);
604 verifyItem(4, 350, 35., false, QString(), true);
605 verifyItem(5, 385, 75., false, "Lazy", false);
606 QCOMPARE(lvwph->m_minYExtent, 0.);
607 QCOMPARE(lvwph->m_clipItem->y(), 0.);
608 QCOMPARE(lvwph->m_clipItem->clip(), false);
609 QCOMPARE(lvwph->m_headerItem->y(), 0.);
610 QCOMPARE(lvwph->m_headerItem->height(), 50.);
611 QCOMPARE(lvwph->contentY(), 0.);
612 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
613 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
614 }
615
616 void testSetEmptyHeaderAtTop()
617 {
618 lvwph->setHeader(nullptr);
619
620 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
621 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
622 verifyItem(0, 0., 190., false, "Agressive", false);
623 verifyItem(1, 190., 240., false, "Regular", false);
624 verifyItem(2, 430., 390., false, "Mild", false);
625 QCOMPARE(lvwph->m_minYExtent, 0.);
626 QCOMPARE(lvwph->m_clipItem->y(), 0.);
627 QCOMPARE(lvwph->m_clipItem->clip(), false);
628 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
629 QCOMPARE(lvwph->contentY(), 0.);
630 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
631 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
632 }
633
634 void testSetEmptyHeaderAtBottom()
635 {
636 scrollToBottom();
637 lvwph->setHeader(nullptr);
638
639 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
640 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
641 verifyItem(0, -588., 390., true, "Bold", true);
642 verifyItem(1, -198, 350., false, QString(), true);
643 verifyItem(2, 152, 390., false, "Lazy", false);
644 QCOMPARE(lvwph->m_minYExtent, 260.);
645 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
646 QCOMPARE(lvwph->m_clipItem->clip(), false);
647 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
648 QCOMPARE(lvwph->contentY(), 1458.);
649 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
650 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
651 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
652 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
653
654 scrollToTop();
655
656 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
657 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
658 verifyItem(0, 0., 190., false, "Agressive", false);
659 verifyItem(1, 190., 240., false, "Regular", false);
660 verifyItem(2, 430., 390., false, "Mild", false);
661 QCOMPARE(lvwph->m_minYExtent, -50.);
662 QCOMPARE(lvwph->m_clipItem->y(), 50.);
663 QCOMPARE(lvwph->m_clipItem->clip(), false);
664 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
665 QCOMPARE(lvwph->contentY(), 50.);
666 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
667 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
668 }
669
670 void testSetEmptyHeaderWhenPartlyShownClipped()
671 {
672 scrollToBottom();
673 changeContentY(-30);
674 lvwph->setHeader(nullptr);
675
676 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
677 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
678 verifyItem(0, -588., 390., true, "Bold", true);
679 verifyItem(1, -198, 350., false, QString(), true);
680 verifyItem(2, 152, 390., false, "Lazy", false);
681 QCOMPARE(lvwph->m_minYExtent, 290.);
682 QCOMPARE(lvwph->m_clipItem->y(), 1428.);
683 QCOMPARE(lvwph->m_clipItem->clip(), false);
684 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
685 QCOMPARE(lvwph->contentY(), 1428.);
686 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
687 QTRY_VERIFY(lvwph->isAtYEnd());
688 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
689 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
690 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
691
692 scrollToTop();
693
694 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
695 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
696 verifyItem(0, 0., 190., false, "Agressive", false);
697 verifyItem(1, 190., 240., false, "Regular", false);
698 verifyItem(2, 430., 390., false, "Mild", false);
699 QCOMPARE(lvwph->m_minYExtent, -20.);
700 QCOMPARE(lvwph->m_clipItem->y(), 20.);
701 QCOMPARE(lvwph->m_clipItem->clip(), false);
702 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
703 QCOMPARE(lvwph->contentY(), 20.);
704 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
705 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
706 }
707
708 void testSetEmptyHeaderWhenPartlyShownNotClipped()
709 {
710 changeContentY(30);
711 lvwph->setHeader(nullptr);
712
713 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
714 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
715 verifyItem(0, -30., 190., false, "Agressive", true);
716 verifyItem(1, 160., 240., false, "Regular", false);
717 verifyItem(2, 400., 390., false, "Mild", false);
718 QCOMPARE(lvwph->m_minYExtent, 0.);
719 QCOMPARE(lvwph->m_clipItem->y(), 30.);
720 QCOMPARE(lvwph->m_clipItem->clip(), false);
721 QCOMPARE(lvwph->m_headerItem, (QQuickItem*)nullptr);
722 QCOMPARE(lvwph->contentY(), 30.);
723 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
724 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
725 QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
726 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
727 }
728
729 void testSetNullDelegate()
730 {
731 lvwph->setDelegate(nullptr);
732
733 QTRY_COMPARE(lvwph->m_visibleItems.count(), 0);
734 QCOMPARE(lvwph->m_firstVisibleIndex, -1);
735 QCOMPARE(lvwph->m_minYExtent, 0.);
736 QCOMPARE(lvwph->m_clipItem->y(), 0.);
737 QCOMPARE(lvwph->m_clipItem->clip(), false);
738 QCOMPARE(lvwph->m_headerItem->y(), 0.);
739 QCOMPARE(lvwph->m_headerItem->height(), 50.);
740 QCOMPARE(lvwph->contentY(), 0.);
741 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
742 QTRY_COMPARE(lvwph->contentHeight(), 50.);
743 QVERIFY(lvwph->isAtYBeginning());
744 QVERIFY(lvwph->isAtYEnd());
745 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
746 }
747
748 void testInsertItems()
749 {
750 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
751 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
752
753 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
754 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
755 verifyItem(0, 50., 190., false, "Agressive", false);
756 verifyItem(1, 240., 165., false, "Regular", false);
757 verifyItem(2, 405., 140., false, "Agressive", false);
758 verifyItem(3, 545., 240., true, "Regular", true);
759 verifyItem(4, 785., 390., true, "Mild", true);
760 QCOMPARE(lvwph->m_minYExtent, 0.);
761 QCOMPARE(lvwph->m_clipItem->y(), 0.);
762 QCOMPARE(lvwph->m_clipItem->clip(), false);
763 QCOMPARE(lvwph->m_headerItem->y(), 0.);
764 QCOMPARE(lvwph->m_headerItem->height(), 50.);
765 QCOMPARE(lvwph->contentY(), 0.);
766 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
767 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
768 }
769
770 void testInsertItemsStealSectionItem()
771 {
772 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
773
774 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
775 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
776 verifyItem(0, 50., 190., false, "Agressive", false);
777 verifyItem(1, 240., 165., false, "Regular", false);
778 verifyItem(2, 405., 200., false, QString(), true);
779 verifyItem(3, 605., 390., true, "Mild", true);
780 QCOMPARE(lvwph->m_minYExtent, 0.);
781 QCOMPARE(lvwph->m_clipItem->y(), 0.);
782 QCOMPARE(lvwph->m_clipItem->clip(), false);
783 QCOMPARE(lvwph->m_headerItem->y(), 0.);
784 QCOMPARE(lvwph->m_headerItem->height(), 50.);
785 QCOMPARE(lvwph->contentY(), 0.);
786 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
787 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
788 }
789
790
791 void testInsertItemsOnNotShownPosition()
792 {
793 changeContentY(800);
794
795 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
796 QTRY_COMPARE(lvwph->m_firstVisibleIndex, 2);
797 verifyItem(0, -320., 390., false, "Mild", true);
798 verifyItem(1, 70, 390., false, "Bold", false);
799 verifyItem(2, 460, 350., false, QString(), false);
800 verifyItem(3, 810, 390., true, "Lazy", true);
801 QCOMPARE(lvwph->m_minYExtent, 330.);
802 QCOMPARE(lvwph->m_clipItem->y(), 800.);
803 QCOMPARE(lvwph->m_clipItem->clip(), false);
804 QCOMPARE(lvwph->m_headerItem->y(), 0.);
805 QCOMPARE(lvwph->m_headerItem->height(), 50.);
806 QCOMPARE(lvwph->contentY(), 800.);
807 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
808 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
809 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
810 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
811
812 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
813 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
814
815 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
816 QCOMPARE(lvwph->m_firstVisibleIndex, 4);
817 verifyItem(0, -320., 390., false, "Mild", true);
818 verifyItem(1, 70, 390., false, "Bold", false);
819 verifyItem(2, 460, 350., false, QString(), false);
820 verifyItem(3, 810, 390., true, "Lazy", true);
821 QCOMPARE(lvwph->m_minYExtent, 1090.);
822 QCOMPARE(lvwph->m_clipItem->y(), 800.);
823 QCOMPARE(lvwph->m_clipItem->clip(), false);
824 QCOMPARE(lvwph->m_headerItem->y(), 0.);
825 QCOMPARE(lvwph->m_headerItem->height(), 50.);
826 QCOMPARE(lvwph->contentY(), 800.);
827 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
828 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
829 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
830 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
831
832 scrollToTop();
833
834 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
835 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
836 verifyItem(0, 50., 190., false, "Agressive", false);
837 verifyItem(1, 240., 165., false, "Regular", false);
838 verifyItem(2, 405., 140., false, "Agressive", false);
839 verifyItem(3, 545., 240., true, "Regular", true);
840 verifyItem(4, 785., 390., true, "Mild", true);
841 QCOMPARE(lvwph->m_minYExtent, 305.);
842 QCOMPARE(lvwph->m_clipItem->y(), -305.);
843 QCOMPARE(lvwph->m_clipItem->clip(), false);
844 QCOMPARE(lvwph->m_headerItem->y(), -305.);
845 QCOMPARE(lvwph->m_headerItem->height(), 50.);
846 QCOMPARE(lvwph->contentY(), -305.);
847 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
848 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
849 }
850
851 void testInsertItemsAtEndOfViewport()
852 {
853 changeContentY(60);
854
855 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
856 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
857 verifyItem(0, -10., 190., false, "Agressive", true);
858 verifyItem(1, 180., 240., false, "Regular", false);
859 verifyItem(2, 420., 390., false, "Mild", false);
860 verifyItem(3, 810., 390., true, "Bold", true);
861 QCOMPARE(lvwph->m_minYExtent, 0.);
862 QCOMPARE(lvwph->m_clipItem->y(), 60.);
863 QCOMPARE(lvwph->m_clipItem->clip(), false);
864 QCOMPARE(lvwph->m_headerItem->y(), 0.);
865 QCOMPARE(lvwph->m_headerItem->height(), 50.);
866 QCOMPARE(lvwph->contentY(), 60.);
867 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
868 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
869 QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
870 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
871
872 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
873 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 3), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
874
875 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
876 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
877 verifyItem(0, -10., 190., false, "Agressive", true);
878 verifyItem(1, 180., 240., false, "Regular", false);
879 verifyItem(2, 420., 390., false, "Mild", false);
880 verifyItem(3, 810., 165., true, "Regular", true);
881 QCOMPARE(lvwph->m_minYExtent, 0.);
882 QCOMPARE(lvwph->m_clipItem->y(), 60.);
883 QCOMPARE(lvwph->m_clipItem->clip(), false);
884 QCOMPARE(lvwph->m_headerItem->y(), 0.);
885 QCOMPARE(lvwph->m_headerItem->height(), 50.);
886 QCOMPARE(lvwph->contentY(), 60.);
887 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
888 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
889 QCOMPARE(section(lvwph->m_topSectionItem), QString("Agressive"));
890 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
891 }
892
893 void testInsertItemsBeforeValidIndex()
894 {
895 changeContentY(520);
896
897 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
898 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
899 verifyItem(0, -280., 240., true, "Regular", true);
900 verifyItem(1, -40, 390., false, "Mild", true);
901 verifyItem(2, 350, 390., false, "Bold", false);
902 verifyItem(3, 740, 350., true, QString(), true);
903 QCOMPARE(lvwph->m_minYExtent, 152.5);
904 QCOMPARE(lvwph->m_clipItem->y(), 520.);
905 QCOMPARE(lvwph->m_clipItem->clip(), false);
906 QCOMPARE(lvwph->m_headerItem->y(), 0.);
907 QCOMPARE(lvwph->m_headerItem->height(), 50.);
908 QCOMPARE(lvwph->contentY(), 520.);
909 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
910 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
911 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
912 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
913
914 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
915 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
916
917 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
918 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
919 verifyItem(0, -280., 240., true, "Regular", true);
920 verifyItem(1, -40, 390., false, "Mild", true);
921 verifyItem(2, 350, 390., false, "Bold", false);
922 verifyItem(3, 740, 350., true, QString(), true);
923 QCOMPARE(lvwph->m_minYExtent, 837.5);
924 QCOMPARE(lvwph->m_clipItem->y(), 520.);
925 QCOMPARE(lvwph->m_clipItem->clip(), false);
926 QCOMPARE(lvwph->m_headerItem->y(), 0.);
927 QCOMPARE(lvwph->m_headerItem->height(), 50.);
928 QCOMPARE(lvwph->contentY(), 520.);
929 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
930 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
931 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
932 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
933 }
934
935 void testInsertItemsBeforeViewport()
936 {
937 changeContentY(375);
938
939 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
940 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
941 verifyItem(0, -325., 190., true, "Agressive", true);
942 verifyItem(1, -135, 240., false, "Regular", true);
943 verifyItem(2, 105, 390., false, "Mild", false);
944 verifyItem(3, 495, 390., false, "Bold", false);
945 QCOMPARE(lvwph->m_minYExtent, 0.);
946 QCOMPARE(lvwph->m_clipItem->y(), 375.);
947 QCOMPARE(lvwph->m_clipItem->clip(), false);
948 QCOMPARE(lvwph->m_headerItem->y(), 0.);
949 QCOMPARE(lvwph->m_headerItem->height(), 50.);
950 QCOMPARE(lvwph->contentY(), 375.);
951 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
952 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
953 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
954 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
955
956 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
957 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
958
959 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
960 QCOMPARE(lvwph->m_firstVisibleIndex, 2);
961 verifyItem(0, -275., 140., true, "Agressive", true);
962 verifyItem(1, -135., 240., false, "Regular", true);
963 verifyItem(2, 105, 390., false, "Mild", false);
964 verifyItem(3, 495, 390., false, "Bold", false);
965 QCOMPARE(lvwph->m_minYExtent, 530.);
966 QCOMPARE(lvwph->m_clipItem->y(), 375.);
967 QCOMPARE(lvwph->m_clipItem->clip(), false);
968 QCOMPARE(lvwph->m_headerItem->y(), 0.);
969 QCOMPARE(lvwph->m_headerItem->height(), 50.);
970 QCOMPARE(lvwph->contentY(), 375.);
971 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
972 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
973 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
974 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
975
976 scrollToTop();
977
978 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
979 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
980 verifyItem(0, 50., 190., false, "Agressive", false);
981 verifyItem(1, 240., 165., false, "Regular", false);
982 verifyItem(2, 405., 140., false, "Agressive", false);
983 verifyItem(3, 545., 240., true, "Regular", true);
984 verifyItem(4, 785., 390., true, "Mild", true);
985 QCOMPARE(lvwph->m_minYExtent, 305.);
986 QCOMPARE(lvwph->m_clipItem->y(), -305.);
987 QCOMPARE(lvwph->m_clipItem->clip(), false);
988 QCOMPARE(lvwph->m_headerItem->y(), -305.);
989 QCOMPARE(lvwph->m_headerItem->height(), 50.);
990 QCOMPARE(lvwph->contentY(), -305.);
991 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
992 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
993 }
994
995 void testInsertItemsAtBottom()
996 {
997 scrollToBottom();
998
999 QVERIFY(lvwph->isAtYEnd());
1000
1001 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 100), Q_ARG(QVariant, "Agressive"));
1002 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 125), Q_ARG(QVariant, "Regular"));
1003
1004 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1005 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1006 verifyItem(0, -588., 390., true, "Bold", true);
1007 verifyItem(1, -198, 350., false, QString(), true);
1008 verifyItem(2, 152, 390., false, "Lazy", false);
1009 verifyItem(3, 542, 165., true, "Regular", true);
1010 verifyItem(4, 707, 140., true, "Agressive", true);
1011 QCOMPARE(lvwph->m_minYExtent, 41.);
1012 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1013 QCOMPARE(lvwph->m_clipItem->clip(), false);
1014 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1015 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1016 QCOMPARE(lvwph->contentY(), 1458.);
1017 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1018 QVERIFY(!lvwph->isAtYEnd());
1019 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1020 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1021 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1022
1023 scrollToBottom();
1024
1025 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1026 QCOMPARE(lvwph->m_firstVisibleIndex, 4);
1027 verifyItem(0, -503., 350., true, QString(), true);
1028 verifyItem(1, -153, 390., false, "Lazy", true);
1029 verifyItem(2, 237, 165., false, "Regular", false);
1030 verifyItem(3, 402, 140., false, "Agressive", false);
1031 QCOMPARE(lvwph->m_minYExtent, -165.);
1032 QCOMPARE(lvwph->m_clipItem->y(), 1763.);
1033 QCOMPARE(lvwph->m_clipItem->clip(), false);
1034 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1035 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1036 QCOMPARE(lvwph->contentY(), 1763.);
1037 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1038 QVERIFY(lvwph->isAtYEnd());
1039 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1040 QCOMPARE(section(lvwph->m_topSectionItem), QString("Lazy"));
1041 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1042 }
1043
1044 void testInsertItemAtTop()
1045 {
1046 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1047
1048 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1049 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1050 verifyItem(0, 50., 115., false, "Agressive", false);
1051 verifyItem(1, 165., 150., false, QString(), false);
1052 verifyItem(2, 315., 240., false, "Regular", false);
1053 verifyItem(3, 555., 390., true, "Mild", true);
1054 QCOMPARE(lvwph->m_minYExtent, 0.);
1055 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1056 QCOMPARE(lvwph->m_clipItem->clip(), false);
1057 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1058 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1059 QCOMPARE(lvwph->contentY(), 0.);
1060 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1061 QVERIFY(lvwph->isAtYBeginning());
1062 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1063 }
1064
1065 void testInsertItem10SmallItemsAtTopWhenAtBottom()
1066 {
1067 scrollToBottom();
1068
1069 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1070 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1071 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1072 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1073 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1074 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1075 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1076 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1077 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Regular"));
1078 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 75), Q_ARG(QVariant, "Agressive"));
1079
1080 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1081 QCOMPARE(lvwph->m_firstVisibleIndex, 13);
1082 verifyItem(0, -588., 390., true, "Bold", true);
1083 verifyItem(1, -198, 350., false, QString(), true);
1084 verifyItem(2, 152, 390., false, "Lazy", false);
1085 QCOMPARE(lvwph->m_minYExtent, 12230./3.);
1086 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1087 QCOMPARE(lvwph->m_clipItem->clip(), false);
1088 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1089 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1090 QCOMPARE(lvwph->contentY(), 1458.);
1091 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1092 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1093 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1094 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1095
1096 changeContentY(-1700);
1097
1098 QTRY_COMPARE(lvwph->m_visibleItems.count(), 10);
1099 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1100 verifyItem(0, -323., 75., true, QString(), true);
1101 verifyItem(1, -248., 115., true, "Regular", true);
1102 verifyItem(2, -133., 75., true, QString(), true);
1103 verifyItem(3, -58., 75., false, QString(), true);
1104 verifyItem(4, 17., 115., false, "Agressive", false);
1105 verifyItem(5, 132., 75., false, QString(), true);
1106 verifyItem(6, 207., 75., false, QString(), true);
1107 verifyItem(7, 282., 150., false, QString(), true);
1108 verifyItem(8, 432., 240., false, "Regular", false);
1109 QCOMPARE(lvwph->m_minYExtent, 980.5);
1110 QCOMPARE(lvwph->m_clipItem->y(), -192.);
1111 QCOMPARE(lvwph->m_clipItem->clip(), true);
1112 QCOMPARE(lvwph->m_headerItem->y(), -242.);
1113 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1114 QCOMPARE(lvwph->contentY(), -242.);
1115 QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
1116 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1117 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1118 QCOMPARE(lvwph->m_topSectionItem->y(), -23.);
1119 }
1120
1121 void testRemoveItemsAtTop()
1122 {
1123 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1124
1125 QTRY_COMPARE(lvwph->m_visibleItems.count(), 2);
1126 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1127 verifyItem(0, 50., 390., false, "Mild", false);
1128 verifyItem(1, 440., 390., false, "Bold", false);
1129 QCOMPARE(lvwph->m_minYExtent, 0.);
1130 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1131 QCOMPARE(lvwph->m_clipItem->clip(), false);
1132 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1133 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1134 QCOMPARE(lvwph->contentY(), 0.);
1135 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1136 QVERIFY(lvwph->isAtYBeginning());
1137 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1138 }
1139
1140 void testRemoveNonCreatedItemsAtTopWhenAtBottom()
1141 {
1142 scrollToBottom();
1143
1144 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 2));
1145
1146 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1147 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1148 verifyItem(0, -588., 390., true, "Bold", true);
1149 verifyItem(1, -198, 350., false, QString(), true);
1150 verifyItem(2, 152, 390., false, "Lazy", false);
1151 QCOMPARE(lvwph->m_minYExtent, -1330./3.);
1152 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1153 QCOMPARE(lvwph->m_clipItem->clip(), false);
1154 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1155 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1156 QCOMPARE(lvwph->contentY(), 1458.);
1157 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1158 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1159 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1160 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1161 }
1162
1163 void testRemoveLastItemsAtBottom()
1164 {
1165 scrollToBottom();
1166
1167 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 4), Q_ARG(QVariant, 2));
1168
1169 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1170 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1171 verifyItem(0, -478., 240., true, "Regular", true);
1172 verifyItem(1, -238, 390., false, "Mild", true);
1173 verifyItem(2, 152, 390., false, "Bold", false);
1174 QCOMPARE(lvwph->m_minYExtent, 150.);
1175 QCOMPARE(lvwph->m_clipItem->y(), 718.);
1176 QCOMPARE(lvwph->m_clipItem->clip(), false);
1177 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1178 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1179 QCOMPARE(lvwph->contentY(), 718.);
1180 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1181 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1182 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1183 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1184 }
1185
1186 void testRemoveItemOutOfViewport()
1187 {
1188 changeContentY(520);
1189
1190 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1191 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1192 verifyItem(0, -280., 240., true, "Regular", true);
1193 verifyItem(1, -40, 390., false, "Mild", true);
1194 verifyItem(2, 350, 390., false, "Bold", false);
1195 verifyItem(3, 740, 350., true, QString(), true);
1196 QCOMPARE(lvwph->m_minYExtent, 152.5);
1197 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1198 QCOMPARE(lvwph->m_clipItem->clip(), false);
1199 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1200 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1201 QCOMPARE(lvwph->contentY(), 520.);
1202 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1203 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1204 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1205 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1206
1207 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1208
1209 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1210 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1211 verifyItem(0, -230., 190., true, "Agressive", true);
1212 verifyItem(1, -40, 390., false, "Mild", true);
1213 verifyItem(2, 350, 390., false, "Bold", false);
1214 verifyItem(3, 740, 350., true, QString(), true);
1215 QCOMPARE(lvwph->m_minYExtent, -240.);
1216 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1217 QCOMPARE(lvwph->m_clipItem->clip(), false);
1218 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1219 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1220 QCOMPARE(lvwph->contentY(), 520.);
1221 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1222 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1223 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1224 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1225 }
1226
1227 void testRemoveFirstOfCategory()
1228 {
1229 changeContentY(520);
1230
1231 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1232 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1233 verifyItem(0, -280., 240., true, "Regular", true);
1234 verifyItem(1, -40, 390., false, "Mild", true);
1235 verifyItem(2, 350, 390., false, "Bold", false);
1236 verifyItem(3, 740, 350., true, QString(), true);
1237 QCOMPARE(lvwph->m_minYExtent, 152.5);
1238 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1239 QCOMPARE(lvwph->m_clipItem->clip(), false);
1240 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1241 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1242 QCOMPARE(lvwph->contentY(), 520.);
1243 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1244 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1245 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1246 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1247
1248 QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 3), Q_ARG(QVariant, 1));
1249
1250 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1251 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
1252 verifyItem(0, -280., 240., true, "Regular", true);
1253 verifyItem(1, -40, 390., false, "Mild", true);
1254 verifyItem(2, 350, 390., false, "Bold", false);
1255 verifyItem(3, 740, 390., true, "Lazy", true);
1256 QCOMPARE(lvwph->m_minYExtent, 162.5);
1257 QCOMPARE(lvwph->m_clipItem->y(), 520.);
1258 QCOMPARE(lvwph->m_clipItem->clip(), false);
1259 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1260 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1261 QCOMPARE(lvwph->contentY(), 520.);
1262 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1263 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1264 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1265 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1266 }
1267
1268 void testAddSecondToCulledCategoryOfTwo()
1269 {
1270 // Do some setup
1271 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1272 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1273
1274 changeContentY(200);
1275
1276 // Very the items are culled
1277 verifyItem(0, -150., 65., true, "Agressive", true);
1278 verifyItem(1, -85, 25., true, QString(), true);
1279
1280 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 25), Q_ARG(QVariant, "Agressive"));
1281
1282 // Very the new item is there correctly
1283 verifyItem(0, -175., 65., true, "Agressive", true);
1284 verifyItem(1, -110, 25., true, QString(), true);
1285 verifyItem(2, -85, 25., true, QString(), true);
1286 }
1287
1288 void testMoveFirstItems()
1289 {
1290 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
1291
1292 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1293 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1294 verifyItem(0, 50., 240., false, "Regular", false);
1295 verifyItem(1, 290., 190., false, "Agressive", false);
1296 verifyItem(2, 480., 390., false, "Mild", false);
1297 QCOMPARE(lvwph->m_minYExtent, 0.);
1298 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1299 QCOMPARE(lvwph->m_clipItem->clip(), false);
1300 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1301 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1302 QCOMPARE(lvwph->contentY(), 0.);
1303 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1304 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1305 }
1306
1307 void testMoveFirstOutOfVisibleItems()
1308 {
1309 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 4), Q_ARG(QVariant, 1));
1310
1311 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1312 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1313 verifyItem(0, 50., 240., false, "Regular", false);
1314 verifyItem(1, 290., 390., false, "Mild", false);
1315 verifyItem(2, 680., 390., true, "Bold", true);
1316 QCOMPARE(lvwph->m_minYExtent, 0.);
1317 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1318 QCOMPARE(lvwph->m_clipItem->clip(), false);
1319 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1320 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1321 QCOMPARE(lvwph->contentY(), 0.);
1322 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1323 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1324 }
1325
1326 void testMoveFirstToLastAtBottom()
1327 {
1328 scrollToBottom();
1329
1330 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1331 QCOMPARE(lvwph->m_firstVisibleIndex, 3);
1332 verifyItem(0, -588., 390., true, "Bold", true);
1333 verifyItem(1, -198, 350., false, QString(), true);
1334 verifyItem(2, 152, 390., false, "Lazy", false);
1335 QCOMPARE(lvwph->m_minYExtent, 310.);
1336 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1337 QCOMPARE(lvwph->m_clipItem->clip(), false);
1338 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1339 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1340 QCOMPARE(lvwph->contentY(), 1458.);
1341 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1342 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1343 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1344 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1345
1346 QMetaObject::invokeMethod(model, "moveItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 5), Q_ARG(QVariant, 1));
1347
1348 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1349 QCOMPARE(lvwph->m_firstVisibleIndex, 2);
1350 verifyItem(0, -588., 390., true, "Bold", true);
1351 verifyItem(1, -198, 350., false, QString(), true);
1352 verifyItem(2, 152, 390., false, "Lazy", false);
1353 verifyItem(3, 542, 190., true, "Agressive", true);
1354 QCOMPARE(lvwph->m_minYExtent, -160.);
1355 QCOMPARE(lvwph->m_clipItem->y(), 1458.);
1356 QCOMPARE(lvwph->m_clipItem->clip(), false);
1357 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1358 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1359 QCOMPARE(lvwph->contentY(), 1458.);
1360 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1361 QVERIFY(!lvwph->isAtYEnd());
1362 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1363 QCOMPARE(section(lvwph->m_topSectionItem), QString("Bold"));
1364 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1365 }
1366
1367 void testChangeSizeVisibleItemNotOnViewport()
1368 {
1369 changeContentY(490);
1370
1371 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1372 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1373 verifyItem(0, -440., 190., true, "Agressive", true);
1374 verifyItem(1, -250., 240., true, "Regular", true);
1375 verifyItem(2, -10, 390., false, "Mild", true);
1376 verifyItem(3, 380, 390., false, "Bold", false);
1377 verifyItem(4, 770, 350., true, QString(), true);
1378 QCOMPARE(lvwph->m_minYExtent, 0.);
1379 QCOMPARE(lvwph->m_clipItem->y(), 490.);
1380 QCOMPARE(lvwph->m_clipItem->clip(), false);
1381 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1382 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1383 QCOMPARE(lvwph->contentY(), 490.);
1384 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1385 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1386 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1387 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1388
1389 model->setProperty(1, "size", 100);
1390
1391 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1392 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1393 verifyItem(0, -340., 190., true, "Agressive", true);
1394 verifyItem(1, -150., 140., true, "Regular", true);
1395 verifyItem(2, -10, 390., false, "Mild", true);
1396 verifyItem(3, 380, 390., false, "Bold", false);
1397 verifyItem(4, 770, 350., true, QString(), true);
1398 QCOMPARE(lvwph->m_minYExtent, -100.);
1399 QCOMPARE(lvwph->m_clipItem->y(), 490.);
1400 QCOMPARE(lvwph->m_clipItem->clip(), false);
1401 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1402 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1403 QCOMPARE(lvwph->contentY(), 490.);
1404 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1405 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1406 QCOMPARE(section(lvwph->m_topSectionItem), QString("Mild"));
1407 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1408 }
1409
1410 void testShowHeaderCloseToTheTop()
1411 {
1412 changeContentY(375);
1413
1414 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1415 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1416 verifyItem(0, -325., 190., true, "Agressive", true);
1417 verifyItem(1, -135, 240., false, "Regular", true);
1418 verifyItem(2, 105, 390., false, "Mild", false);
1419 verifyItem(3, 495, 390., false, "Bold", false);
1420 QCOMPARE(lvwph->m_minYExtent, 0.);
1421 QCOMPARE(lvwph->m_clipItem->y(), 375.);
1422 QCOMPARE(lvwph->m_clipItem->clip(), false);
1423 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1424 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1425 QCOMPARE(lvwph->contentY(), 375.);
1426 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);;
1427 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1428 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1429 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1430
1431 lvwph->showHeader();
1432
1433 QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1434 QTRY_COMPARE(lvwph->m_visibleItems.count(), 4);
1435 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1436 verifyItem(0, -375., 190., true, "Agressive", true);
1437 verifyItem(1, -185, 240., false, "Regular", true);
1438 verifyItem(2, 55, 390., false, "Mild", false);
1439 verifyItem(3, 445, 390., false, "Bold", false);
1440 QCOMPARE(lvwph->m_minYExtent, 50.);
1441 QCOMPARE(lvwph->m_clipItem->y(), 375.);
1442 QCOMPARE(lvwph->m_clipItem->clip(), true);
1443 QCOMPARE(lvwph->m_headerItem->y(), 325.);
1444 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1445 QCOMPARE(lvwph->contentY(), 325.);
1446 QCOMPARE(lvwph->m_headerItemShownHeight, 50.);
1447 QVERIFY(!QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1448 QCOMPARE(section(lvwph->m_topSectionItem), QString("Regular"));
1449 QCOMPARE(lvwph->m_topSectionItem->y(), 0.);
1450
1451 scrollToTop();
1452
1453 QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
1454 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1455 verifyItem(0, 50., 190., false, "Agressive", false);
1456 verifyItem(1, 240., 240., false, "Regular", false);
1457 verifyItem(2, 480., 390., false, "Mild", false);
1458 QCOMPARE(lvwph->m_minYExtent, 50.);
1459 QCOMPARE(lvwph->m_clipItem->y(), -50.);
1460 QCOMPARE(lvwph->m_clipItem->clip(), false);
1461 QCOMPARE(lvwph->m_headerItem->y(), -50.);
1462 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1463 QCOMPARE(lvwph->contentY(), -50.);
1464 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1465 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
1466 }
1467
1468 void testShowHeaderAtBottom()
1469 {
1470 scrollToBottom();
1471
1472 lvwph->showHeader();
1473
1474 QTRY_VERIFY(!lvwph->m_headerShowAnimation->isRunning());
1475 QTRY_VERIFY (lvwph->isAtYEnd());
1476 }
1477
1478 void growWindow()
1479 {
1480 view->rootObject()->setHeight(850);
1481
1482 QTRY_COMPARE(lvwph->m_visibleItems.count(), 5);
1483 QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1484 verifyItem(0, 50., 190., false, "Agressive", false);
1485 verifyItem(1, 240., 240., false, "Regular", false);
1486 verifyItem(2, 480., 390., false, "Mild", false);
1487 verifyItem(3, 870., 390., true, "Bold", true);
1488 verifyItem(4, 1260., 350., true, QString(), true);
1489 QCOMPARE(lvwph->m_minYExtent, 0.);
1490 QCOMPARE(lvwph->m_clipItem->y(), 0.);
1491 QCOMPARE(lvwph->m_clipItem->clip(), false);
1492 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1493 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1494 QCOMPARE(lvwph->contentY(), 0.);
1495 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1496 }
1497
1498 void growWindowAtBottom()
1499 {
1500 // Need a bunch small items at the bottom to trigger the problem
1501 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1502 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1503 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1504 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1505 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1506 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1507 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1508 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1509 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1510 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1511 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1512 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Regular"));
1513 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1514 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1515 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1516 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1517 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1518 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1519 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1520 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 6), Q_ARG(QVariant, 50), Q_ARG(QVariant, "Agressive"));
1521
1522 scrollToBottom();
1523
1524 view->rootObject()->setHeight(850);
1525
1526 QTRY_COMPARE(lvwph->m_visibleItems.count(), 21);
1527 QCOMPARE(lvwph->m_firstVisibleIndex, 5);
1528 verifyItem(0, -660., 390., true, "Lazy", true);
1529 verifyItem(1, -270., 90., true, "Agressive", true);
1530 verifyItem(2, -180, 50., true, QString(), true);
1531 verifyItem(3, -130, 50., true, QString(), true);
1532 verifyItem(4, -80, 50., true, QString(), true);
1533 verifyItem(5, -30, 50., false, QString(), true);
1534 verifyItem(6, 20, 50., false, QString(), true);
1535 verifyItem(7, 70, 50., false, QString(), true);
1536 verifyItem(8, 120, 50., false, QString(), true);
1537 verifyItem(9, 170, 90., false, "Regular", false);
1538 verifyItem(10, 260, 50., false, QString(), true);
1539 verifyItem(11, 310, 50., false, QString(), true);
1540 verifyItem(12, 360, 50., false, QString(), true);
1541 verifyItem(13, 410, 50., false, QString(), true);
1542 verifyItem(14, 460, 50., false, QString(), true);
1543 verifyItem(15, 510, 90., false, "Agressive", false);
1544 verifyItem(16, 600, 50., false, QString(), true);
1545 verifyItem(17, 650, 50., false, QString(), true);
1546 verifyItem(18, 700, 50., false, QString(), true);
1547 verifyItem(19, 750, 50., false, QString(), true);
1548 verifyItem(20, 800, 50., false, QString(), true);
1549 QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - 1970 + 50);
1550 QCOMPARE(lvwph->m_clipItem->y(), 1970.);
1551 QCOMPARE(lvwph->m_clipItem->clip(), false);
1552 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1553 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1554 QCOMPARE(lvwph->contentY(), 1970.);
1555 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1556 }
1557
1558private:
1559 QQuickView *view;
1560 ListViewWithPageHeader *lvwph;
1561 QQuickListModel *model;
1562 QQmlComponent *otherDelegate;
1563};
1564
1565QTEST_MAIN(ListViewWithPageHeaderTestSection)
1566
1567#include "listviewwithpageheadertestsection.moc"
01568
=== added file 'tests/plugins/ListViewWithPageHeader/test.qml'
--- tests/plugins/ListViewWithPageHeader/test.qml 1970-01-01 00:00:00 +0000
+++ tests/plugins/ListViewWithPageHeader/test.qml 2013-07-01 11:23:32 +0000
@@ -0,0 +1,96 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import ListViewWithPageHeader 0.1
19
20Rectangle {
21 width: 300
22 height: 542
23 color: "lightblue"
24
25 ListModel {
26 id: model
27
28 function insertItem(index, size) {
29 insert(index, { size: size });
30 }
31
32 function removeItems(index, count) {
33 remove(index, count);
34 }
35
36 function moveItems(indexFrom, indexTo, count) {
37 move(indexFrom, indexTo, count);
38 }
39
40 ListElement { size: 150 }
41 ListElement { size: 200 }
42 ListElement { size: 350 }
43 ListElement { size: 350 }
44 ListElement { size: 350 }
45 ListElement { size: 350 }
46 }
47
48 Component {
49 id: otherRect
50 Rectangle {
51 height: 35
52 width: parent.width
53 color: index % 2 == 0 ? "yellow" : "purple"
54 }
55 }
56
57 ListViewWithPageHeader {
58 id: listView
59 width: parent.width
60 anchors.top: parent.top
61 anchors.bottom: parent.bottom
62 model: model
63 delegate: Rectangle {
64 property bool timerDone: false
65 width: parent.width - 20
66 x: 10
67 color: index % 2 == 0 ? "red" : "blue"
68 height: timerDone ? size : 350
69 Text {
70 text: index
71 }
72 Timer {
73 id: sizeTimer
74 interval: 10;
75 onTriggered: {
76 timerDone = true
77 }
78 }
79 Component.onCompleted: {
80 sizeTimer.start()
81 }
82 }
83
84 pageHeader: Rectangle {
85 color: "transparent"
86 width: parent.width
87 height: 50
88 implicitHeight: 50
89 Text {
90 anchors.fill: parent
91 text: "APPS"
92 font.pixelSize: 40
93 }
94 }
95 }
96}
097
=== added file 'tests/plugins/ListViewWithPageHeader/test_section.qml'
--- tests/plugins/ListViewWithPageHeader/test_section.qml 1970-01-01 00:00:00 +0000
+++ tests/plugins/ListViewWithPageHeader/test_section.qml 2013-07-01 11:23:32 +0000
@@ -0,0 +1,106 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import ListViewWithPageHeader 0.1
19
20Rectangle {
21 width: 300
22 height: 542
23 color: "lightblue"
24
25 ListModel {
26 id: model
27
28 function insertItem(index, size, type) {
29 insert(index, { size: size, type: type });
30 }
31
32 function removeItems(index, count) {
33 remove(index, count);
34 }
35
36 function moveItems(indexFrom, indexTo, count) {
37 move(indexFrom, indexTo, count);
38 }
39
40 ListElement { type: "Agressive"; size: 150 }
41 ListElement { type: "Regular"; size: 200 }
42 ListElement { type: "Mild"; size: 350 }
43 ListElement { type: "Bold"; size: 350 }
44 ListElement { type: "Bold"; size: 350 }
45 ListElement { type: "Lazy"; size: 350 }
46 }
47
48 Component {
49 id: otherRect
50 Rectangle {
51 height: 35
52 width: parent.width
53 color: index % 2 == 0 ? "yellow" : "purple"
54 }
55 }
56
57 ListViewWithPageHeader {
58 id: listView
59 width: parent.width
60 anchors.top: parent.top
61 anchors.bottom: parent.bottom
62 model: model
63 delegate: Rectangle {
64 property bool timerDone: false
65 width: parent.width - 20
66 x: 10
67 color: index % 2 == 0 ? "red" : "blue"
68 height: timerDone ? size : 350
69 Text {
70 text: index
71 }
72 Timer {
73 id: sizeTimer
74 interval: 10;
75 onTriggered: {
76 timerDone = true
77 }
78 }
79 Component.onCompleted: {
80 sizeTimer.start()
81 }
82 }
83
84 pageHeader: Rectangle {
85 color: "transparent"
86 width: parent.width
87 height: 50
88 implicitHeight: 50
89 Text {
90 anchors.fill: parent
91 text: "APPS"
92 font.pixelSize: 40
93 }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: