Merge lp:~zsombi/ubuntu-ui-toolkit/bottomEdge into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Tim Peeters
Approved revision: 1781
Merged at revision: 1731
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/bottomEdge
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 4292 lines (+3826/-40)
47 files modified
components.api (+48/-0)
examples/ubuntu-ui-toolkit-gallery/BottomEdgePage.qml (+177/-14)
examples/ubuntu-ui-toolkit-gallery/Template.qml (+1/-0)
examples/ubuntu-ui-toolkit-gallery/TemplateRow.qml (+1/-1)
examples/ubuntu-ui-toolkit-gallery/gallery-logging.config (+1/-0)
src/Ubuntu/Components/1.3/AdaptivePageLayout.qml (+0/-2)
src/Ubuntu/Components/1.3/PageTreeNode.qml (+0/-4)
src/Ubuntu/Components/ComponentModule.pro (+1/-1)
src/Ubuntu/Components/Themes/Ambiance/1.3/BottomEdgeStyle.qml (+125/-0)
src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro (+2/-1)
src/Ubuntu/Components/Themes/Ambiance/qmldir (+2/-0)
src/Ubuntu/Components/plugin/plugin.cpp (+6/-0)
src/Ubuntu/Components/plugin/plugin.pri (+9/-2)
src/Ubuntu/Components/plugin/ucaction.h (+6/-5)
src/Ubuntu/Components/plugin/ucbottomedge.cpp (+1059/-0)
src/Ubuntu/Components/plugin/ucbottomedge.h (+120/-0)
src/Ubuntu/Components/plugin/ucbottomedge_p.h (+111/-0)
src/Ubuntu/Components/plugin/ucbottomedgehint.cpp (+1/-1)
src/Ubuntu/Components/plugin/ucbottomedgehint.h (+2/-0)
src/Ubuntu/Components/plugin/ucbottomedgeregion.cpp (+253/-0)
src/Ubuntu/Components/plugin/ucbottomedgeregion.h (+78/-0)
src/Ubuntu/Components/plugin/ucbottomedgestyle.cpp (+65/-0)
src/Ubuntu/Components/plugin/ucbottomedgestyle.h (+60/-0)
src/Ubuntu/Components/plugin/ucstyleditembase.cpp (+21/-5)
src/Ubuntu/Components/plugin/ucstyleditembase.h (+1/-0)
src/Ubuntu/Components/plugin/ucstyleditembase_p.h (+2/-0)
src/Ubuntu/Test/plugin/uctestextras.cpp (+136/-3)
src/Ubuntu/Test/plugin/uctestextras.h (+6/-0)
tests/unit_x11/tst_bottomedge/AddCustomRegionOnCompleted.qml (+41/-0)
tests/unit_x11/tst_bottomedge/AddCustomRegionOwnedByOtherBottomEdge.qml (+39/-0)
tests/unit_x11/tst_bottomedge/AddCustomRegionUsingDataProperty.qml (+33/-0)
tests/unit_x11/tst_bottomedge/AddCustomRegionUsingRegionsProperty.qml (+33/-0)
tests/unit_x11/tst_bottomedge/AlternateDefaultRegionContent.qml (+53/-0)
tests/unit_x11/tst_bottomedge/AlternateRegionContent.qml (+47/-0)
tests/unit_x11/tst_bottomedge/AutoCollapseInPageHeader.qml (+45/-0)
tests/unit_x11/tst_bottomedge/AutoCollapseInPageWithPageHeader.qml (+44/-0)
tests/unit_x11/tst_bottomedge/BottomEdgeInItem.qml (+36/-0)
tests/unit_x11/tst_bottomedge/ClearCustomRegions.qml (+42/-0)
tests/unit_x11/tst_bottomedge/Defaults.qml (+28/-0)
tests/unit_x11/tst_bottomedge/DifferentSizes.qml (+34/-0)
tests/unit_x11/tst_bottomedge/LastItem.qml (+46/-0)
tests/unit_x11/tst_bottomedge/LeanActiveRegionChange.qml (+45/-0)
tests/unit_x11/tst_bottomedge/OverlappingRegions.qml (+46/-0)
tests/unit_x11/tst_bottomedge/ShorterBottomEdge.qml (+39/-0)
tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp (+856/-0)
tests/unit_x11/tst_bottomedge/tst_bottomedge.pro (+23/-0)
tests/unit_x11/unit_x11.pro (+2/-1)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/bottomEdge
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Tim Peeters Approve
Review via email: mp+278336@code.launchpad.net

Commit message

BottomEdge component

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

In the gallery, I can over-drag the bottom edge a bit, which puts the app in a weird state until it pops back to a closed bottom edge in about 500ms. See https://www.dropbox.com/s/f1n2cthukzl4e31/Screenshot%202015-11-23%2021.14.55.png?dl=0

I think instead of this, the contents should snap to open fully instead of being able to drag it too far.

review: Needs Fixing
Revision history for this message
Tim Peeters (tpeeters) wrote :

qml: WARNING! Do not put Page/Tabs/PageStack inside another Page because that causes confusion which is the active page that sets the title and actions.

This is a bit annoying. I think we can remove this warning now. With the new header, it is no longer a problem to define a Page inside another Page.

Revision history for this message
Tim Peeters (tpeeters) wrote :

> qml: WARNING! Do not put Page/Tabs/PageStack inside another Page because that
> causes confusion which is the active page that sets the title and actions.
>
> This is a bit annoying. I think we can remove this warning now. With the new
> header, it is no longer a problem to define a Page inside another Page.

better to do that in another MR, so just leave it here.

Revision history for this message
Tim Peeters (tpeeters) wrote :

I find the line on top of the bottom edge when it is open ugly.

https://www.dropbox.com/s/mvexgb66nt5nr3n/Screenshot%202015-11-23%2021.27.24.png?dl=0

Also, see the comments I added to the API doc, and one diff comment below (more to follow).

Revision history for this message
Tim Peeters (tpeeters) wrote :

I don't understand what "push content into the layout" does, and why when I open the bottom edge with that checkbox checked, it says state: hidden in the header: https://www.dropbox.com/s/dns1zeuivsdfn49/Screenshot%202015-11-23%2021.29.50.png?dl=0

Revision history for this message
Tim Peeters (tpeeters) wrote :

See more inline comments.

Since the BottomEdgeRegions are (at the moment) mainly used in the browser, perhaps we should ask the browser guys to test this MR (ideally to have a prototype browser that uses this) to see if it fulfills all their requirements.

Revision history for this message
Zsombor Egri (zsombi) wrote :

> See more inline comments.
>
>
> Since the BottomEdgeRegions are (at the moment) mainly used in the browser,
> perhaps we should ask the browser guys to test this MR (ideally to have a
> prototype browser that uses this) to see if it fulfills all their
> requirements.

It is not only in the Browser, there can be many other use cases for it. And then the whole logic is implemented using that. We talked about this in Berlin with mzanetti while going to the hotel from airport that he'd need such a functionality in his apps. And honestly I would not spend time to add it later when we have it right away.

Revision history for this message
Zsombor Egri (zsombi) wrote :

Replied to the inline comments, and pushed the changes I had so far. Revisions starting 1740 are for tests, please check them one by one to see the progress.

There are more tests to come. Will keep you posted.

1740. By Zsombor Egri

implicit height fix

1741. By Zsombor Egri

implicit height fix

1742. By Zsombor Egri

complete default check

1743. By Zsombor Egri

fix reparenting

1744. By Zsombor Egri

adding remaining test skeletons

1745. By Zsombor Egri

test that BottomEdgeStyle instance is always the last child

1746. By Zsombor Egri

test commit on click

1747. By Zsombor Egri

touch-click commits

1748. By Zsombor Egri

rename state into status, content into contentUrl

1749. By Zsombor Egri

verifying state transition when the bottom edge is swiped for a short time

1750. By Zsombor Egri

verifying state transition when the bottom edge is swiped for a short time

1751. By Zsombor Egri

add some threshold and extra steps especially for touch to properly produce a swipe

1752. By Zsombor Egri

drag downwards after dragged upwards collapses

1753. By Zsombor Egri

bottomedge height is less than the parent's height

1754. By Zsombor Egri

do not overshoot; API fix

1755. By Zsombor Egri

commit signals test

1756. By Zsombor Egri

collapse signals test

1757. By Zsombor Egri

collapse signals test

1758. By Zsombor Egri

fixing doc for contentUrl and contentComponent

1759. By Zsombor Egri

wording fix

1760. By Zsombor Egri

wording fix

1761. By Zsombor Egri

typo fix

1762. By Zsombor Egri

property fix

1763. By Zsombor Egri

qdoc tag fix

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

The hint text is not centered any more (in gallery), see https://www.dropbox.com/s/uvwzak5eumwcgnp/Screenshot%202015-11-26%2011.04.38.png?dl=0

Revision history for this message
Tim Peeters (tpeeters) wrote :

> > 304 - // we need to clip because the header does not have a background
> > 305 - clip: true
> > we shouldn't just remove this. It is no longer needed with the new PageHeader,
> > but apps may still > use the old AppHeader (by configuring Page.head, instead of
> > using Page.header).

> If we have this clip set, the bottom edge content header will always be clipped. I am open
> for solutions.

Removing it will break current apps. The solution for UITK 1.4/2.0 will come easy, because there we will remove the old AppHeader.

For now, we could add a solid background to the AppHeader that is the same color as the MainView.headerColor. However, if the app has a background texture or gradient (old designs), the header does not match any more, so it may mess up existing apps. An alternative is to force people to switch to the new header when they use APL.

1764. By Zsombor Egri

commit on collapse test case

1765. By Zsombor Egri

region operation tests

1766. By Zsombor Egri

alternate default region content

1767. By Zsombor Egri

rest order reshufled, covered tests removed

1768. By Zsombor Egri

dragEnded test

1769. By Zsombor Egri

end drag in region commits to teh region top

1770. By Zsombor Egri

collapse when the drag ends in an area that is not covered by any region

1771. By Zsombor Egri

remove tests covered by default check

1772. By Zsombor Egri

validate and warn on overlapping regions

1773. By Zsombor Egri

refining region validation

1774. By Zsombor Egri

auto-collapse added if content has PageHeader; remove warning about nesting page

1775. By Zsombor Egri

remove style specific test

1776. By Zsombor Egri

remove fill reset as it destroys the style overriden one, causing the nint to be mislocated

1777. By Zsombor Egri

change default region limits in gallery

1778. By Zsombor Egri

redo validation to validate runtime too

1779. By Zsombor Egri

remove comments, tweaking done meantime seemed to help

1780. By Zsombor Egri

staging sync

Revision history for this message
Zsombor Egri (zsombi) wrote :

> > > 304 - // we need to clip because the header does not have a background
> > > 305 - clip: true
> > > we shouldn't just remove this. It is no longer needed with the new
> PageHeader,
> > > but apps may still > use the old AppHeader (by configuring Page.head,
> instead of
> > > using Page.header).
>
> > If we have this clip set, the bottom edge content header will always be
> clipped. I am open
> > for solutions.
>
> Removing it will break current apps. The solution for UITK 1.4/2.0 will come
> easy, because there we will remove the old AppHeader.

No, we cannot. That's the problem. We announce the deprecation 1.3, which means we have to keep it one more version and then remove it. So as long as 1.4 will have the AppHeader, it will cause trouble, because people will still use it.

>
> For now, we could add a solid background to the AppHeader that is the same
> color as the MainView.headerColor. However, if the app has a background
> texture or gradient (old designs), the header does not match any more, so it
> may mess up existing apps. An alternative is to force people to switch to the
> new header when they use APL.

What if we do it in a separate MR? Like immediately after this...

Revision history for this message
Zsombor Egri (zsombi) wrote :

> The hint text is not centered any more (in gallery), see https://www.dropbox.c
> om/s/uvwzak5eumwcgnp/Screenshot%202015-11-26%2011.04.38.png?dl=0

Fixed, it was the anchor.fill introduced by the style overridden by the cpp resetFill() :/

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1781. By Zsombor Egri

activeRegion changes optimized

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

> > > > 304 - // we need to clip because the header does not have a background
> > > > 305 - clip: true
> > > > we shouldn't just remove this. It is no longer needed with the new
> > PageHeader,
> > > > but apps may still > use the old AppHeader (by configuring Page.head,
> > instead of
> > > > using Page.header).
> >
> > > If we have this clip set, the bottom edge content header will always be
> > clipped. I am open
> > > for solutions.
> >
> > Removing it will break current apps. The solution for UITK 1.4/2.0 will come
> > easy, because there we will remove the old AppHeader.
>
> No, we cannot. That's the problem. We announce the deprecation 1.3, which
> means we have to keep it one more version and then remove it. So as long as
> 1.4 will have the AppHeader, it will cause trouble, because people will still
> use it.
>
> >
> > For now, we could add a solid background to the AppHeader that is the same
> > color as the MainView.headerColor. However, if the app has a background
> > texture or gradient (old designs), the header does not match any more, so it
> > may mess up existing apps. An alternative is to force people to switch to
> the
> > new header when they use APL.
>
> What if we do it in a separate MR? Like immediately after this...

If we do it *after*, then we have the header+APL broken until that next MR lands...

Revision history for this message
Tim Peeters (tpeeters) wrote :

looks good. But we MUST give the AppHeader a background in a separate MR before landing this in trunk.

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

The desire to give the AppHeader a background as stated above, was reported in this bug: https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1531457

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2015-11-20 07:47:08 +0000
3+++ components.api 2015-11-27 10:36:32 +0000
4@@ -175,6 +175,36 @@
5 property var icon
6 property bool iconFrame
7 property bool progression
8+Ubuntu.Components.BottomEdge 1.3: StyledItem
9+ readonly property BottomEdgeRegion activeRegion
10+ property Component contentComponent
11+ readonly property Item contentItem
12+ property url contentUrl
13+ readonly property DragDirection dragDirection
14+ readonly property double dragProgress
15+ readonly property BottomEdgeHint hint
16+ signal dragProgressChanged(double dragProgress)
17+ signal dragDirectionChanged(BottomEdge.DragDirection direction)
18+ signal statusChanged(BottomEdge.Status status)
19+ signal contentChanged(url url)
20+ signal contentComponentChanged(Component component)
21+ signal activeRegionChanged(BottomEdgeRegion activeRegion)
22+ signal commitStarted()
23+ signal commitCompleted()
24+ signal collapseStarted()
25+ signal collapseCompleted()
26+ function commit()
27+ function collapse()
28+ property list<BottomEdgeRegion> regions
29+ readonly property Status status
30+Ubuntu.Components.BottomEdge.DragDirection: Enum
31+ Downwards
32+ Undefined
33+ Upwards
34+Ubuntu.Components.BottomEdge.Status: Enum
35+ Committed
36+ Hidden
37+ Revealed
38 Ubuntu.Components.BottomEdgeHint 1.3: ActionItem
39 property int deactivateTimeout
40 property Flickable flickable
41@@ -186,6 +216,20 @@
42 Hidden
43 Inactive
44 Locked
45+Ubuntu.Components.BottomEdgeRegion 1.3: QtObject
46+ property Component contentComponent
47+ property url contentUrl
48+ property bool enabled
49+ property double from
50+ signal entered()
51+ signal exited()
52+ signal dragEnded()
53+ property double to
54+Ubuntu.Components.Styles.BottomEdgeStyle 1.3: Item
55+ property Item contentItem
56+ property Item panel
57+ property Animation panelAnimation
58+ property double revealThreshold
59 Ubuntu.Components.Button 1.0 0.1: AbstractButton
60 property color color
61 property QFont font
62@@ -1131,6 +1175,10 @@
63 function touchMove(int touchId, Item item, Qt.point point)
64 function touchDrag(int touchId, Item item, Qt.point from, Qt.point delta, int steps)
65 function touchDrag(int touchId, Item item, Qt.point from, Qt.point delta)
66+ function mouseDrag(Item item, Qt.point from, Qt.point delta, Qt.MouseButton button, Qt.KeyboardModifiers stateKey, int steps, int delay)
67+ function mouseDrag(Item item, Qt.point from, Qt.point delta, Qt.MouseButton button, Qt.KeyboardModifiers stateKey, int steps)
68+ function mouseDrag(Item item, Qt.point from, Qt.point delta, Qt.MouseButton button, Qt.KeyboardModifiers stateKey)
69+ function mouseDrag(Item item, Qt.point from, Qt.point delta, Qt.MouseButton button)
70 readonly property bool touchPresent
71 Ubuntu.Components.TextArea 1.0 0.1: StyledItem
72 property bool autoExpand
73
74=== modified file 'examples/ubuntu-ui-toolkit-gallery/BottomEdgePage.qml'
75--- examples/ubuntu-ui-toolkit-gallery/BottomEdgePage.qml 2015-11-05 14:28:58 +0000
76+++ examples/ubuntu-ui-toolkit-gallery/BottomEdgePage.qml 2015-11-27 10:36:32 +0000
77@@ -17,22 +17,185 @@
78 import QtQuick 2.4
79 import Ubuntu.Components 1.3
80
81-Page {
82+Template {
83+ id: page
84+ header: PageHeader {
85+ title: i18n.tr("Bottom Edge")
86+ trailingActionBar.actions: [
87+ bottomEdge.hint.action
88+ ]
89+ }
90+
91 TemplateSection {
92- title: "Bottom Edge Hint"
93+ title: "BottomEdgeHint"
94 className: "BottomEdgeHint"
95
96- anchors {
97- top: parent.top
98- left: parent.left
99- right: parent.right
100- margins: units.gu(2)
101- }
102- }
103-
104- BottomEdgeHint {
105- iconName: "stock_message"
106- text: "Compose a new message"
107- onClicked: state = "Hidden"
108+ TemplateRow {
109+ title: i18n.tr("On clicked")
110+ Row {
111+ spacing: units.gu(1)
112+ CheckBox {
113+ id: contentToLayout
114+ text: i18n.tr("push content into the layout")
115+ enabled: bottomEdge.hint.status >= BottomEdgeHint.Active
116+ }
117+ Label {
118+ text: contentToLayout.text
119+ anchors.verticalCenter: contentToLayout.verticalCenter
120+ }
121+ }
122+ }
123+ }
124+
125+ TemplateSection {
126+ title: "BottomEdge"
127+ className: "BottomEdge"
128+
129+ TemplateRow {
130+ title: i18n.tr("Top")
131+ Slider {
132+ id: bottomEdgeHeight
133+ maximumValue: page.height
134+ value: bottomEdge.height
135+ onValueChanged: bottomEdge.height = value
136+ }
137+ }
138+
139+ TemplateRow {
140+ title: i18n.tr("Hint")
141+ Row {
142+ spacing: units.gu(1)
143+ CheckBox {
144+ id: attachHintToContent
145+ text: i18n.tr("attach hint to content")
146+ }
147+ Label {
148+ text: attachHintToContent.text
149+ anchors.verticalCenter: attachHintToContent.verticalCenter
150+ }
151+ }
152+ }
153+
154+ TemplateRow {
155+ title: i18n.tr("Regions")
156+ Slider {
157+ id: regionCount
158+ width: units.gu(20)
159+ maximumValue: 3.0
160+ live: true
161+ }
162+ }
163+ Repeater {
164+ id: regionConfig
165+ model: regionCount.value.toFixed(0)
166+ TemplateRow {
167+ title: i18n.tr("Region #%1").arg(index)
168+ property int regionIndex: index
169+ Repeater {
170+ model: ["from", "to"]
171+ Row {
172+ spacing: units.gu(2)
173+ Label {
174+ text: i18n.tr(modelData)
175+ }
176+ TextField {
177+ id: regionFrom
178+ text: bottomEdge.regions[regionIndex][modelData]
179+ validator: DoubleValidator {bottom: 0.0; top: 1.0; decimals: 2; locale: "d.d"}
180+ inputMethodHints: Qt.ImhPreferNumbers | Qt.ImhFormattedNumbersOnly
181+ width: units.gu(7)
182+ hasClearButton: false
183+ errorHighlight: true
184+ onAccepted: bottomEdge.regions[regionIndex][modelData] = text
185+ onTextChanged: {
186+ if (regionFrom.acceptableInput) {
187+ bottomEdge.regions[regionIndex][modelData] = parseFloat(text);
188+ }
189+ }
190+ onActiveFocusChanged: if (activeFocus) selectAll()
191+ }
192+ }
193+ }
194+ }
195+ }
196+ }
197+
198+ BottomEdge {
199+ id: bottomEdge
200+ // make sure it doesn't land inside the flickable
201+ parent: page
202+ // hint
203+ hint {
204+ action: Action {
205+ text: "Demo content"
206+ iconName: "stock_message"
207+ onTriggered: bottomEdge.commit()
208+ }
209+ flickable: page.flickable
210+ }
211+ contentComponent: bottomEdgeContent
212+
213+ StyleHints {
214+ attachHintToContent: attachHintToContent.checked
215+ }
216+
217+ onCommitCompleted: {
218+ if (contentToLayout.checked && contentToLayout.enabled) {
219+ page.pageStack.addPageToCurrentColumn(page, contentComponent);
220+ collapse();
221+ }
222+ }
223+
224+ regions: [
225+ BottomEdgeRegion {
226+ objectName: "CustomRegion1"
227+ enabled: regionConfig.model >= 1
228+ to: 0.3
229+ property color baseColor: UbuntuColors.lightGrey
230+ },
231+ BottomEdgeRegion {
232+ objectName: "CustomRegion2"
233+ enabled: regionConfig.model >= 2
234+ from: 0.3
235+ to: 0.6
236+ },
237+ BottomEdgeRegion {
238+ objectName: "CustomRegion3"
239+ enabled: regionConfig.model >= 3
240+ from: 0.6
241+ },
242+ // default region, mimics the default setup
243+ BottomEdgeRegion {
244+ objectName: "DefaultRegion"
245+ enabled: regionConfig.model <= 0
246+ from: 0.3
247+ }
248+ ]
249+
250+ Component {
251+ id: bottomEdgeContent
252+ Page {
253+ height: bottomEdge.height
254+ header: PageHeader {
255+ title: {
256+ var state = "UNDEFINED";
257+ switch (bottomEdge.status) {
258+ case BottomEdge.Hidden: state = "Hidden"; break;
259+ case BottomEdge.Revealed: state = "Revealed"; break;
260+ case BottomEdge.Committed: state = "Committed"; break;
261+ }
262+ return bottomEdge.activeRegion
263+ ? i18n.tr("Within region '%1', status: %2").arg(bottomEdge.activeRegion.objectName).arg(state)
264+ : i18n.tr("Not in any active region, status: %1").arg(state);
265+ }
266+ }
267+ Rectangle {
268+ anchors.fill: parent
269+ anchors.margins: units.gu(1)
270+ color: bottomEdge.activeRegion && bottomEdge.activeRegion.hasOwnProperty("baseColor") ?
271+ bottomEdge.activeRegion.baseColor : Qt.rgba(0.5, 1, bottomEdge.dragProgress, 1)
272+ }
273+ }
274+ }
275 }
276 }
277
278=== modified file 'examples/ubuntu-ui-toolkit-gallery/Template.qml'
279--- examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-11-09 07:35:14 +0000
280+++ examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-11-27 10:36:32 +0000
281@@ -23,6 +23,7 @@
282 default property alias content: column.children
283 property alias spacing: column.spacing
284 property alias scrollable: flickable.interactive
285+ readonly property alias flickable: flickable
286
287 header: PageHeader {
288 title: template.title
289
290=== modified file 'examples/ubuntu-ui-toolkit-gallery/TemplateRow.qml'
291--- examples/ubuntu-ui-toolkit-gallery/TemplateRow.qml 2015-04-25 08:18:45 +0000
292+++ examples/ubuntu-ui-toolkit-gallery/TemplateRow.qml 2015-11-27 10:36:32 +0000
293@@ -26,7 +26,7 @@
294 default property alias content: contentRow.children
295
296 height: Math.max(contentRow.height, label.height)
297- width: parent.width
298+ width: parent ? parent.width : 0
299
300 Label {
301 id: label
302
303=== modified file 'examples/ubuntu-ui-toolkit-gallery/gallery-logging.config'
304--- examples/ubuntu-ui-toolkit-gallery/gallery-logging.config 2015-11-17 11:48:09 +0000
305+++ examples/ubuntu-ui-toolkit-gallery/gallery-logging.config 2015-11-27 10:36:32 +0000
306@@ -2,3 +2,4 @@
307 libubuntugestures.TouchRegistry.debug=false
308 ubuntu.components.SwipeArea.debug=false
309 ubuntu.components.SwipeArea.ActiveTouchInfo.debug=false
310+ubuntu.components.BottomEdge.debug=false
311
312=== modified file 'src/Ubuntu/Components/1.3/AdaptivePageLayout.qml'
313--- src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 2015-10-22 20:09:56 +0000
314+++ src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 2015-11-27 10:36:32 +0000
315@@ -743,8 +743,6 @@
316 right: parent.right
317 rightMargin: dividerThickness
318 }
319- // we need to clip because the header does not have a background
320- clip: true
321 Item {
322 id: hiddenItem
323 anchors.fill: parent
324
325=== modified file 'src/Ubuntu/Components/1.3/PageTreeNode.qml'
326--- src/Ubuntu/Components/1.3/PageTreeNode.qml 2015-10-20 11:05:53 +0000
327+++ src/Ubuntu/Components/1.3/PageTreeNode.qml 2015-11-27 10:36:32 +0000
328@@ -125,10 +125,6 @@
329 if (i.isLeaf) {
330 // children of a leaf are not part of the tree
331 node = null;
332- print("WARNING! " +
333- "Do not put Page/Tabs/PageStack inside another "+
334- "Page because that causes confusion which is the "+
335- "active page that sets the title and actions.");
336 } else {
337 // current node is part of the tree with i as its parent.
338 node = i;
339
340=== modified file 'src/Ubuntu/Components/ComponentModule.pro'
341--- src/Ubuntu/Components/ComponentModule.pro 2015-11-18 15:07:15 +0000
342+++ src/Ubuntu/Components/ComponentModule.pro 2015-11-27 10:36:32 +0000
343@@ -132,7 +132,7 @@
344 1.3/PageColumn.qml \
345 1.3/PageColumnsLayout.qml \
346 1.3/ProgressionSlot.qml \
347- 1.3/PageHeader.qml \
348+ 1.3/PageHeader.qml \
349
350 OTHER_FILES+= qmldir \
351 1.3/CrossFadeImage.qdoc \
352
353=== added file 'src/Ubuntu/Components/Themes/Ambiance/1.3/BottomEdgeStyle.qml'
354--- src/Ubuntu/Components/Themes/Ambiance/1.3/BottomEdgeStyle.qml 1970-01-01 00:00:00 +0000
355+++ src/Ubuntu/Components/Themes/Ambiance/1.3/BottomEdgeStyle.qml 2015-11-27 10:36:32 +0000
356@@ -0,0 +1,125 @@
357+/*
358+ * Copyright (C) 2015 Canonical, Ltd.
359+ *
360+ * This program is free software; you can redistribute it and/or modify
361+ * it under the terms of the GNU General Public License as published by
362+ * the Free Software Foundation; version 3.
363+ *
364+ * This program is distributed in the hope that it will be useful,
365+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
366+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
367+ * GNU General Public License for more details.
368+ *
369+ * You should have received a copy of the GNU General Public License
370+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
371+ */
372+import QtQuick 2.4
373+import Ubuntu.Components 1.3
374+import Ubuntu.Components.Styles 1.3
375+
376+BottomEdgeStyle {
377+ id: bottomEdgeStyle
378+ //setup properties
379+ property BottomEdge bottomEdge: styledItem
380+ panel: panelItem
381+ contentItem: loader.item
382+ panelAnimation: panelBehavior
383+ revealThreshold: bottomEdge.hint.height + units.gu(2)
384+
385+ // own styling properties
386+ property color backgroundColor: "transparent"
387+ property color panelColor: theme.palette.normal.background
388+ property color shadowColor: theme.palette.selected.background
389+ property bool attachHintToContent: false
390+
391+ anchors.fill: parent
392+
393+ Rectangle {
394+ id: background
395+ anchors.fill: parent
396+ color: backgroundColor
397+ z: -1
398+ }
399+
400+ states: State {
401+ name: "reparentedHint"
402+ when: attachHintToContent
403+ AnchorChanges {
404+ target: bottomEdge.hint
405+ anchors.bottom: panelItem.top
406+ }
407+ }
408+
409+ Rectangle {
410+ id: panelItem
411+ objectName: "bottomedge_panel"
412+ anchors {
413+ left: parent.left
414+ right: parent.right
415+ top: parent.bottom
416+ topMargin: bottomEdge.status >= BottomEdge.Revealed
417+ ? -(bottomEdge.height * bottomEdge.dragProgress)
418+ : 0
419+ }
420+ height: loader.item ? loader.item.height : 0
421+ color: panelColor
422+ opacity: bottomEdge.status >= BottomEdge.Revealed ? 1.0 : 0.0
423+
424+ Behavior on anchors.topMargin { UbuntuNumberAnimation { id: panelBehavior } }
425+
426+ state: bottomEdge.status > BottomEdge.Hidden ? "lock-hint" : ""
427+ states: [
428+ State {
429+ name: "lock-hint"
430+ PropertyChanges {
431+ target: bottomEdge.hint
432+ status: BottomEdgeHint.Locked
433+ }
434+ }
435+ ]
436+
437+ // shadows
438+ Rectangle {
439+ id: topShadow
440+ anchors {
441+ bottom: parent.top
442+ left: parent.left
443+ right: parent.right
444+ }
445+ height: units.gu(1)
446+ gradient: Gradient {
447+ GradientStop { position: 0.0; color: Qt.rgba(shadowColor.r, shadowColor.g, shadowColor.b, 0.0) }
448+ GradientStop { position: 1.0; color: Qt.rgba(shadowColor.r, shadowColor.g, shadowColor.b, 0.3) }
449+ }
450+ }
451+ Rectangle {
452+ id: bottomShadow
453+ anchors {
454+ top: parent.bottom
455+ left: parent.left
456+ right: parent.right
457+ }
458+ height: units.gu(1)
459+ rotation: 180
460+ gradient: Gradient {
461+ GradientStop { position: 0.0; color: Qt.rgba(shadowColor.r, shadowColor.g, shadowColor.b, 0.0) }
462+ GradientStop { position: 1.0; color: Qt.rgba(shadowColor.r, shadowColor.g, shadowColor.b, 0.3) }
463+ }
464+ }
465+
466+ // content
467+ Loader {
468+ id: loader
469+ anchors.horizontalCenter: parent.horizontalCenter
470+ asynchronous: true
471+ source: bottomEdge.status > BottomEdge.Hidden ? bottomEdge.contentUrl : ""
472+ sourceComponent: bottomEdge.status > BottomEdge.Hidden ? bottomEdge.contentComponent : null
473+ onItemChanged: {
474+ if (item) {
475+ item.parent = panelItem;
476+ item.anchors.horizontalCenter = panelItem.horizontalCenter;
477+ }
478+ }
479+ }
480+ }
481+}
482
483=== modified file 'src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro'
484--- src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro 2015-10-23 05:42:14 +0000
485+++ src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro 2015-11-27 10:36:32 +0000
486@@ -114,7 +114,8 @@
487 1.3/IconButtonStyle.qml \
488 1.3/PageHeaderStyle.qml \
489 1.3/BottomEdgeHintStyle.qml \
490- $$ARTWORK_FILES
491+ 1.3/BottomEdgeStyle.qml \
492+ $$ARTWORK_FILES
493
494 load(ubuntu_qml_module)
495
496
497=== modified file 'src/Ubuntu/Components/Themes/Ambiance/qmldir'
498--- src/Ubuntu/Components/Themes/Ambiance/qmldir 2015-11-18 15:07:15 +0000
499+++ src/Ubuntu/Components/Themes/Ambiance/qmldir 2015-11-27 10:36:32 +0000
500@@ -84,3 +84,5 @@
501 internal SliderUtils 1.3/sliderUtils.js
502 internal ColorUtils 1.3/colorUtils.js
503 PageHeaderStyle 1.3 ./1.3/PageHeaderStyle.qml
504+BottomEdgeHintStyle 1.3 ./1.3/BottomEdgeHintStyle.qml
505+BottomEdgeStyle 1.3 ./1.3/BottomEdgeStyle.qml
506
507=== modified file 'src/Ubuntu/Components/plugin/plugin.cpp'
508--- src/Ubuntu/Components/plugin/plugin.cpp 2015-11-19 09:05:19 +0000
509+++ src/Ubuntu/Components/plugin/plugin.cpp 2015-11-27 10:36:32 +0000
510@@ -73,6 +73,9 @@
511 #include "ucbottomedgehint.h"
512 #include "gestures/ucswipearea.h"
513 #include "ucmathutils.h"
514+#include "ucbottomedge.h"
515+#include "ucbottomedgeregion.h"
516+#include "ucbottomedgestyle.h"
517
518 #include <sys/types.h>
519 #include <unistd.h>
520@@ -254,6 +257,8 @@
521 qmlRegisterType<UCLabel>(uri, 1, 3, "Label");
522 qmlRegisterType<UCBottomEdgeHint>(uri, 1, 3, "BottomEdgeHint");
523 qmlRegisterType<UCSwipeArea>(uri, 1, 3, "SwipeArea");
524+ qmlRegisterType<UCBottomEdge>(uri, 1, 3, "BottomEdge");
525+ qmlRegisterType<UCBottomEdgeRegion>(uri, 1, 3, "BottomEdgeRegion");
526 }
527
528 void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
529@@ -265,6 +270,7 @@
530 const char *styleUri = "Ubuntu.Components.Styles";
531 qmlRegisterType<UCListItemStyle>(styleUri, 1, 2, "ListItemStyle");
532 qmlRegisterType<UCListItemStyle, 1>(styleUri, 1, 3, "ListItemStyle");
533+ qmlRegisterType<UCBottomEdgeStyle>(styleUri, 1, 3, "BottomEdgeStyle");
534
535 QQmlExtensionPlugin::initializeEngine(engine, uri);
536 QQmlContext* context = engine->rootContext();
537
538=== modified file 'src/Ubuntu/Components/plugin/plugin.pri'
539--- src/Ubuntu/Components/plugin/plugin.pri 2015-11-20 07:47:08 +0000
540+++ src/Ubuntu/Components/plugin/plugin.pri 2015-11-27 10:36:32 +0000
541@@ -98,7 +98,11 @@
542 $$PWD/gestures/ucswipearea_p.h \
543 $$PWD/gestures/damper.h \
544 $$PWD/gestures/ubuntugesturesqmlglobal.h \
545- $$PWD/ucmathutils.h
546+ $$PWD/ucmathutils.h \
547+ $$PWD/ucbottomedge.h \
548+ $$PWD/ucbottomedge_p.h \
549+ $$PWD/ucbottomedgestyle.h \
550+ $$PWD/ucbottomedgeregion.h
551
552 SOURCES += $$PWD/plugin.cpp \
553 $$PWD/uctheme.cpp \
554@@ -164,7 +168,10 @@
555 $$PWD/ucimportversionchecker_p.cpp \
556 $$PWD/ucbottomedgehint.cpp \
557 $$PWD/gestures/ucswipearea.cpp \
558- $$PWD/ucmathutils.cpp
559+ $$PWD/ucmathutils.cpp \
560+ $$PWD/ucbottomedge.cpp \
561+ $$PWD/ucbottomedgestyle.cpp \
562+ $$PWD/ucbottomedgeregion.cpp
563
564 # adapters
565 SOURCES += $$PWD/adapters/alarmsadapter_organizer.cpp
566
567=== modified file 'src/Ubuntu/Components/plugin/ucaction.h'
568--- src/Ubuntu/Components/plugin/ucaction.h 2015-09-01 08:44:11 +0000
569+++ src/Ubuntu/Components/plugin/ucaction.h 2015-11-27 10:36:32 +0000
570@@ -60,6 +60,12 @@
571 return m_published;
572 }
573
574+ void setName(const QString &name);
575+ void setIconName(const QString &name);
576+ void setIconSource(const QUrl &url);
577+ void setItemHint(QQmlComponent *);
578+ void setShortcut(const QVariant&);
579+
580 Q_SIGNALS:
581 void nameChanged();
582 void textChanged();
583@@ -99,11 +105,6 @@
584
585 bool isValidType(QVariant::Type valueType);
586 void generateName();
587- void setName(const QString &name);
588- void setIconName(const QString &name);
589- void setIconSource(const QUrl &url);
590- void setItemHint(QQmlComponent *);
591- void setShortcut(const QVariant&);
592 bool event(QEvent *event);
593 };
594
595
596=== added file 'src/Ubuntu/Components/plugin/ucbottomedge.cpp'
597--- src/Ubuntu/Components/plugin/ucbottomedge.cpp 1970-01-01 00:00:00 +0000
598+++ src/Ubuntu/Components/plugin/ucbottomedge.cpp 2015-11-27 10:36:32 +0000
599@@ -0,0 +1,1059 @@
600+/*
601+ * Copyright 2015 Canonical Ltd.
602+ *
603+ * This program is free software; you can redistribute it and/or modify
604+ * it under the terms of the GNU Lesser General Public License as published by
605+ * the Free Software Foundation; version 3.
606+ *
607+ * This program is distributed in the hope that it will be useful,
608+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
609+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
610+ * GNU Lesser General Public License for more details.
611+ *
612+ * You should have received a copy of the GNU Lesser General Public License
613+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
614+ *
615+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
616+ */
617+
618+#include "ucbottomedge_p.h"
619+#include "ucbottomedgestyle.h"
620+#include "ucbottomedgeregion.h"
621+#include "ucbottomedgehint.h"
622+#include "ucstyleditembase_p.h"
623+#include <QtQml/QQmlEngine>
624+#include <QtGui/QScreen>
625+#include <QtQml/QQmlProperty>
626+#include <QtGui/QGuiApplication>
627+#include <QtGui/QStyleHints>
628+#include <QtQuick/private/qquickitem_p.h>
629+#include <QtQuick/private/qquickflickable_p.h>
630+
631+#include "plugin.h"
632+#include "ucnamespace.h"
633+#include "ucheader.h"
634+#include "ucaction.h"
635+#include "quickutils.h"
636+#include "gestures/ucswipearea.h"
637+#include <QtQuick/private/qquickanimation_p.h>
638+
639+Q_LOGGING_CATEGORY(ucBottomEdge, "ubuntu.components.BottomEdge", QtMsgType::QtWarningMsg)
640+
641+#define LOG qCDebug(ucBottomEdge) << "[BottomEdge]"
642+
643+UCBottomEdgePrivate::UCBottomEdgePrivate()
644+ : UCStyledItemBasePrivate()
645+ , activeRegion(Q_NULLPTR)
646+ , hint(new UCBottomEdgeHint)
647+ , contentComponent(Q_NULLPTR)
648+ , bottomPanel(Q_NULLPTR)
649+ , previousDistance(0.0)
650+ , dragProgress(0.)
651+ , status(UCBottomEdge::Hidden)
652+ , operationStatus(Idle)
653+ , dragDirection(UCBottomEdge::Undefined)
654+ , defaultRegionsReset(false)
655+ , mousePressed(false)
656+{
657+}
658+
659+void UCBottomEdgePrivate::init()
660+{
661+ Q_Q(UCBottomEdge);
662+ // initialize hint
663+ QQml_setParent_noEvent(hint, q);
664+ hint->setParentItem(q);
665+
666+ // create default regions
667+ createDefaultRegions();
668+
669+ // set the style name
670+ styleDocument = QStringLiteral("BottomEdgeStyle");
671+}
672+
673+// overload default data property so we can filter out the regions declared
674+// in the body of the component as resources
675+QQmlListProperty<QObject> UCBottomEdgePrivate::data()
676+{
677+ return QQmlListProperty<QObject>(q_func(), 0, UCBottomEdgePrivate::overload_data_append,
678+ QQuickItemPrivate::data_count,
679+ QQuickItemPrivate::data_at,
680+ UCBottomEdgePrivate::overload_data_clear);
681+}
682+
683+void UCBottomEdgePrivate::overload_data_append(QQmlListProperty<QObject> *data, QObject *object)
684+{
685+ QQuickItemPrivate::data_append(data, object);
686+ // if the object is a region, add to the regions as well
687+ UCBottomEdgeRegion *region = qobject_cast<UCBottomEdgeRegion*>(object);
688+ if (region) {
689+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(data->object));
690+ bottomEdge->appendRegion(region);
691+ }
692+}
693+
694+void UCBottomEdgePrivate::overload_data_clear(QQmlListProperty<QObject> *data)
695+{
696+ // clear regions as well
697+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(data->object));
698+ bottomEdge->clearRegions(false);
699+ QQuickItemPrivate::data_clear(data);
700+}
701+
702+// appends a BottomEdgeRegion to the list; clears the default regions before appending the
703+// custom ones
704+void UCBottomEdgePrivate::appendRegion(UCBottomEdgeRegion *region)
705+{
706+ Q_Q(UCBottomEdge);
707+ Q_ASSERT(region);
708+ // the region must be owned by other non-BottomEdge component,
709+ // otherwise we cannot "reuse" the region
710+ if (region->parent() != q && qobject_cast<UCBottomEdge*>(region->parent())) {
711+ qmlInfo(q) << "Cannot reuse region owned by other BottomEdge components";
712+ return;
713+ }
714+
715+ // make sure we own the region!
716+ QQml_setParent_noEvent(region, q);
717+ // take ownership!
718+ QQmlEngine::setObjectOwnership(region, QQmlEngine::CppOwnership);
719+ region->attachToBottomEdge(q);
720+
721+ if (!defaultRegionsReset) {
722+ defaultRegionsReset = true;
723+ qDeleteAll(regions);
724+ regions.clear();
725+ }
726+
727+ // validate the region before we append
728+ validateRegion(region);
729+
730+ // append region definition
731+ regions.append(region);
732+}
733+
734+// clears the custom regions list and restores the default ones
735+void UCBottomEdgePrivate::clearRegions(bool destroy)
736+{
737+ if (!defaultRegionsReset) {
738+ return;
739+ }
740+ if (destroy) {
741+ qDeleteAll(regions);
742+ }
743+ regions.clear();
744+ defaultRegionsReset = false;
745+ createDefaultRegions();
746+}
747+
748+// validates an added region
749+void UCBottomEdgePrivate::validateRegion(UCBottomEdgeRegion *region, int regionsSize)
750+{
751+ // we should not validate unti the bottom edge is not completed,
752+ // property changes may invalidate the result of the validation
753+ // also leave if the region is disabled
754+ if (!componentComplete || !region->m_enabled) {
755+ return;
756+ }
757+ Q_Q(UCBottomEdge);
758+ // use QRectF to help on finding the intersection
759+ QRectF boundingRect = q->boundingRect();
760+ if (boundingRect.isNull()) {
761+ boundingRect = QRectF(0, 0, 1, 1);
762+ }
763+ if (regionsSize < 0 || regionsSize > regions.size()) {
764+ regionsSize = regions.size();
765+ }
766+ const QRectF regionRect(region->rect(boundingRect));
767+ for (int i = 0; i < regionsSize; ++i) {
768+ UCBottomEdgeRegion *stackedRegion = regions[i];
769+ if (region == stackedRegion || !stackedRegion->m_enabled) {
770+ continue;
771+ }
772+ QRectF rect(stackedRegion->rect(boundingRect));
773+ if (rect.contains(regionRect)) {
774+ qmlInfo(region) << QString("Region at index %1 contains this region. This region will never activate.").arg(i);
775+ } else {
776+ QRectF intersect = regionRect.intersected(stackedRegion->rect(boundingRect));
777+ if (!intersect.isNull()) {
778+ qmlInfo(region) << QString("Region intersects the one from index %1 having from: %2 and to: %3")
779+ .arg(i).arg(stackedRegion->m_from).arg(stackedRegion->m_to);
780+ }
781+ }
782+ }
783+}
784+
785+// creates the default region(s)
786+void UCBottomEdgePrivate::createDefaultRegions()
787+{
788+ Q_Q(UCBottomEdge);
789+ // add the default stages
790+ UCBottomEdgeRegion *commitRegion = new UCBottomEdgeRegion(q);
791+ // for testing purposes
792+ commitRegion->setObjectName("default_BottomEdgeRegion");
793+ // enters in this stage when drag ratio reaches 30% of the area
794+ commitRegion->m_from = 0.33;
795+ commitRegion->m_to = 1.0;
796+
797+ regions.append(commitRegion);
798+}
799+
800+// update status, drag direction and activeRegion during drag
801+void UCBottomEdgePrivate::updateProgressionStates(qreal distance)
802+{
803+ Q_Q(UCBottomEdge);
804+
805+ // refresh drag progress
806+ setDragProgress(distance / q->height());
807+
808+ detectDirection(distance);
809+ if (isLocked()) {
810+ // there is an operation ongoing, do not update drag and activeRegion
811+ return;
812+ }
813+ if (distance >= bottomPanel->m_revealThreshold) {
814+ // the content can be revealed
815+ setStatus(UCBottomEdge::Revealed);
816+ }
817+
818+ // go through the regions and spot the active region
819+ UCBottomEdgeRegion *newActive = Q_NULLPTR;
820+ Q_FOREACH(UCBottomEdgeRegion *region, regions) {
821+ if (region->contains(dragProgress)) {
822+ newActive = region;
823+ break;
824+ }
825+ }
826+ if (newActive != activeRegion) {
827+ setActiveRegion(newActive);
828+ }
829+}
830+
831+// set the active region
832+bool UCBottomEdgePrivate::setActiveRegion(UCBottomEdgeRegion *region)
833+{
834+ if (activeRegion == region) {
835+ return false;
836+ }
837+ if (activeRegion) {
838+ activeRegion->exit();
839+ }
840+ activeRegion = region;
841+ if (activeRegion) {
842+ activeRegion->enter();
843+ }
844+ Q_EMIT q_func()->activeRegionChanged(activeRegion);
845+ return true;
846+}
847+
848+// updates the dragDirection property
849+void UCBottomEdgePrivate::detectDirection(qreal currentDistance)
850+{
851+ if (!previousDistance) {
852+ previousDistance = currentDistance;
853+ }
854+
855+ UCBottomEdge::DragDirection newDirection = dragDirection;
856+ qreal delta = previousDistance - currentDistance;
857+ bool deltaPassed = abs(delta) >= qApp->styleHints()->startDragDistance();
858+ if (!deltaPassed) {
859+ return;
860+ }
861+
862+ previousDistance = currentDistance;
863+ newDirection = (delta < 0) ? UCBottomEdge::Upwards : UCBottomEdge::Downwards;
864+ setDragDirection(newDirection);
865+}
866+
867+// internal dragDirection property setter
868+void UCBottomEdgePrivate::setDragDirection(UCBottomEdge::DragDirection direction)
869+{
870+ if (dragDirection != direction) {
871+ dragDirection = direction;
872+ switch (dragDirection) {
873+ case UCBottomEdge::Undefined: LOG << "direction: Undefined"; break;
874+ case UCBottomEdge::Upwards: LOG << "direction: Upwards"; break;
875+ case UCBottomEdge::Downwards: LOG << "direction: Downwards"; break;
876+ }
877+ Q_EMIT q_func()->dragDirectionChanged(dragDirection);
878+ }
879+}
880+
881+// proceed with drag completion action
882+void UCBottomEdgePrivate::onDragEnded()
883+{
884+ if (!activeRegion || (dragDirection == UCBottomEdge::Downwards)) {
885+ q_func()->collapse();
886+ } else {
887+ // emit region's dragEnded first
888+ Q_EMIT activeRegion->dragEnded();
889+ commit(activeRegion->m_to);
890+ }
891+}
892+
893+void UCBottomEdgePrivate::commit(qreal to)
894+{
895+ if (operationStatus == CommitToTop
896+ || operationStatus == CommitToRegion
897+ || status == UCBottomEdge::Committed) {
898+ LOG << "redundant commit call";
899+ return;
900+ }
901+ Q_Q(UCBottomEdge);
902+ setOperationStatus(qFuzzyCompare(to, 1.0) ? CommitToTop : CommitToRegion);
903+ if (operationStatus == CommitToTop) {
904+ LOG << "emit commitStarted()";
905+ Q_EMIT q->commitStarted();
906+ }
907+ // make sure the status is set to revealed first
908+ bool animated = bottomPanel && bottomPanel->m_panelAnimation;
909+ if (animated) {
910+ QObject::connect(bottomPanel->m_panelAnimation, &QQuickAbstractAnimation::runningChanged,
911+ q, &UCBottomEdge::unlockOperation, Qt::UniqueConnection);
912+ }
913+ // make sure the setStatus is set to Revealed first
914+ if (status == UCBottomEdge::Hidden) {
915+ setStatus(UCBottomEdge::Revealed);
916+ }
917+ setDragProgress(to);
918+ if (!animated) {
919+ q->unlockOperation(false);
920+ }
921+}
922+
923+// common handler to complete the operations
924+void UCBottomEdge::unlockOperation(bool running)
925+{
926+ if (running) {
927+ return;
928+ }
929+ Q_D(UCBottomEdge);
930+ if (d->bottomPanel && d->bottomPanel->m_panelAnimation) {
931+ disconnect(d->bottomPanel->m_panelAnimation, &QQuickAbstractAnimation::runningChanged,
932+ 0, 0);
933+ }
934+
935+ UCBottomEdgePrivate::OperationStatus oldStatus = d->operationStatus;
936+ switch (d->operationStatus) {
937+ case UCBottomEdgePrivate::CommitToTop:
938+ case UCBottomEdgePrivate::CommitToRegion:
939+ d->setStatus(UCBottomEdge::Committed);
940+ d->patchContentItemHeader();
941+ if (d->operationStatus == UCBottomEdgePrivate::CommitToTop) {
942+ LOG << "emit commitCompleted()";
943+ Q_EMIT commitCompleted();
944+ }
945+ break;
946+ case UCBottomEdgePrivate::Collapsing:
947+ d->setStatus(UCBottomEdge::Hidden);
948+ Q_EMIT collapseCompleted();
949+ break;
950+ default: break;
951+ }
952+
953+ // the operation status may got changed due to a new operation being
954+ // initiated from the previosu one (e.g.collapse from commitCompleted, etc
955+ if (oldStatus == d->operationStatus) {
956+ d->setDragDirection(Undefined);
957+ d->setOperationStatus(UCBottomEdgePrivate::Idle);
958+ }
959+}
960+
961+// positions the bottom edge panel holding the content to the given height percentage
962+void UCBottomEdgePrivate::setDragProgress(qreal position)
963+{
964+ if (dragProgress == position || (position < 0.0) || position > 1.0) {
965+ return;
966+ }
967+ dragProgress = position;
968+ Q_EMIT q_func()->dragProgressChanged(dragProgress);
969+}
970+
971+// separated in a derived class to ease testing
972+UCCollapseAction::UCCollapseAction(QObject *parent)
973+ : UCAction(parent)
974+{
975+ QQmlEngine::setObjectOwnership(this, QQmlEngine::objectOwnership(parent));
976+ setIconName("down");
977+}
978+
979+// inject collapse action into the content if the content has a PageHeader
980+void UCBottomEdgePrivate::patchContentItemHeader()
981+{
982+ // ugly, as it can be, as we don't have the PageHeader in cpp to detect the type
983+ UCHeader *header = bottomPanel->m_contentItem ? bottomPanel->m_contentItem->findChild<UCHeader*>() : Q_NULLPTR;
984+ if (!header || !QuickUtils::inherits(header, "PageHeader") || status != UCBottomEdge::Committed) {
985+ return;
986+ }
987+
988+ // get the navigationActions and inject an action there
989+ QVariant list(header->property("navigationActions"));
990+ QQmlListProperty<UCAction> actions = list.value< QQmlListProperty<UCAction> >();
991+ QList<UCAction*> *navigationActions = reinterpret_cast<QList<UCAction*>*>(actions.data);
992+
993+ // clear the actions first
994+ navigationActions->clear();
995+
996+ // inject the action
997+ UCAction *collapse = new UCCollapseAction(header);
998+ QObject::connect(collapse, &UCAction::triggered, q_func(), &UCBottomEdge::collapse, Qt::DirectConnection);
999+ navigationActions->append(collapse);
1000+
1001+ // invoke PageHeader.navigationActionsChanged signal
1002+ int signal = header->metaObject()->indexOfSignal("navigationActionsChanged()");
1003+ if (signal >= 0) {
1004+ header->metaObject()->invokeMethod(header, "navigationActionsChanged");
1005+ }
1006+}
1007+
1008+bool UCBottomEdgePrivate::loadStyleItem(bool animated)
1009+{
1010+ // fix styleVersion
1011+ Q_Q(UCBottomEdge);
1012+ if (!styleVersion) {
1013+ styleVersion = BUILD_VERSION(1, 3);
1014+ }
1015+ bool result = UCStyledItemBasePrivate::loadStyleItem(animated);
1016+ bottomPanel = qobject_cast<UCBottomEdgeStyle*>(styleItem);
1017+ if (bottomPanel) {
1018+ // reparent style item to the BottomEdge's parent
1019+ bottomPanel->setParentItem(parentItem);
1020+ // bring style item in front
1021+ bottomPanel->setZ(std::numeric_limits<qreal>::max());
1022+ // anchor to the bottom of the BottomEdge
1023+ QQuickAnchors *styleAnchors = QQuickItemPrivate::get(bottomPanel)->anchors();
1024+ styleAnchors->setBottom(anchors()->bottom());
1025+
1026+ // move hint under the panel
1027+ hint->setParentItem(bottomPanel);
1028+ // and stack it before the panel, so it is covered by the panel when revealed
1029+ hint->stackBefore(bottomPanel->m_panel);
1030+
1031+ // connect style stuff
1032+ QObject::connect(bottomPanel, &UCBottomEdgeStyle::contentItemChanged,
1033+ q, &UCBottomEdge::contentItemChanged, Qt::DirectConnection);
1034+ // follow contentItem change to patch the header
1035+ QObject::connect(bottomPanel, &UCBottomEdgeStyle::contentItemChanged, [=]() {
1036+ patchContentItemHeader();
1037+ });
1038+ }
1039+ return result;
1040+}
1041+
1042+// make sure the bottom edge panel is always the last one
1043+void UCBottomEdgePrivate::itemChildAdded(QQuickItem *item, QQuickItem *)
1044+{
1045+ // make sure the BottomEdge's panel is the last one
1046+ QQuickItem *last = item->childItems().last();
1047+ if (bottomPanel && last != bottomPanel) {
1048+ bottomPanel->stackAfter(last);
1049+ }
1050+}
1051+
1052+// remove this to be change listener from the previous parent
1053+void UCBottomEdgePrivate::itemChildRemoved(QQuickItem *item, QQuickItem *child)
1054+{
1055+ Q_Q(UCBottomEdge);
1056+ if (child == q) {
1057+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Children);
1058+ }
1059+}
1060+
1061+void UCBottomEdgePrivate::setOperationStatus(OperationStatus s)
1062+{
1063+ operationStatus = s;
1064+ switch (s) {
1065+ case Idle: LOG << "OP" << "Idle"; break;
1066+ case CommitToTop: LOG << "OP" << "CommitToTop"; break;
1067+ case CommitToRegion: LOG << "OP" << "CommitToRegion"; break;
1068+ case Collapsing: LOG << "OP" << "Collapsing"; break;
1069+ }
1070+}
1071+
1072+/*!
1073+ * \qmltype BottomEdge
1074+ * \instantiates UCBottomEdge
1075+ * \inqmlmodule Ubuntu.Components 1.3
1076+ * \inherits StyledItem
1077+ * \ingroup ubuntu
1078+ * \since Ubuntu.Components 1.3
1079+ * \brief A component to handle bottom edge gesture and content.
1080+ *
1081+ * The component provides bottom edge content handling. The bottom egde feature
1082+ * is typically composed of a hint and some content. The contentUrl is committed
1083+ * (i.e. fully shown) when the drag is completed after it has been dragged for
1084+ * a certain amount, that is 30% of the height of the BottomEdge. The contentUrl
1085+ * can be anything, defined by \l contentUrl or \l contentComponent.
1086+ *
1087+ * As the name suggests, the component automatically anchors to the bottom of its
1088+ * parent and takes the width of the parent. The drag is detected within the parent
1089+ * area, and the height drives till what extent the bottom edge content should be
1090+ * exposed on \l commit call. The content is centered into a panel which is dragged from
1091+ * the bottom of the BottomEdge. The content must specify its width and height.
1092+ * \qml
1093+ * import QtQuick 2.4
1094+ * import Ubuntu.Components 1.3
1095+ *
1096+ * MainView {
1097+ * width: units.gu(40)
1098+ * height: units.gu(70)
1099+ *
1100+ * Page {
1101+ * id: page
1102+ * title: "BottomEdge"
1103+ *
1104+ * BottomEdge {
1105+ * height: parent.height - units.gu(20)
1106+ * hint.text: "My bottom edge"
1107+ * contentComponent: Rectangle {
1108+ * width: page.width
1109+ * height: page.height
1110+ * color: UbuntuColors.green
1111+ * }
1112+ * }
1113+ * }
1114+ * }
1115+ * \endqml
1116+ *
1117+ * \note The content is specified either through \l contentUrl or \l contentComponent,
1118+ * where \l contentComponent has precedence over \l contentUrl.
1119+ *
1120+ * There can be situations when the content depends on the progress of the drag.
1121+ * There are two possibilities to follow this, depending on the use case. The
1122+ * \l dragProgress provides live updates about the fraction of the drag.
1123+ * \qml
1124+ * BottomEdge {
1125+ * id: bottomEdge
1126+ * height: parent.height
1127+ * hint.text: "progression"
1128+ * contentComponent: Rectangle {
1129+ * width: bottomEdge.width
1130+ * height: bottomEdge.height
1131+ * color: Qt.rgba(0.5, 1, bottomEdge.dragProgress, 1);
1132+ * }
1133+ * }
1134+ * \endqml
1135+ *
1136+ * The other use case is when the content needs to be completely different in certain
1137+ * regions of the area. These regions can be defined through BottomEdgeRegion elements
1138+ * listed in the \l regions property.
1139+ * \qml
1140+ * import QtQuick 2.4
1141+ * import Ubuntu.Components 1.3
1142+ *
1143+ * MainView {
1144+ * width: units.gu(40)
1145+ * height: units.gu(70)
1146+ *
1147+ * Page {
1148+ * title: "BottomEdge"
1149+ *
1150+ * BottomEdge {
1151+ * id: bottomEdge
1152+ * height: parent.height - units.gu(20)
1153+ * hint.text: "My bottom edge"
1154+ * contentComponent: Rectangle {
1155+ * width: bottomEdge.width
1156+ * height: bottomEdge.height
1157+ * color: bottomEdge.activeRegion ?
1158+ * bottomEdge.activeRegion.color : UbuntuColors.green
1159+ * }
1160+ * regions: [
1161+ * BottomEdgeRegion {
1162+ * from: 0.4
1163+ * to: 0.6
1164+ * property color color: UbuntuColors.red
1165+ * },
1166+ * BottomEdgeRegion {
1167+ * from: 0.6
1168+ * to: 1.0
1169+ * property color color: UbuntuColors.lightGrey
1170+ * }
1171+ * ]
1172+ * }
1173+ * }
1174+ * }
1175+ * \endqml
1176+ * \note Custom regions override the default declared ones. Therefore there must
1177+ * be one region which has its \l {BottomEdgeRegion::to}{to} limit set to 1.0
1178+ * otherwise the content will not be committed at all.
1179+ * \note Regions can also be declared as child elements the same way as resources.
1180+ *
1181+ * The BottomEdge takes ownership over the custom BottomEdgeRegions, therefore
1182+ * we cannot 'reuse' regions declared in other BottomEdge components, as those
1183+ * will be destroyed together with the reusing BottomEdge component. The following
1184+ * scenario only works if the \e customRegion is not used in any other regions.
1185+ * \qml
1186+ * Page {
1187+ * BottomEdge {
1188+ * id: bottomEdge
1189+ * hint.text: "reusing regions"
1190+ * // put your content and setup here
1191+ *
1192+ * regions: [customRegion]
1193+ * }
1194+ *
1195+ * BottomEdgeRegion {
1196+ * id: customRegion
1197+ * from: 0.2
1198+ * }
1199+ * }
1200+ * \endqml
1201+ * \sa BottomEdgeRegion
1202+ *
1203+ * \section2 Page As Content
1204+ * BottomEdge accepts any component to be set as content. Also it can detect
1205+ * whether the content has a PageHeader component declared, and will inject a
1206+ * collapse navigation action automatically. In case the content has no header,
1207+ * the collapse must be provided by the content itself by calling the \l collapse
1208+ * function.
1209+ * \qml
1210+ * BottomEdge {
1211+ * id: bottomEdge
1212+ * height: parent.height
1213+ * hint.text: "Sample collapse"
1214+ * contentComponent: Rectangle {
1215+ * width: bottomEdge.width
1216+ * height: bottomEdge.height
1217+ * color: Qt.rgba(0.5, 1, bottomEdge.dragProgress, 1);
1218+ * Button {
1219+ * text: "Collapse"
1220+ * onClicked: bottomEdge.collapse()
1221+ * }
1222+ * }
1223+ * }
1224+ * \endqml
1225+ * Alternatively you can put a PageHeader component in your custom content
1226+ * as follows:
1227+ * \qml
1228+ * BottomEdge {
1229+ * id: bottomEdge
1230+ * height: parent.height
1231+ * hint.text: "Injected collapse"
1232+ * contentComponent: Rectangle {
1233+ * width: bottomEdge.width
1234+ * height: bottomEdge.height
1235+ * color: Qt.rgba(0.5, 1, bottomEdge.dragProgress, 1);
1236+ * PageHeader {
1237+ * title: "Fancy content"
1238+ * }
1239+ * }
1240+ * }
1241+ * \endqml
1242+ *
1243+ * \section2 Styling
1244+ * Similar to the other components the default style is expected to be defined
1245+ * in the theme's \e BottomEdgeStyle. However the style is not parented to the
1246+ * BottomEdge itself, but to the BottomEdge's parent item. When loaded, the style
1247+ * does not fill the parent but its bottom anchor is set to the bottom of the
1248+ * BottomEdge. Beside this the hint is also parented to the style instance. Custom
1249+ * styles are expected to implement the BottomEgdeStyle API.
1250+ */
1251+
1252+/*!
1253+ * \qmlsignal BottomEdge::commitStarted()
1254+ * Signal emitted when the content commit is started.
1255+ */
1256+
1257+/*!
1258+ * \qmlsignal BottomEdge::commitCompleted()
1259+ * Signal emitted when the content commit is completed.
1260+ */
1261+
1262+/*!
1263+ * \qmlsignal BottomEdge::collapseStarted()
1264+ * Signal emitted when the content collapse is started.
1265+ */
1266+
1267+/*!
1268+ * \qmlsignal BottomEdge::collapseCompleted()
1269+ * Signal emitted when the content collapse is completed.
1270+ */
1271+
1272+UCBottomEdge::UCBottomEdge(QQuickItem *parent)
1273+ : UCStyledItemBase(*(new UCBottomEdgePrivate), parent)
1274+{
1275+ Q_D(UCBottomEdge);
1276+ d->init();
1277+}
1278+UCBottomEdge::~UCBottomEdge()
1279+{
1280+}
1281+
1282+void UCBottomEdge::initializeComponent()
1283+{
1284+ Q_D(UCBottomEdge);
1285+ // initialize hint
1286+ d->hint->init();
1287+
1288+ // hint click() always commits
1289+ connect(d->hint, SIGNAL(clicked()), this, SLOT(commit()), Qt::DirectConnection);
1290+
1291+ // follow drag progress
1292+ connect(d->hint->swipeArea(), &UCSwipeArea::distanceChanged, [=](qreal distance) {
1293+ d->updateProgressionStates(distance);
1294+ });
1295+
1296+ // follow swipe end
1297+ connect(d->hint->swipeArea(), &UCSwipeArea::draggingChanged, [=](bool dragging) {
1298+ if (!dragging) {
1299+ d->onDragEnded();
1300+ }
1301+ });
1302+
1303+ // filter hint for mouse events
1304+ d->hint->installEventFilter(this);
1305+}
1306+
1307+void UCBottomEdge::onParentHeightChanged()
1308+{
1309+ Q_D(UCBottomEdge);
1310+ if (d->parentItem) {
1311+ setImplicitHeight(d->parentItem->height());
1312+ }
1313+}
1314+
1315+void UCBottomEdge::classBegin()
1316+{
1317+ UCStyledItemBase::classBegin();
1318+ initializeComponent();
1319+}
1320+
1321+void UCBottomEdge::componentComplete()
1322+{
1323+ UCStyledItemBase::componentComplete();
1324+ Q_D(UCBottomEdge);
1325+ // fix the hint's style version as that has no qmlContext of its own
1326+ // and thus import version check will fail; setting the context for
1327+ // the hint using this component's hint won't work either as this
1328+ // component's context does not contain the properties from the hint.
1329+ UCStyledItemBasePrivate *hintPrivate = UCStyledItemBasePrivate::get(d->hint);
1330+ hintPrivate->styleVersion = d->styleVersion;
1331+ // also set the qml data as hitn does not have that either
1332+ QQmlData::get(d->hint, true);
1333+ QQmlEngine::setContextForObject(d->hint, new QQmlContext(qmlContext(this), d->hint));
1334+ // finally complete hint creation
1335+ hintPrivate->completeStyledItem();
1336+ // and validate regions, leave out the first one as that supposed to be added first
1337+ // mimic the top limit of the regions list like we would add them one by one
1338+ for (int i = 1; i < d->regions.size(); ++i) {
1339+ UCBottomEdgeRegion *region = d->regions[i];
1340+ d->validateRegion(region, i);
1341+ }
1342+}
1343+
1344+void UCBottomEdge::itemChange(ItemChange change, const ItemChangeData &data)
1345+{
1346+ if (change == ItemParentHasChanged) {
1347+ Q_D(UCBottomEdge);
1348+ // disconnect from old parent
1349+ if (d->oldParentItem) {
1350+ disconnect(d->oldParentItem, &QQuickItem::heightChanged,
1351+ this, &UCBottomEdge::onParentHeightChanged);
1352+ }
1353+ QQuickAnchors *anchors = QQuickItemPrivate::get(this)->anchors();
1354+ if (data.item) {
1355+ const QQuickAnchorLine left = QQuickItemPrivate::get(data.item)->left();
1356+ const QQuickAnchorLine right = QQuickItemPrivate::get(data.item)->right();
1357+ const QQuickAnchorLine bottom = QQuickItemPrivate::get(data.item)->bottom();
1358+ anchors->setLeft(left);
1359+ anchors->setRight(right);
1360+ anchors->setBottom(bottom);
1361+ QQuickItemPrivate::get(data.item)->addItemChangeListener(d, QQuickItemPrivate::Children);
1362+ // follow implicitHeight
1363+ connect(data.item, &QQuickItem::heightChanged,
1364+ this, &UCBottomEdge::onParentHeightChanged);
1365+ onParentHeightChanged();
1366+ } else {
1367+ anchors->resetLeft();
1368+ anchors->resetRight();
1369+ anchors->resetBottom();
1370+ }
1371+ if (d->bottomPanel) {
1372+ d->bottomPanel->setParentItem(data.item);
1373+ }
1374+ }
1375+ UCStyledItemBase::itemChange(change, data);
1376+}
1377+
1378+bool UCBottomEdge::eventFilter(QObject *target, QEvent *event)
1379+{
1380+ Q_D(UCBottomEdge);
1381+
1382+ switch (event->type()) {
1383+ case QEvent::MouseButtonPress:
1384+ {
1385+ QMouseEvent *mouse = static_cast<QMouseEvent*>(event);
1386+ d->mousePressed = d->hint->contains(mouse->localPos());
1387+ LOG << "drag with mouse";
1388+ break;
1389+ }
1390+ case QEvent::MouseButtonRelease:
1391+ if (d->mousePressed) {
1392+ d->onDragEnded();
1393+ }
1394+ d->mousePressed = false;
1395+ break;
1396+ case QEvent::MouseMove:
1397+ {
1398+ if (d->mousePressed) {
1399+ QMouseEvent *mouse = static_cast<QMouseEvent*>(event);
1400+ qreal mouseItemY = mapFromScene(mouse->windowPos()).y();
1401+ qreal distance = abs(height() - mouseItemY);
1402+ d->updateProgressionStates(distance);
1403+ }
1404+ break;
1405+ }
1406+ default: break;
1407+ }
1408+ return UCStyledItemBase::eventFilter(target, event);
1409+}
1410+
1411+/*!
1412+ * \qmlproperty BottomEdgeHint BottomEdge::hint
1413+ * The property holds the component to display the hint for the bottom edge element.
1414+ */
1415+UCBottomEdgeHint *UCBottomEdge::hint() const
1416+{
1417+ Q_D(const UCBottomEdge);
1418+ return d->hint;
1419+}
1420+
1421+/*!
1422+ * \qmlproperty real BottomEdge::dragProgress
1423+ * \readonly
1424+ * The property specifies the proggress of the drag within [0..1] interval.
1425+ */
1426+qreal UCBottomEdge::dragProgress()
1427+{
1428+ Q_D(UCBottomEdge);
1429+ return d->dragProgress;
1430+}
1431+
1432+/*!
1433+ * \qmlproperty DragDirection BottomEdge::dragDirection
1434+ * \readonly
1435+ * The property reports the current direction of the drag. The direction is flipped
1436+ * when the drag passes the drag threshold.
1437+ * \table
1438+ * \header
1439+ * \li DragDirection
1440+ * \li Description
1441+ * \row
1442+ * \li Undefined
1443+ * \li Default. The drag is not performed or the direction is not detected.
1444+ * \row
1445+ * \li Upwards
1446+ * \li The drag is performed from bottom up or it passed the drag threshold from
1447+ * from the last point the drag was going downwards.
1448+ * \row
1449+ * \li Downwards
1450+ * \li The drag is performed from up to bottom or it passed the drag threshold from
1451+ * from the last point the drag was going upwards.
1452+ * \endtable
1453+ *
1454+ * Defaults to \e Undefined
1455+ */
1456+UCBottomEdge::DragDirection UCBottomEdge::dragDirection() const
1457+{
1458+ Q_D(const UCBottomEdge);
1459+ return d->dragDirection;
1460+}
1461+
1462+/*!
1463+ * \qmlproperty Status BottomEdge::status
1464+ * \readonly
1465+ * The property reports the actual state of the bottom edge. It can have the
1466+ * following values:
1467+ * \table
1468+ * \header
1469+ * \li Status
1470+ * \li Description
1471+ * \row
1472+ * \li Hidden
1473+ * \li The bottom edge is hidden. This does not contain the hint states.
1474+ * \row
1475+ * \li Revealed
1476+ * \li The Bottom edge content is revealed. The state can be reached only if the
1477+ * \l hint is in "Active" state.
1478+ * \row
1479+ * \li Committed
1480+ * \li The bottom edge content is fully exposed.
1481+ * \endtable
1482+ * \note Once \e Commited status is set, no further draging is possible on the content.
1483+ */
1484+UCBottomEdge::Status UCBottomEdge::status() const
1485+{
1486+ Q_D(const UCBottomEdge);
1487+ return d->status;
1488+}
1489+void UCBottomEdgePrivate::setStatus(UCBottomEdge::Status status)
1490+{
1491+ if (status == this->status) {
1492+ return;
1493+ }
1494+ this->status = status;
1495+ // logging
1496+ switch (status) {
1497+ case UCBottomEdge::Hidden: LOG << "STATUS" << "Hidden"; break;
1498+ case UCBottomEdge::Revealed: LOG << "STATUS" << "Revealed"; break;
1499+ case UCBottomEdge::Committed: LOG << "STATUS" << "Committed"; break;
1500+ }
1501+
1502+ if (bottomPanel) {
1503+ bottomPanel->setConsumeMouse(status > UCBottomEdge::Hidden);
1504+ }
1505+
1506+ Q_EMIT q_func()->statusChanged(this->status);
1507+}
1508+
1509+/*!
1510+ * \qmlproperty url BottomEdge::contentUrl
1511+ * The property holds the url to the document defining the content of the bottom
1512+ * edge. The property behaves the same way as Loader's \e source property.
1513+ */
1514+QUrl UCBottomEdge::contentUrl() const
1515+{
1516+ Q_D(const UCBottomEdge);
1517+ return d->contentUrl;
1518+}
1519+void UCBottomEdge::setContent(const QUrl &url)
1520+{
1521+ Q_D(UCBottomEdge);
1522+ if (d->contentUrl == url) {
1523+ return;
1524+ }
1525+
1526+ d->contentUrl = url;
1527+ Q_EMIT contentChanged(d->contentUrl);
1528+}
1529+
1530+/*!
1531+ * \qmlproperty Component BottomEdge::contentComponent
1532+ * The property holds the component defining the content of the bottom edge. The
1533+ * property behaves the same way as Loader's \e sourceComponent property.
1534+ */
1535+QQmlComponent *UCBottomEdge::contentComponent() const
1536+{
1537+ Q_D(const UCBottomEdge);
1538+ return d->contentComponent;
1539+}
1540+void UCBottomEdge::setContentComponent(QQmlComponent *component)
1541+{
1542+ Q_D(UCBottomEdge);
1543+ if (d->contentComponent == component) {
1544+ return;
1545+ }
1546+ d->contentComponent = component;
1547+ Q_EMIT contentComponentChanged(d->contentComponent);
1548+}
1549+
1550+/*!
1551+ * \qmlproperty Item BottomEdge::contentItem
1552+ * \readonly
1553+ * The property holds the item created either from \l contentUrl or \l contentComponent
1554+ * properties.
1555+ */
1556+QQuickItem *UCBottomEdge::contentItem() const
1557+{
1558+ Q_D(const UCBottomEdge);
1559+ return d->bottomPanel ? d->bottomPanel->m_contentItem : Q_NULLPTR;
1560+}
1561+
1562+/*!
1563+ * \qmlmethod void BottomEdge::commit()
1564+ * The function forces the bottom edge content to be fully exposed. Emits
1565+ * \l commitStarted and \l commitCompleted signals to notify the start and the
1566+ * completion of the commit operation. It is safe to call commit() multiple times.
1567+ */
1568+void UCBottomEdge::commit()
1569+{
1570+ Q_D(UCBottomEdge);
1571+ d->commit(1.0);
1572+}
1573+
1574+/*!
1575+ * \qmlmethod void BottomEdge::collapse()
1576+ * The function forces the bottom edge content to be hidden. Emits \l collapseStarted
1577+ * and \l collapseCompleted signals to notify the start and the completion of the
1578+ * collapse operation.
1579+ */
1580+void UCBottomEdge::collapse()
1581+{
1582+ Q_D(UCBottomEdge);
1583+ if (d->operationStatus == UCBottomEdgePrivate::Collapsing || d->status == UCBottomEdge::Hidden) {
1584+ LOG << "redundant collapse call";
1585+ return;
1586+ }
1587+ d->setOperationStatus(UCBottomEdgePrivate::Collapsing);
1588+ Q_EMIT collapseStarted();
1589+ bool animated = d->bottomPanel && d->bottomPanel->m_panelAnimation;
1590+ if (animated) {
1591+ connect(d->bottomPanel->m_panelAnimation, &QQuickAbstractAnimation::runningChanged,
1592+ this, &UCBottomEdge::unlockOperation, Qt::UniqueConnection);
1593+ }
1594+ // set the setStatus first to Revealed
1595+ if (d->status == UCBottomEdge::Committed) {
1596+ d->setStatus(Revealed);
1597+ }
1598+ d->setDragProgress(0.0);
1599+ if (!animated) {
1600+ unlockOperation(false);
1601+ }
1602+}
1603+
1604+/*!
1605+ * \qmlproperty list<BottomEdgeRegion> BottomEdge::regions
1606+ * The property holds the custom regions configured for the BottomEdge. The default
1607+ * configuration contains one region, which commits the content when reached. The
1608+ * defaults can be restored by setting an empty list to the property or by
1609+ * calling regions.clear().
1610+ * See \l BottomEdgeRegion.
1611+ */
1612+QQmlListProperty<UCBottomEdgeRegion> UCBottomEdge::regions()
1613+{
1614+ Q_D(UCBottomEdge);
1615+ return QQmlListProperty<UCBottomEdgeRegion>(this, &d->regions,
1616+ regions_append,
1617+ regions_count,
1618+ regions_at,
1619+ regions_clear);
1620+}
1621+
1622+void UCBottomEdge::regions_append(QQmlListProperty<UCBottomEdgeRegion> *regions, UCBottomEdgeRegion *region)
1623+{
1624+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(regions->object));
1625+ bottomEdge->appendRegion(region);
1626+}
1627+
1628+int UCBottomEdge::regions_count(QQmlListProperty<UCBottomEdgeRegion> *regions)
1629+{
1630+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(regions->object));
1631+ return bottomEdge->regions.size();
1632+}
1633+
1634+UCBottomEdgeRegion *UCBottomEdge::regions_at(QQmlListProperty<UCBottomEdgeRegion> *regions, int index)
1635+{
1636+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(regions->object));
1637+ Q_ASSERT((index >= 0) && (index < bottomEdge->regions.size()));
1638+ return bottomEdge->regions.at(index);
1639+}
1640+
1641+void UCBottomEdge::regions_clear(QQmlListProperty<UCBottomEdgeRegion> *regions)
1642+{
1643+ UCBottomEdgePrivate *bottomEdge = UCBottomEdgePrivate::get(static_cast<UCBottomEdge*>(regions->object));
1644+ bottomEdge->clearRegions(true);
1645+}
1646+
1647+/*!
1648+ * \qmlproperty BottomEdgeRegion BottomEdge::activeRegion
1649+ * \readonly
1650+ * Specifies the current active region.
1651+ */
1652+UCBottomEdgeRegion *UCBottomEdge::activeRegion()
1653+{
1654+ Q_D(UCBottomEdge);
1655+ return d->activeRegion;
1656+}
1657+
1658+#include "moc_ucbottomedge.cpp"
1659
1660=== added file 'src/Ubuntu/Components/plugin/ucbottomedge.h'
1661--- src/Ubuntu/Components/plugin/ucbottomedge.h 1970-01-01 00:00:00 +0000
1662+++ src/Ubuntu/Components/plugin/ucbottomedge.h 2015-11-27 10:36:32 +0000
1663@@ -0,0 +1,120 @@
1664+/*
1665+ * Copyright 2015 Canonical Ltd.
1666+ *
1667+ * This program is free software; you can redistribute it and/or modify
1668+ * it under the terms of the GNU Lesser General Public License as published by
1669+ * the Free Software Foundation; version 3.
1670+ *
1671+ * This program is distributed in the hope that it will be useful,
1672+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1673+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1674+ * GNU Lesser General Public License for more details.
1675+ *
1676+ * You should have received a copy of the GNU Lesser General Public License
1677+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1678+ *
1679+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
1680+ */
1681+
1682+#ifndef UCBOTTOMEDGE_H
1683+#define UCBOTTOMEDGE_H
1684+
1685+#include "ucstyleditembase.h"
1686+#include <QtCore/QPointer>
1687+#include <QtQuick/private/qquickitemchangelistener_p.h>
1688+#include <QtCore/QLoggingCategory>
1689+
1690+class UCBottomEdgeHint;
1691+class UCBottomEdgeRegion;
1692+class QQuickAbstractAnimation;
1693+class UCBottomEdgePrivate;
1694+class UCBottomEdge : public UCStyledItemBase
1695+{
1696+ Q_OBJECT
1697+ Q_ENUMS(Status DragDirection)
1698+
1699+ Q_PROPERTY(UCBottomEdgeHint* hint READ hint CONSTANT FINAL)
1700+ Q_PROPERTY(qreal dragProgress READ dragProgress NOTIFY dragProgressChanged FINAL)
1701+ Q_PROPERTY(DragDirection dragDirection READ dragDirection NOTIFY dragDirectionChanged FINAL)
1702+ Q_PROPERTY(Status status READ status NOTIFY statusChanged FINAL)
1703+ Q_PROPERTY(QUrl contentUrl READ contentUrl WRITE setContent NOTIFY contentChanged FINAL)
1704+ Q_PROPERTY(QQmlComponent *contentComponent READ contentComponent WRITE setContentComponent NOTIFY contentComponentChanged FINAL)
1705+ Q_PROPERTY(QQuickItem* contentItem READ contentItem NOTIFY contentItemChanged FINAL)
1706+ Q_PROPERTY(QQmlListProperty<UCBottomEdgeRegion> regions READ regions NOTIFY regionsChanged FINAL)
1707+ Q_PROPERTY(UCBottomEdgeRegion* activeRegion READ activeRegion NOTIFY activeRegionChanged FINAL)
1708+
1709+ // overloaded data property to catch regions
1710+ Q_PRIVATE_PROPERTY(UCBottomEdge::d_func(), QQmlListProperty<QObject> data READ data DESIGNABLE false)
1711+ Q_CLASSINFO("DefaultProperty", "data")
1712+public:
1713+ enum Status {
1714+ Hidden,
1715+ Revealed,
1716+ Committed
1717+ };
1718+ enum DragDirection {
1719+ Undefined,
1720+ Upwards,
1721+ Downwards
1722+ };
1723+
1724+ explicit UCBottomEdge(QQuickItem *parent = 0);
1725+ virtual ~UCBottomEdge();
1726+
1727+ UCBottomEdgeHint *hint() const;
1728+ qreal dragProgress();
1729+ DragDirection dragDirection() const;
1730+ Status status() const;
1731+ QUrl contentUrl() const;
1732+ void setContent(const QUrl &url);
1733+ QQmlComponent *contentComponent() const;
1734+ void setContentComponent(QQmlComponent *component);
1735+ QQuickItem *contentItem() const;
1736+ void setFillWindow(bool fill);
1737+ QQmlListProperty<UCBottomEdgeRegion> regions();
1738+ UCBottomEdgeRegion *activeRegion();
1739+
1740+Q_SIGNALS:
1741+ void dragProgressChanged(qreal dragProgress);
1742+ void dragDirectionChanged(UCBottomEdge::DragDirection direction);
1743+ void statusChanged(UCBottomEdge::Status status);
1744+ void contentChanged(const QUrl url);
1745+ void contentComponentChanged(QQmlComponent *component);
1746+ void contentItemChanged();
1747+ void regionsChanged();
1748+ void activeRegionChanged(UCBottomEdgeRegion *activeRegion);
1749+
1750+ void commitStarted();
1751+ void commitCompleted();
1752+ void collapseStarted();
1753+ void collapseCompleted();
1754+
1755+public Q_SLOTS:
1756+ void commit();
1757+ void collapse();
1758+
1759+protected:
1760+ static void regions_append(QQmlListProperty<UCBottomEdgeRegion> *sections, UCBottomEdgeRegion *section);
1761+ static int regions_count(QQmlListProperty<UCBottomEdgeRegion> *sections);
1762+ static UCBottomEdgeRegion *regions_at(QQmlListProperty<UCBottomEdgeRegion> *sections, int index);
1763+ static void regions_clear(QQmlListProperty<UCBottomEdgeRegion> *sections);
1764+
1765+ void initializeComponent();
1766+ void classBegin();
1767+ void componentComplete();
1768+ void itemChange(ItemChange change, const ItemChangeData &data);
1769+ bool eventFilter(QObject *target, QEvent *event) override;
1770+
1771+ void unlockOperation(bool running);
1772+ void onParentHeightChanged();
1773+
1774+ Q_DECLARE_PRIVATE(UCBottomEdge)
1775+
1776+ friend class tst_BottomEdge;
1777+};
1778+Q_DECLARE_METATYPE(UCBottomEdge::Status)
1779+Q_DECLARE_METATYPE(UCBottomEdge::DragDirection)
1780+
1781+Q_DECLARE_LOGGING_CATEGORY(ucBottomEdge)
1782+
1783+#endif // UCBOTTOMEDGE_H
1784
1785=== added file 'src/Ubuntu/Components/plugin/ucbottomedge_p.h'
1786--- src/Ubuntu/Components/plugin/ucbottomedge_p.h 1970-01-01 00:00:00 +0000
1787+++ src/Ubuntu/Components/plugin/ucbottomedge_p.h 2015-11-27 10:36:32 +0000
1788@@ -0,0 +1,111 @@
1789+/*
1790+ * Copyright 2015 Canonical Ltd.
1791+ *
1792+ * This program is free software; you can redistribute it and/or modify
1793+ * it under the terms of the GNU Lesser General Public License as published by
1794+ * the Free Software Foundation; version 3.
1795+ *
1796+ * This program is distributed in the hope that it will be useful,
1797+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1798+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1799+ * GNU Lesser General Public License for more details.
1800+ *
1801+ * You should have received a copy of the GNU Lesser General Public License
1802+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1803+ *
1804+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
1805+ */
1806+
1807+#ifndef UCBOTTOMEDGE_P_H
1808+#define UCBOTTOMEDGE_P_H
1809+
1810+#include "ucbottomedge.h"
1811+#include "ucstyleditembase_p.h"
1812+#include "ucaction.h"
1813+
1814+class UCBottomEdgeStyle;
1815+class UCBottomEdgePrivate : public UCStyledItemBasePrivate, protected QQuickItemChangeListener
1816+{
1817+ Q_DECLARE_PUBLIC(UCBottomEdge)
1818+
1819+public:
1820+ UCBottomEdgePrivate();
1821+
1822+ static UCBottomEdgePrivate *get(UCBottomEdge *item)
1823+ {
1824+ return item->d_func();
1825+ }
1826+ void init();
1827+
1828+ // data property
1829+ QQmlListProperty<QObject> data();
1830+ static void overload_data_append(QQmlListProperty<QObject> *, QObject *);
1831+ static void overload_data_clear(QQmlListProperty<QObject> *);
1832+
1833+ // range funcs
1834+ void appendRegion(UCBottomEdgeRegion *range);
1835+ void clearRegions(bool destroy);
1836+ void validateRegion(UCBottomEdgeRegion *region, int regionsSize = -1);
1837+
1838+ // page header manipulation
1839+ void patchContentItemHeader();
1840+ void createDefaultRegions();
1841+ void updateProgressionStates(qreal distance);
1842+ bool setActiveRegion(UCBottomEdgeRegion *range);
1843+ void detectDirection(qreal currentDistance);
1844+ void setDragDirection(UCBottomEdge::DragDirection direction);
1845+ void onDragEnded();
1846+ void commit(qreal to);
1847+
1848+ // panel positioning
1849+ void setDragProgress(qreal position);
1850+ // internal setters
1851+ void setStatus(UCBottomEdge::Status status);
1852+
1853+ // from UCStyledItemBase
1854+ bool loadStyleItem(bool animated = true) override;
1855+ // from QQuickItemChangeListener
1856+ void itemChildAdded(QQuickItem *item, QQuickItem *child);
1857+ void itemChildRemoved(QQuickItem *item, QQuickItem *child);
1858+
1859+ // members
1860+ QUrl contentUrl;
1861+ QList<UCBottomEdgeRegion*> regions;
1862+ UCBottomEdgeRegion *activeRegion;
1863+ UCBottomEdgeHint *hint;
1864+ QQmlComponent *contentComponent;
1865+ UCBottomEdgeStyle *bottomPanel;
1866+
1867+ qreal previousDistance;
1868+ qreal dragProgress;
1869+ UCBottomEdge::Status status;
1870+
1871+ enum OperationStatus {
1872+ Idle,
1873+ CommitToTop,
1874+ CommitToRegion,
1875+ Collapsing
1876+ };
1877+ OperationStatus operationStatus;
1878+ UCBottomEdge::DragDirection dragDirection;
1879+
1880+ bool defaultRegionsReset:1;
1881+ bool mousePressed:1;
1882+
1883+ // status management
1884+ void setOperationStatus(OperationStatus s);
1885+ bool isLocked()
1886+ {
1887+ return operationStatus > Idle;
1888+ }
1889+};
1890+
1891+class UCCollapseAction : public UCAction
1892+{
1893+ Q_OBJECT
1894+public:
1895+ UCCollapseAction(QObject *parent = 0);
1896+};
1897+Q_DECLARE_METATYPE(QQmlListProperty<UCAction>)
1898+
1899+#endif // UCBOTTOMEDGE_P_H
1900
1901=== modified file 'src/Ubuntu/Components/plugin/ucbottomedgehint.cpp'
1902--- src/Ubuntu/Components/plugin/ucbottomedgehint.cpp 2015-11-19 10:04:28 +0000
1903+++ src/Ubuntu/Components/plugin/ucbottomedgehint.cpp 2015-11-27 10:36:32 +0000
1904@@ -169,7 +169,7 @@
1905 }
1906 void UCBottomEdgeHint::mouseReleaseEvent(QMouseEvent *event)
1907 {
1908- if (m_pressed && (m_status >= Active)) {
1909+ if (m_pressed && (m_status >= Active) && contains(event->localPos())) {
1910 Q_EMIT clicked();
1911 event->accept();
1912 } else {
1913
1914=== modified file 'src/Ubuntu/Components/plugin/ucbottomedgehint.h'
1915--- src/Ubuntu/Components/plugin/ucbottomedgehint.h 2015-11-17 13:45:25 +0000
1916+++ src/Ubuntu/Components/plugin/ucbottomedgehint.h 2015-11-27 10:36:32 +0000
1917@@ -87,6 +87,8 @@
1918 int m_deactivateTimeout;
1919 Status m_status;
1920 bool m_pressed:1;
1921+
1922+ friend class UCBottomEdge;
1923 };
1924
1925 #endif // UCBOTTOMEDGEHINT_H
1926
1927=== added file 'src/Ubuntu/Components/plugin/ucbottomedgeregion.cpp'
1928--- src/Ubuntu/Components/plugin/ucbottomedgeregion.cpp 1970-01-01 00:00:00 +0000
1929+++ src/Ubuntu/Components/plugin/ucbottomedgeregion.cpp 2015-11-27 10:36:32 +0000
1930@@ -0,0 +1,253 @@
1931+/*
1932+ * Copyright 2015 Canonical Ltd.
1933+ *
1934+ * This program is free software; you can redistribute it and/or modify
1935+ * it under the terms of the GNU Lesser General Public License as published by
1936+ * the Free Software Foundation; version 3.
1937+ *
1938+ * This program is distributed in the hope that it will be useful,
1939+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1940+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1941+ * GNU Lesser General Public License for more details.
1942+ *
1943+ * You should have received a copy of the GNU Lesser General Public License
1944+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1945+ *
1946+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
1947+ */
1948+
1949+#include "ucbottomedge.h"
1950+#include "ucbottomedge_p.h"
1951+#include "ucbottomedgeregion.h"
1952+#include "propertychange_p.h"
1953+#include <QtQml/private/qqmlproperty_p.h>
1954+
1955+/*!
1956+ * \qmltype BottomEdgeRegion
1957+ * \instantiates UCBottomEdgeRegion
1958+ * \inherits QtObject
1959+ * \inmodule Ubuntu.Components 1.3
1960+ * \since Ubuntu.Components 1.3
1961+ * \ingroup ubuntu
1962+ * \brief Defines an active region within the BottomEdge component.
1963+ *
1964+ * Bottom edge regions are portions within the bottom edge area which can define
1965+ * different content or action whenever the drag enters in the area. The area is
1966+ * defined by \l from and \l to properties vertically, whereas horizontally is
1967+ * stretched across bottom edge width. Custom content can be defined through
1968+ * \l contentUrl or \l contentComponent properties, which will override the
1969+ * \l BottomEdge::contentUrl and \l BottomEdge::contentComponent properties for the
1970+ * time the gesture is in the section area.
1971+ * \qml
1972+ * import QtQuick 2.4
1973+ * import Ubuntu.Components 1.3
1974+ *
1975+ * MainView {
1976+ * width: units.gu(40)
1977+ * height: units.gu(70)
1978+ *
1979+ * Page {
1980+ * header: PageHeader {
1981+ * title: "BottomEdge regions"
1982+ * }
1983+ *
1984+ * BottomEdge {
1985+ * id: bottomEdge
1986+ * height: parent.height - units.gu(20)
1987+ * hint: BottomEdgeHint {
1988+ * text: "My bottom edge"
1989+ * }
1990+ * // a fake content till we reach the committable area
1991+ * contentComponent: Rectangle {
1992+ * width: bottomEdge.width
1993+ * height: bottomEdge.height
1994+ * color: UbuntuColors.green
1995+ * }
1996+ * // override bottom edge sections to switch to real content
1997+ * BottomEdgeRegion {
1998+ * from: 0.33
1999+ * contentComponent: Page {
2000+ * width: bottomEdge.width
2001+ * height: bottomEdge.height
2002+ * header: PageHeader {
2003+ * title: "BottomEdge Content"
2004+ * }
2005+ * }
2006+ * }
2007+ * }
2008+ * }
2009+ * }
2010+ * \endqml
2011+ *
2012+ * Entering into the section area is signalled by the \l entered signal and when
2013+ * drag leaves the area the \l exited signal is emitted. If the drag ends within
2014+ * the section area, the \l dragEnded signal is emitted. In case the section's
2015+ * \l to property is less than 1.0, the bottom edge content will only be exposed
2016+ * to that value, and the \l BottomEdge::status will get the \e Committed value.
2017+ * No further drag is possible after reaching \e Commited state.
2018+ *
2019+ * \note Whereas there is no restriction on making overlapping sections, beware that
2020+ * overlapping sections changing the content through the \l contentUrl or \l contentComponent
2021+ * properties will cause unpredictable results.
2022+ */
2023+
2024+UCBottomEdgeRegion::UCBottomEdgeRegion(QObject *parent)
2025+ : QObject(parent)
2026+ , m_bottomEdge(qobject_cast<UCBottomEdge*>(parent))
2027+ , m_component(Q_NULLPTR)
2028+ , m_urlBackup(Q_NULLPTR)
2029+ , m_componentBackup(Q_NULLPTR)
2030+ , m_from(0.0)
2031+ , m_to(-1.0)
2032+ , m_enabled(true)
2033+{
2034+}
2035+
2036+void UCBottomEdgeRegion::attachToBottomEdge(UCBottomEdge *bottomEdge)
2037+{
2038+ QQml_setParent_noEvent(this, bottomEdge);
2039+ m_bottomEdge = bottomEdge;
2040+ // adjust to property value if not set yet
2041+ if (m_to <= 0.0) {
2042+ m_to = 1.0;
2043+ Q_EMIT toChanged();
2044+ }
2045+}
2046+
2047+bool UCBottomEdgeRegion::contains(qreal dragRatio)
2048+{
2049+ return (m_enabled && (m_from < m_to) && dragRatio >= m_from && dragRatio <= m_to);
2050+}
2051+
2052+void UCBottomEdgeRegion::enter()
2053+{
2054+ Q_EMIT entered();
2055+ // backup url
2056+ if (m_url.isValid()) {
2057+ m_urlBackup = new PropertyChange(m_bottomEdge, "contentUrl");
2058+ QQmlProperty property(this, "contentUrl", qmlContext(this));
2059+ QQmlAbstractBinding *binding = QQmlPropertyPrivate::binding(property);
2060+ if (binding) {
2061+ PropertyChange::setBinding(m_urlBackup, binding);
2062+ } else {
2063+ PropertyChange::setValue(m_urlBackup, m_url);
2064+ }
2065+ }
2066+ if (m_component) {
2067+ m_componentBackup = new PropertyChange(m_bottomEdge, "contentComponent");
2068+ QQmlProperty property(this, "contentComponent", qmlContext(this));
2069+ QQmlAbstractBinding *binding = QQmlPropertyPrivate::binding(property);
2070+ if (binding) {
2071+ PropertyChange::setBinding(m_componentBackup, binding);
2072+ } else {
2073+ PropertyChange::setValue(m_componentBackup, QVariant::fromValue<QQmlComponent*>(m_component));
2074+ }
2075+ }
2076+}
2077+
2078+void UCBottomEdgeRegion::exit()
2079+{
2080+ if (m_componentBackup) {
2081+ delete m_componentBackup;
2082+ m_componentBackup = Q_NULLPTR;
2083+ }
2084+ if (m_urlBackup) {
2085+ delete m_urlBackup;
2086+ m_urlBackup = Q_NULLPTR;
2087+ }
2088+ Q_EMIT exited();
2089+}
2090+
2091+const QRectF UCBottomEdgeRegion::rect(const QRectF &bottomEdgeRect)
2092+{
2093+ QRectF regionRect(
2094+ bottomEdgeRect.topLeft() + QPointF(0, bottomEdgeRect.height() * (1.0 - m_to)),
2095+ QSizeF(bottomEdgeRect.width(), bottomEdgeRect.height() * (m_to - m_from)));
2096+ return regionRect;
2097+}
2098+
2099+/*!
2100+ * \qmlproperty bool BottomEdgeRegion::enabled
2101+ * Enables the section. Disabled sections do not trigger nor change the BottomEdge
2102+ * content. Defaults to false.
2103+ */
2104+void UCBottomEdgeRegion::setEnabled(bool enabled)
2105+{
2106+ if (enabled == m_enabled) {
2107+ return;
2108+ }
2109+ m_enabled = enabled;
2110+ if (m_bottomEdge) {
2111+ UCBottomEdgePrivate::get(m_bottomEdge)->validateRegion(this);
2112+ }
2113+ Q_EMIT enabledChanged();
2114+}
2115+
2116+/*!
2117+ * \qmlproperty real BottomEdgeRegion::from
2118+ * Specifies the starting ratio of the bottom erge area. The value must be bigger
2119+ * or equal to 0 but strictly smaller than \l to. Defaults to 0.0.
2120+ */
2121+void UCBottomEdgeRegion::setFrom(qreal from)
2122+{
2123+ if (from == m_from) {
2124+ return;
2125+ }
2126+ m_from = from;
2127+ if (m_bottomEdge) {
2128+ UCBottomEdgePrivate::get(m_bottomEdge)->validateRegion(this);
2129+ }
2130+ Q_EMIT fromChanged();
2131+}
2132+
2133+/*!
2134+ * \qmlproperty real BottomEdgeRegion::to
2135+ * Specifies the ending ratio of the bottom edge area. The value must be bigger
2136+ * than \l from and smaller or equal to 1.0.
2137+ * \note If the end point is less than 1.0, ending the drag within the section
2138+ * will result in exposing the bottom edge content only till the ration specified
2139+ * by this property.
2140+ */
2141+void UCBottomEdgeRegion::setTo(qreal to)
2142+{
2143+ if (to == m_to) {
2144+ return;
2145+ }
2146+ m_to = to;
2147+ if (m_bottomEdge) {
2148+ UCBottomEdgePrivate::get(m_bottomEdge)->validateRegion(this);
2149+ }
2150+ Q_EMIT toChanged();
2151+}
2152+
2153+/*!
2154+ * \qmlproperty url BottomEdgeRegion::contentUrl
2155+ * Specifies the url to the document defining the section specific content. This
2156+ * propery will temporarily override the \l BottomEdge::contentUrl property value
2157+ * when the drag gesture enters the section area. The orginal value will be restored
2158+ * once the gesture leaves the section area.
2159+ */
2160+
2161+/*!
2162+ * \qmlproperty Component BottomEdgeRegion::contentComponent
2163+ * Specifies the component defining the section specific content. This propery
2164+ * will temporarily override the \l BottomEdge::contentComponent property value
2165+ * when the drag gesture enters the section area. The orginal value will be restored
2166+ * once the gesture leaves the section area.
2167+ */
2168+
2169+/*!
2170+ * \qmlsignal void BottomEdgeRegion::entered()
2171+ * Signal triggered when the drag enters into the area defined by the bottom edge
2172+ * section.
2173+ */
2174+
2175+/*!
2176+ * \qmlsignal void BottomEdgeRegion::exited()
2177+ * Signal triggered when the drag leaves the area defined by the bottom edge section.
2178+ */
2179+
2180+/*!
2181+ * \qmlsignal void BottomEdgeRegion::dragEnded()
2182+ * Signal triggered when the drag ends within the active bottom edge section area.
2183+ */
2184
2185=== added file 'src/Ubuntu/Components/plugin/ucbottomedgeregion.h'
2186--- src/Ubuntu/Components/plugin/ucbottomedgeregion.h 1970-01-01 00:00:00 +0000
2187+++ src/Ubuntu/Components/plugin/ucbottomedgeregion.h 2015-11-27 10:36:32 +0000
2188@@ -0,0 +1,78 @@
2189+/*
2190+ * Copyright 2015 Canonical Ltd.
2191+ *
2192+ * This program is free software; you can redistribute it and/or modify
2193+ * it under the terms of the GNU Lesser General Public License as published by
2194+ * the Free Software Foundation; version 3.
2195+ *
2196+ * This program is distributed in the hope that it will be useful,
2197+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2198+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2199+ * GNU Lesser General Public License for more details.
2200+ *
2201+ * You should have received a copy of the GNU Lesser General Public License
2202+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2203+ *
2204+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
2205+ */
2206+
2207+#ifndef UCBOTTOMEDGEREGION_H
2208+#define UCBOTTOMEDGEREGION_H
2209+
2210+#include <QtCore/QObject>
2211+#include <QtQml/QQmlParserStatus>
2212+#include <QtCore/QUrl>
2213+#include <QtCore/QPointer>
2214+
2215+class UCBottomEdge;
2216+class QQmlComponent;
2217+class PropertyChange;
2218+class UCBottomEdgeRegion : public QObject
2219+{
2220+ Q_OBJECT
2221+
2222+ Q_PROPERTY(bool enabled MEMBER m_enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
2223+ Q_PROPERTY(qreal from MEMBER m_from WRITE setFrom NOTIFY fromChanged FINAL)
2224+ Q_PROPERTY(qreal to MEMBER m_to WRITE setTo NOTIFY toChanged FINAL)
2225+ Q_PROPERTY(QUrl contentUrl MEMBER m_url NOTIFY contentChanged FINAL)
2226+ Q_PROPERTY(QQmlComponent* contentComponent MEMBER m_component NOTIFY contentComponentChanged FINAL)
2227+public:
2228+ explicit UCBottomEdgeRegion(QObject *parent = 0);
2229+ void attachToBottomEdge(UCBottomEdge *bottomEdge);
2230+
2231+ // used internally
2232+ void setFrom(qreal from);
2233+ void setTo(qreal to);
2234+ void setEnabled(bool enabled);
2235+ bool contains(qreal dragRatio);
2236+ void enter();
2237+ void exit();
2238+ const QRectF rect(const QRectF &bottomEdgeRect);
2239+
2240+Q_SIGNALS:
2241+ void enabledChanged();
2242+ void fromChanged();
2243+ void toChanged();
2244+ void contentChanged();
2245+ void contentComponentChanged();
2246+
2247+ void entered();
2248+ void exited();
2249+ void dragEnded();
2250+
2251+protected:
2252+ QUrl m_url;
2253+ QPointer<UCBottomEdge> m_bottomEdge;
2254+ QQmlComponent *m_component;
2255+ PropertyChange *m_urlBackup;
2256+ PropertyChange *m_componentBackup;
2257+ qreal m_from;
2258+ qreal m_to;
2259+ bool m_enabled:1;
2260+
2261+ friend class UCBottomEdge;
2262+ friend class UCBottomEdgePrivate;
2263+ friend class tst_BottomEdge;
2264+};
2265+
2266+#endif // UCBOTTOMEDGEREGION_H
2267
2268=== added file 'src/Ubuntu/Components/plugin/ucbottomedgestyle.cpp'
2269--- src/Ubuntu/Components/plugin/ucbottomedgestyle.cpp 1970-01-01 00:00:00 +0000
2270+++ src/Ubuntu/Components/plugin/ucbottomedgestyle.cpp 2015-11-27 10:36:32 +0000
2271@@ -0,0 +1,65 @@
2272+/*
2273+ * Copyright 2015 Canonical Ltd.
2274+ *
2275+ * This program is free software; you can redistribute it and/or modify
2276+ * it under the terms of the GNU Lesser General Public License as published by
2277+ * the Free Software Foundation; version 3.
2278+ *
2279+ * This program is distributed in the hope that it will be useful,
2280+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2281+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2282+ * GNU Lesser General Public License for more details.
2283+ *
2284+ * You should have received a copy of the GNU Lesser General Public License
2285+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2286+ *
2287+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
2288+ */
2289+
2290+#include "ucbottomedgestyle.h"
2291+#include "ucbottomedge_p.h"
2292+
2293+/*!
2294+ * \qmltype BottomEdgeStyle
2295+ * \instantiates UCBottomEdgeStyle
2296+ * \inqmlmodule Ubuntu.Components.Styles 1.3
2297+ * \since Ubuntu.Components.Styles 1.3
2298+ * \ingroup style-api
2299+ * \brief Style API for BottomEdge content holder panel.
2300+ */
2301+
2302+void UCBottomEdgeStyle::setConsumeMouse(bool consume)
2303+{
2304+ if (consume) {
2305+ setAcceptedMouseButtons(Qt::AllButtons);
2306+ } else {
2307+ setAcceptedMouseButtons(Qt::NoButton);
2308+ }
2309+}
2310+
2311+void UCBottomEdgeStyle::mousePressEvent(QMouseEvent *event)
2312+{
2313+ event->accept();
2314+}
2315+
2316+/*!
2317+ * \qmlproperty Item BottomEdgeStyle::panel
2318+ * Represents the item holding the bottom edge content.
2319+ */
2320+
2321+/*!
2322+ * \qmlproperty Item BottomEdgeStyle::contentItem
2323+ * Holds the current content item created from \l BottomEdge::contentUrl or \l BottomEdge::contentComponent.
2324+ */
2325+
2326+/*!
2327+ * \qmlproperty Animation BottomEdgeStyle::panelAnimation
2328+ * Holds the animation of the panel. BottomEdge requires this property to know
2329+ * when the bottom edge is fully committed or collapsed.
2330+ */
2331+
2332+/*!
2333+ * \qmlproperty real BottomEdgeStyle::revealThreshold
2334+ * Holds the style configured value which drives when the bottom edge content
2335+ * should be exposed.
2336+ */
2337
2338=== added file 'src/Ubuntu/Components/plugin/ucbottomedgestyle.h'
2339--- src/Ubuntu/Components/plugin/ucbottomedgestyle.h 1970-01-01 00:00:00 +0000
2340+++ src/Ubuntu/Components/plugin/ucbottomedgestyle.h 2015-11-27 10:36:32 +0000
2341@@ -0,0 +1,60 @@
2342+/*
2343+ * Copyright 2015 Canonical Ltd.
2344+ *
2345+ * This program is free software; you can redistribute it and/or modify
2346+ * it under the terms of the GNU Lesser General Public License as published by
2347+ * the Free Software Foundation; version 3.
2348+ *
2349+ * This program is distributed in the hope that it will be useful,
2350+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2351+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2352+ * GNU Lesser General Public License for more details.
2353+ *
2354+ * You should have received a copy of the GNU Lesser General Public License
2355+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2356+ *
2357+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
2358+ */
2359+
2360+#ifndef UCBOTTOMEDGESTYLE_H
2361+#define UCBOTTOMEDGESTYLE_H
2362+
2363+#include <QtQuick/QQuickItem>
2364+
2365+class QQuickItem;
2366+class QQuickAbstractAnimation;
2367+class UCBottomEdge;
2368+class UCBottomEdgeStyle : public QQuickItem
2369+{
2370+ Q_OBJECT
2371+ Q_PROPERTY(QQuickItem* panel MEMBER m_panel NOTIFY panelChanged FINAL)
2372+ Q_PROPERTY(QQuickItem* contentItem MEMBER m_contentItem NOTIFY contentItemChanged FINAL)
2373+ Q_PROPERTY(QQuickAbstractAnimation *panelAnimation MEMBER m_panelAnimation NOTIFY panelAnimationChanged FINAL)
2374+ Q_PROPERTY(qreal revealThreshold MEMBER m_revealThreshold NOTIFY revealThresholdChanged FINAL)
2375+public:
2376+ explicit UCBottomEdgeStyle(QQuickItem *parent = 0)
2377+ : QQuickItem(parent)
2378+ {}
2379+
2380+ void setConsumeMouse(bool consume);
2381+
2382+Q_SIGNALS:
2383+ void panelChanged();
2384+ void contentItemChanged();
2385+ void panelAnimationChanged();
2386+ void revealThresholdChanged();
2387+
2388+protected:
2389+ void mousePressEvent(QMouseEvent *event);
2390+
2391+private:
2392+ QQuickItem *m_panel = nullptr;
2393+ QQuickItem *m_contentItem = nullptr;
2394+ QQuickAbstractAnimation *m_panelAnimation = nullptr;
2395+ qreal m_revealThreshold = 0.;
2396+
2397+ friend class UCBottomEdge;
2398+ friend class UCBottomEdgePrivate;
2399+};
2400+
2401+#endif // UCBOTTOMEDGESTYLE_H
2402
2403=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.cpp'
2404--- src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2015-10-20 11:32:09 +0000
2405+++ src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2015-11-27 10:36:32 +0000
2406@@ -26,7 +26,8 @@
2407 #include <QtQuick/private/qquickanchors_p.h>
2408
2409 UCStyledItemBasePrivate::UCStyledItemBasePrivate()
2410- : styleComponent(Q_NULLPTR)
2411+ : oldParentItem(Q_NULLPTR)
2412+ , styleComponent(Q_NULLPTR)
2413 , styleItem(Q_NULLPTR)
2414 , styleVersion(0)
2415 , activeFocusOnPress(false)
2416@@ -469,6 +470,15 @@
2417 }
2418 }
2419
2420+void UCStyledItemBasePrivate::completeStyledItem()
2421+{
2422+ Q_Q(UCStyledItemBase);
2423+ // no animation at this time
2424+ // prepare style context if not been done yet
2425+ postStyleChanged();
2426+ loadStyleItem(false);
2427+}
2428+
2429 void UCStyledItemBase::componentComplete()
2430 {
2431 QQuickItem::componentComplete();
2432@@ -476,10 +486,16 @@
2433 // make sure the theme version is up to date
2434 d->styleVersion = d->importVersion(this);
2435 UCTheme::checkMixedVersionImports(this, d->styleVersion);
2436- // no animation at this time
2437- // prepare style context if not been done yet
2438- d->postStyleChanged();
2439- d->loadStyleItem(false);
2440+ d->completeStyledItem();
2441+}
2442+
2443+void UCStyledItemBase::itemChange(ItemChange change, const ItemChangeData &data)
2444+{
2445+ QQuickItem::itemChange(change, data);
2446+ if (change == ItemParentHasChanged) {
2447+ // update parentItem
2448+ d_func()->oldParentItem = data.item;
2449+ }
2450 }
2451
2452 // grab pressed state and focus if it can be
2453
2454=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.h'
2455--- src/Ubuntu/Components/plugin/ucstyleditembase.h 2015-10-20 10:19:10 +0000
2456+++ src/Ubuntu/Components/plugin/ucstyleditembase.h 2015-11-27 10:36:32 +0000
2457@@ -60,6 +60,7 @@
2458 virtual void postThemeChanged();
2459
2460 void componentComplete();
2461+ void itemChange(ItemChange change, const ItemChangeData &data);
2462 void mousePressEvent(QMouseEvent *event);
2463 bool childMouseEventFilter(QQuickItem *child, QEvent *event);
2464
2465
2466=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase_p.h'
2467--- src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2015-10-20 11:05:53 +0000
2468+++ src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2015-11-27 10:36:32 +0000
2469@@ -56,6 +56,7 @@
2470 virtual void preStyleChanged();
2471 virtual void postStyleChanged() {}
2472 virtual bool loadStyleItem(bool animated = true);
2473+ virtual void completeStyledItem();
2474
2475 // from UCImportVersionChecker
2476 virtual QString propertyForVersion(quint16 version) const;
2477@@ -64,6 +65,7 @@
2478
2479 QPointer<QQmlContext> styleItemContext;
2480 QString styleDocument;
2481+ QQuickItem *oldParentItem;
2482 QQmlComponent *styleComponent;
2483 QQuickItem *styleItem;
2484 quint16 styleVersion;
2485
2486=== modified file 'src/Ubuntu/Test/plugin/uctestextras.cpp'
2487--- src/Ubuntu/Test/plugin/uctestextras.cpp 2015-11-04 10:56:03 +0000
2488+++ src/Ubuntu/Test/plugin/uctestextras.cpp 2015-11-27 10:36:32 +0000
2489@@ -227,18 +227,151 @@
2490 steps = 5;
2491 }
2492 touchPress(touchId, item, from);
2493- QTest::qWait(10);
2494+ QTest::qWait(20);
2495 touchMove(touchId, item, from);
2496 QPoint movePoint(from);
2497 qreal stepDx = delta.x() / steps;
2498 qreal stepDy = delta.y() / steps;
2499 if (!delta.isNull()) {
2500 for (int i = 0; i < steps - 1; i++) {
2501- QTest::qWait(10);
2502+ QTest::qWait(20);
2503 movePoint += QPoint(stepDx, stepDy);
2504 touchMove(touchId, item, movePoint);
2505 }
2506 }
2507- QTest::qWait(10);
2508+ QTest::qWait(20);
2509 touchRelease(touchId, item, from + delta);
2510 }
2511+
2512+/*!
2513+ * \qmlmethod void TestExtras::mouseDrag(touchId, item, from, delta, button, stateKey, steps = 5, delay = 20)
2514+ * The function performs a drag with a mouse over an \a item from the starting
2515+ * point \a from with a \a delta. The gesture is realized with a mouse press,
2516+ * \a step moves and a release event, with a \e delay in between each mouse event.
2517+ *
2518+ * By default the function uses 5 steps to produce the drag. This value can be any
2519+ * positive number, driving the gesture appliance to be faster (less than 5 moves) or
2520+ * slower (more than 5 moves). If a negative or 0 value is given, the function will
2521+ * use the default 5 steps to produce the gesture.
2522+ */
2523+
2524+void UCTestExtras::mouseDrag(QQuickItem *item, const QPoint &from, const QPoint &delta, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, int steps, int delay)
2525+{
2526+ if (delta.isNull()) {
2527+ qWarning() << "delta point is invalid";
2528+ return;
2529+ }
2530+ if (steps <= 0) {
2531+ steps = 5;
2532+ }
2533+ if (delay < 20) {
2534+ delay = 20;
2535+ }
2536+ QTest::mousePress(item->window(), button, stateKey, from, delay);
2537+ QPoint movePoint(from);
2538+ qreal stepDx = delta.x() / steps;
2539+ qreal stepDy = delta.y() / steps;
2540+ if (!delta.isNull()) {
2541+ for (int i = 0; i < steps; i++) {
2542+ QTest::mouseMove(item->window(), movePoint, delay);
2543+ movePoint += QPoint(stepDx, stepDy);
2544+ }
2545+ }
2546+ QTest::mouseRelease(item->window(), button, stateKey, movePoint, delay);
2547+}
2548+
2549+/*
2550+ * void TestExtras::touchDragWithPoints(touchId, item, list<point> ponts, delay = 20)
2551+ * Similar to \l touchDrag function, but here the points must be specified in \e points property.
2552+ * The first point is expected to be the starting position, after which each point is a relative
2553+ * point defining the move. A minimum of 5 points are needed to properly produce a drag.
2554+ * \qml
2555+ * import Qt.Test 1.0
2556+ * import Ubuntu.Test 1.0
2557+ * Item {
2558+ * id: testItem
2559+ * UbuntuTestCase {
2560+ *
2561+ * function test_vertical_drag_upwards() {
2562+ * var points = [];
2563+ * points.push(centerOf(testItem));
2564+ * points.push(Qt.point(0, -1));
2565+ * points.push(Qt.point(0, -3));
2566+ * points.push(Qt.point(0, -6));
2567+ * points.push(Qt.point(0, -5));
2568+ * points.push(Qt.point(0, -2));
2569+ * TestExtras.touchDragWithPoints(0, testItem, points);
2570+ * }
2571+ * }
2572+ * }
2573+ * \endqml
2574+ * The delay must be a minimum of 20 milliseconds.
2575+ */
2576+void UCTestExtras::touchDragWithPoints(int touchId, QQuickItem *item, QList<QPoint> points, int delay)
2577+{
2578+ if (points.size() < 5) {
2579+ qWarning() << "minimum 5 points are needed.";
2580+ return;
2581+ }
2582+ if (delay < 20) {
2583+ delay = 20;
2584+ }
2585+
2586+ QPoint movePoint(points[0]);
2587+ touchPress(touchId, item, movePoint);
2588+ QTest::qWait(delay);
2589+ touchMove(touchId, item, movePoint);
2590+ for (int i = 1; i < points.size(); ++i) {
2591+ QTest::qWait(delay);
2592+ movePoint += points[i];
2593+ touchMove(touchId, item, movePoint);
2594+ }
2595+ QTest::qWait(delay);
2596+ touchRelease(touchId, item, movePoint);
2597+}
2598+
2599+/*
2600+ * void TestExtras::mouseDragWithPoints(item, button, list<point> ponts, stateKey = 0, delay = 20)
2601+ * Similar to \l mouseDrag function, but here the points must be specified in \e points property.
2602+ * The first point is expected to be the starting position, after which each point is a relative
2603+ * point defining the move. A minimum of 5 points are needed to properly produce a drag.
2604+ * \qml
2605+ * import Qt.Test 1.0
2606+ * import Ubuntu.Test 1.0
2607+ * Item {
2608+ * id: testItem
2609+ * UbuntuTestCase {
2610+ *
2611+ * function test_vertical_drag_upwards() {
2612+ * var points = [];
2613+ * points.push(centerOf(testItem));
2614+ * points.push(Qt.point(0, -1));
2615+ * points.push(Qt.point(0, -3));
2616+ * points.push(Qt.point(0, -6));
2617+ * points.push(Qt.point(0, -5));
2618+ * points.push(Qt.point(0, -2));
2619+ * TestExtras.mouseDragWithPoints(testItem, Qt.LeftButton, points);
2620+ * }
2621+ * }
2622+ * }
2623+ * \endqml
2624+ * The delay must be a minimum of 20 milliseconds.
2625+ */
2626+void UCTestExtras::mouseDragWithPoints(QQuickItem *item, QList<QPoint> points, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, int delay)
2627+{
2628+ if (points.size() < 5) {
2629+ qWarning() << "minimum 5 points are needed.";
2630+ return;
2631+ }
2632+ if (delay < 20) {
2633+ delay = 20;
2634+ }
2635+ QTest::mousePress(item->window(), button, stateKey, item->mapToScene(points[0]).toPoint(), delay);
2636+ QPoint movePoint(item->mapToScene(points[0]).toPoint());
2637+ QTest::mouseMove(item->window(), movePoint, delay);
2638+ for (int i = 1; i < points.size(); ++i) {
2639+ movePoint += points[i];
2640+ QTest::mouseMove(item->window(), movePoint, delay);
2641+ }
2642+ QTest::mouseRelease(item->window(), button, stateKey, movePoint, delay);
2643+}
2644
2645=== modified file 'src/Ubuntu/Test/plugin/uctestextras.h'
2646--- src/Ubuntu/Test/plugin/uctestextras.h 2015-11-10 14:42:20 +0000
2647+++ src/Ubuntu/Test/plugin/uctestextras.h 2015-11-27 10:36:32 +0000
2648@@ -44,6 +44,12 @@
2649 static void touchMove(int touchId, QQuickItem *item, const QPoint &point);
2650 static void touchDrag(int touchId, QQuickItem *item, const QPoint &from, const QPoint &delta, int steps = 5);
2651
2652+ static void mouseDrag(QQuickItem *item, const QPoint &from, const QPoint &delta, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, int steps = -1, int delay = -1);
2653+
2654+public: // yet for cpp use
2655+ static void touchDragWithPoints(int touchId, QQuickItem *item, QList<QPoint> points, int delay = -1);
2656+ static void mouseDragWithPoints(QQuickItem *item, QList<QPoint> points, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, int delay = -1);
2657+
2658 private:
2659 static QTouchDevice *m_touchDevice;
2660 static UCTestExtras *m_testExtras;
2661
2662=== added directory 'tests/unit_x11/tst_bottomedge'
2663=== added file 'tests/unit_x11/tst_bottomedge/AddCustomRegionOnCompleted.qml'
2664--- tests/unit_x11/tst_bottomedge/AddCustomRegionOnCompleted.qml 1970-01-01 00:00:00 +0000
2665+++ tests/unit_x11/tst_bottomedge/AddCustomRegionOnCompleted.qml 2015-11-27 10:36:32 +0000
2666@@ -0,0 +1,41 @@
2667+/*
2668+ * Copyright 2015 Canonical Ltd.
2669+ *
2670+ * This program is free software; you can redistribute it and/or modify
2671+ * it under the terms of the GNU Lesser General Public License as published by
2672+ * the Free Software Foundation; version 3.
2673+ *
2674+ * This program is distributed in the hope that it will be useful,
2675+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2676+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2677+ * GNU Lesser General Public License for more details.
2678+ *
2679+ * You should have received a copy of the GNU Lesser General Public License
2680+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2681+ *
2682+ */
2683+
2684+import QtQuick 2.4
2685+import Ubuntu.Components 1.3
2686+
2687+Item {
2688+ id: holder
2689+ width: units.gu(40)
2690+ height: units.gu(71)
2691+
2692+ BottomEdge {
2693+ hint.text: "Test"
2694+ objectName: "testItem"
2695+
2696+ Component.onCompleted: {
2697+ var v = [];
2698+ v.push(customRegion);
2699+ regions = v;
2700+ }
2701+ }
2702+ BottomEdgeRegion {
2703+ id: customRegion
2704+ objectName: "customRegion"
2705+ }
2706+}
2707+
2708
2709=== added file 'tests/unit_x11/tst_bottomedge/AddCustomRegionOwnedByOtherBottomEdge.qml'
2710--- tests/unit_x11/tst_bottomedge/AddCustomRegionOwnedByOtherBottomEdge.qml 1970-01-01 00:00:00 +0000
2711+++ tests/unit_x11/tst_bottomedge/AddCustomRegionOwnedByOtherBottomEdge.qml 2015-11-27 10:36:32 +0000
2712@@ -0,0 +1,39 @@
2713+/*
2714+ * Copyright 2015 Canonical Ltd.
2715+ *
2716+ * This program is free software; you can redistribute it and/or modify
2717+ * it under the terms of the GNU Lesser General Public License as published by
2718+ * the Free Software Foundation; version 3.
2719+ *
2720+ * This program is distributed in the hope that it will be useful,
2721+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2722+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2723+ * GNU Lesser General Public License for more details.
2724+ *
2725+ * You should have received a copy of the GNU Lesser General Public License
2726+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2727+ *
2728+ */
2729+
2730+import QtQuick 2.4
2731+import Ubuntu.Components 1.3
2732+
2733+Item {
2734+ id: holder
2735+ width: units.gu(40)
2736+ height: units.gu(71)
2737+
2738+ BottomEdge {
2739+ hint.text: "Test"
2740+ objectName: "testItem"
2741+ regions: [customRegion]
2742+ }
2743+ BottomEdge {
2744+ hint.text: "Other"
2745+ BottomEdgeRegion {
2746+ id: customRegion
2747+ objectName: "customRegion"
2748+ }
2749+ }
2750+}
2751+
2752
2753=== added file 'tests/unit_x11/tst_bottomedge/AddCustomRegionUsingDataProperty.qml'
2754--- tests/unit_x11/tst_bottomedge/AddCustomRegionUsingDataProperty.qml 1970-01-01 00:00:00 +0000
2755+++ tests/unit_x11/tst_bottomedge/AddCustomRegionUsingDataProperty.qml 2015-11-27 10:36:32 +0000
2756@@ -0,0 +1,33 @@
2757+/*
2758+ * Copyright 2015 Canonical Ltd.
2759+ *
2760+ * This program is free software; you can redistribute it and/or modify
2761+ * it under the terms of the GNU Lesser General Public License as published by
2762+ * the Free Software Foundation; version 3.
2763+ *
2764+ * This program is distributed in the hope that it will be useful,
2765+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2766+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2767+ * GNU Lesser General Public License for more details.
2768+ *
2769+ * You should have received a copy of the GNU Lesser General Public License
2770+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2771+ *
2772+ */
2773+
2774+import QtQuick 2.4
2775+import Ubuntu.Components 1.3
2776+
2777+Item {
2778+ id: holder
2779+ width: units.gu(40)
2780+ height: units.gu(71)
2781+
2782+ BottomEdge {
2783+ hint.text: "Test"
2784+ objectName: "testItem"
2785+
2786+ BottomEdgeRegion { objectName: "customRegion" }
2787+ }
2788+}
2789+
2790
2791=== added file 'tests/unit_x11/tst_bottomedge/AddCustomRegionUsingRegionsProperty.qml'
2792--- tests/unit_x11/tst_bottomedge/AddCustomRegionUsingRegionsProperty.qml 1970-01-01 00:00:00 +0000
2793+++ tests/unit_x11/tst_bottomedge/AddCustomRegionUsingRegionsProperty.qml 2015-11-27 10:36:32 +0000
2794@@ -0,0 +1,33 @@
2795+/*
2796+ * Copyright 2015 Canonical Ltd.
2797+ *
2798+ * This program is free software; you can redistribute it and/or modify
2799+ * it under the terms of the GNU Lesser General Public License as published by
2800+ * the Free Software Foundation; version 3.
2801+ *
2802+ * This program is distributed in the hope that it will be useful,
2803+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2804+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2805+ * GNU Lesser General Public License for more details.
2806+ *
2807+ * You should have received a copy of the GNU Lesser General Public License
2808+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2809+ *
2810+ */
2811+
2812+import QtQuick 2.4
2813+import Ubuntu.Components 1.3
2814+
2815+Item {
2816+ id: holder
2817+ width: units.gu(40)
2818+ height: units.gu(71)
2819+
2820+ BottomEdge {
2821+ hint.text: "Test"
2822+ objectName: "testItem"
2823+
2824+ regions: BottomEdgeRegion { objectName: "customRegion" }
2825+ }
2826+}
2827+
2828
2829=== added file 'tests/unit_x11/tst_bottomedge/AlternateDefaultRegionContent.qml'
2830--- tests/unit_x11/tst_bottomedge/AlternateDefaultRegionContent.qml 1970-01-01 00:00:00 +0000
2831+++ tests/unit_x11/tst_bottomedge/AlternateDefaultRegionContent.qml 2015-11-27 10:36:32 +0000
2832@@ -0,0 +1,53 @@
2833+/*
2834+ * Copyright 2015 Canonical Ltd.
2835+ *
2836+ * This program is free software; you can redistribute it and/or modify
2837+ * it under the terms of the GNU Lesser General Public License as published by
2838+ * the Free Software Foundation; version 3.
2839+ *
2840+ * This program is distributed in the hope that it will be useful,
2841+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2842+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2843+ * GNU Lesser General Public License for more details.
2844+ *
2845+ * You should have received a copy of the GNU Lesser General Public License
2846+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2847+ *
2848+ */
2849+
2850+import QtQuick 2.4
2851+import Ubuntu.Components 1.3
2852+
2853+Item {
2854+ id: holder
2855+ width: units.gu(40)
2856+ height: units.gu(71)
2857+
2858+ BottomEdge {
2859+ id: bottomEdge
2860+ hint.text: "Test"
2861+ objectName: "testItem"
2862+ contentComponent: Rectangle {
2863+ objectName: "defaultContent"
2864+ width: holder.width
2865+ height: holder.height
2866+ color: UbuntuColors.green
2867+ }
2868+
2869+ Component {
2870+ id: altContent
2871+ Rectangle {
2872+ objectName: "regionContent"
2873+ width: holder.width - units.gu(20)
2874+ height: holder.height
2875+ color: UbuntuColors.red
2876+ }
2877+ }
2878+ }
2879+
2880+ Component.onCompleted: {
2881+ var defaultRegion = bottomEdge.regions[0];
2882+ defaultRegion.contentComponent = altContent;
2883+ }
2884+}
2885+
2886
2887=== added file 'tests/unit_x11/tst_bottomedge/AlternateRegionContent.qml'
2888--- tests/unit_x11/tst_bottomedge/AlternateRegionContent.qml 1970-01-01 00:00:00 +0000
2889+++ tests/unit_x11/tst_bottomedge/AlternateRegionContent.qml 2015-11-27 10:36:32 +0000
2890@@ -0,0 +1,47 @@
2891+/*
2892+ * Copyright 2015 Canonical Ltd.
2893+ *
2894+ * This program is free software; you can redistribute it and/or modify
2895+ * it under the terms of the GNU Lesser General Public License as published by
2896+ * the Free Software Foundation; version 3.
2897+ *
2898+ * This program is distributed in the hope that it will be useful,
2899+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2900+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2901+ * GNU Lesser General Public License for more details.
2902+ *
2903+ * You should have received a copy of the GNU Lesser General Public License
2904+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2905+ *
2906+ */
2907+
2908+import QtQuick 2.4
2909+import Ubuntu.Components 1.3
2910+
2911+Item {
2912+ id: holder
2913+ width: units.gu(40)
2914+ height: units.gu(71)
2915+
2916+ BottomEdge {
2917+ hint.text: "Test"
2918+ objectName: "testItem"
2919+ contentComponent: Rectangle {
2920+ objectName: "defaultContent"
2921+ width: holder.width
2922+ height: holder.height
2923+ color: UbuntuColors.green
2924+ }
2925+
2926+ regions: BottomEdgeRegion {
2927+ from: 0.2
2928+ contentComponent: Rectangle {
2929+ objectName: "regionContent"
2930+ width: holder.width - units.gu(20)
2931+ height: holder.height
2932+ color: UbuntuColors.red
2933+ }
2934+ }
2935+ }
2936+}
2937+
2938
2939=== added file 'tests/unit_x11/tst_bottomedge/AutoCollapseInPageHeader.qml'
2940--- tests/unit_x11/tst_bottomedge/AutoCollapseInPageHeader.qml 1970-01-01 00:00:00 +0000
2941+++ tests/unit_x11/tst_bottomedge/AutoCollapseInPageHeader.qml 2015-11-27 10:36:32 +0000
2942@@ -0,0 +1,45 @@
2943+/*
2944+ * Copyright 2015 Canonical Ltd.
2945+ *
2946+ * This program is free software; you can redistribute it and/or modify
2947+ * it under the terms of the GNU Lesser General Public License as published by
2948+ * the Free Software Foundation; version 3.
2949+ *
2950+ * This program is distributed in the hope that it will be useful,
2951+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2952+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2953+ * GNU Lesser General Public License for more details.
2954+ *
2955+ * You should have received a copy of the GNU Lesser General Public License
2956+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2957+ *
2958+ */
2959+
2960+import QtQuick 2.4
2961+import Ubuntu.Components 1.3
2962+
2963+MainView {
2964+ width: units.gu(40)
2965+ height: units.gu(71)
2966+
2967+ Page {
2968+ title: "Main page"
2969+
2970+ BottomEdge {
2971+ id: bottomEdge
2972+ objectName: "testItem"
2973+ hint.text: "Bottom edge"
2974+
2975+ contentComponent: Rectangle {
2976+ width: bottomEdge.width
2977+ height: bottomEdge.height
2978+ color: UbuntuColors.green
2979+ PageHeader {
2980+ title: "Bottom edge content"
2981+ objectName: "bottomEdgeHeader"
2982+ }
2983+ }
2984+ }
2985+ }
2986+}
2987+
2988
2989=== added file 'tests/unit_x11/tst_bottomedge/AutoCollapseInPageWithPageHeader.qml'
2990--- tests/unit_x11/tst_bottomedge/AutoCollapseInPageWithPageHeader.qml 1970-01-01 00:00:00 +0000
2991+++ tests/unit_x11/tst_bottomedge/AutoCollapseInPageWithPageHeader.qml 2015-11-27 10:36:32 +0000
2992@@ -0,0 +1,44 @@
2993+/*
2994+ * Copyright 2015 Canonical Ltd.
2995+ *
2996+ * This program is free software; you can redistribute it and/or modify
2997+ * it under the terms of the GNU Lesser General Public License as published by
2998+ * the Free Software Foundation; version 3.
2999+ *
3000+ * This program is distributed in the hope that it will be useful,
3001+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3002+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3003+ * GNU Lesser General Public License for more details.
3004+ *
3005+ * You should have received a copy of the GNU Lesser General Public License
3006+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3007+ *
3008+ */
3009+
3010+import QtQuick 2.4
3011+import Ubuntu.Components 1.3
3012+
3013+MainView {
3014+ width: units.gu(40)
3015+ height: units.gu(71)
3016+
3017+ Page {
3018+ title: "Main page"
3019+
3020+ BottomEdge {
3021+ id: bottomEdge
3022+ objectName: "testItem"
3023+ hint.text: "Bottom edge"
3024+
3025+ contentComponent: Page {
3026+ width: bottomEdge.width
3027+ height: bottomEdge.height
3028+ header: PageHeader {
3029+ title: "Bottom edge content"
3030+ objectName: "bottomEdgeHeader"
3031+ }
3032+ }
3033+ }
3034+ }
3035+}
3036+
3037
3038=== added file 'tests/unit_x11/tst_bottomedge/BottomEdgeInItem.qml'
3039--- tests/unit_x11/tst_bottomedge/BottomEdgeInItem.qml 1970-01-01 00:00:00 +0000
3040+++ tests/unit_x11/tst_bottomedge/BottomEdgeInItem.qml 2015-11-27 10:36:32 +0000
3041@@ -0,0 +1,36 @@
3042+/*
3043+ * Copyright 2015 Canonical Ltd.
3044+ *
3045+ * This program is free software; you can redistribute it and/or modify
3046+ * it under the terms of the GNU Lesser General Public License as published by
3047+ * the Free Software Foundation; version 3.
3048+ *
3049+ * This program is distributed in the hope that it will be useful,
3050+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3051+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3052+ * GNU Lesser General Public License for more details.
3053+ *
3054+ * You should have received a copy of the GNU Lesser General Public License
3055+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3056+ *
3057+ */
3058+
3059+import QtQuick 2.4
3060+import Ubuntu.Components 1.3
3061+
3062+Item {
3063+ id: holder
3064+ width: units.gu(40)
3065+ height: units.gu(71)
3066+
3067+ BottomEdge {
3068+ hint.text: "Test"
3069+ objectName: "testItem"
3070+ contentComponent: Rectangle {
3071+ width: holder.width
3072+ height: holder.height
3073+ color: UbuntuColors.green
3074+ }
3075+ }
3076+}
3077+
3078
3079=== added file 'tests/unit_x11/tst_bottomedge/ClearCustomRegions.qml'
3080--- tests/unit_x11/tst_bottomedge/ClearCustomRegions.qml 1970-01-01 00:00:00 +0000
3081+++ tests/unit_x11/tst_bottomedge/ClearCustomRegions.qml 2015-11-27 10:36:32 +0000
3082@@ -0,0 +1,42 @@
3083+/*
3084+ * Copyright 2015 Canonical Ltd.
3085+ *
3086+ * This program is free software; you can redistribute it and/or modify
3087+ * it under the terms of the GNU Lesser General Public License as published by
3088+ * the Free Software Foundation; version 3.
3089+ *
3090+ * This program is distributed in the hope that it will be useful,
3091+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3092+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3093+ * GNU Lesser General Public License for more details.
3094+ *
3095+ * You should have received a copy of the GNU Lesser General Public License
3096+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3097+ *
3098+ */
3099+
3100+import QtQuick 2.4
3101+import Ubuntu.Components 1.3
3102+
3103+Item {
3104+ id: holder
3105+ width: units.gu(40)
3106+ height: units.gu(71)
3107+
3108+ BottomEdge {
3109+ hint.text: "Test"
3110+ objectName: "testItem"
3111+
3112+ BottomEdgeRegion {
3113+ to: 0.5
3114+ }
3115+ BottomEdgeRegion {
3116+ from: 0.5
3117+ }
3118+
3119+ Component.onCompleted: {
3120+ regions = [];
3121+ }
3122+ }
3123+}
3124+
3125
3126=== added file 'tests/unit_x11/tst_bottomedge/Defaults.qml'
3127--- tests/unit_x11/tst_bottomedge/Defaults.qml 1970-01-01 00:00:00 +0000
3128+++ tests/unit_x11/tst_bottomedge/Defaults.qml 2015-11-27 10:36:32 +0000
3129@@ -0,0 +1,28 @@
3130+/*
3131+ * Copyright 2015 Canonical Ltd.
3132+ *
3133+ * This program is free software; you can redistribute it and/or modify
3134+ * it under the terms of the GNU Lesser General Public License as published by
3135+ * the Free Software Foundation; version 3.
3136+ *
3137+ * This program is distributed in the hope that it will be useful,
3138+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3139+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3140+ * GNU Lesser General Public License for more details.
3141+ *
3142+ * You should have received a copy of the GNU Lesser General Public License
3143+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3144+ *
3145+ */
3146+
3147+import QtQuick 2.4
3148+import Ubuntu.Components 1.3
3149+
3150+Item {
3151+ width: 100
3152+ height: 100
3153+ BottomEdge {
3154+ objectName: "testItem"
3155+ }
3156+}
3157+
3158
3159=== added file 'tests/unit_x11/tst_bottomedge/DifferentSizes.qml'
3160--- tests/unit_x11/tst_bottomedge/DifferentSizes.qml 1970-01-01 00:00:00 +0000
3161+++ tests/unit_x11/tst_bottomedge/DifferentSizes.qml 2015-11-27 10:36:32 +0000
3162@@ -0,0 +1,34 @@
3163+/*
3164+ * Copyright 2015 Canonical Ltd.
3165+ *
3166+ * This program is free software; you can redistribute it and/or modify
3167+ * it under the terms of the GNU Lesser General Public License as published by
3168+ * the Free Software Foundation; version 3.
3169+ *
3170+ * This program is distributed in the hope that it will be useful,
3171+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3172+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3173+ * GNU Lesser General Public License for more details.
3174+ *
3175+ * You should have received a copy of the GNU Lesser General Public License
3176+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3177+ *
3178+ */
3179+
3180+import QtQuick 2.4
3181+import Ubuntu.Components 1.3
3182+
3183+Item {
3184+ width: 100
3185+ height: 100
3186+
3187+ Item {
3188+ objectName: "oldParent"
3189+ width: 100
3190+ height: 50
3191+ BottomEdge {
3192+ objectName: "testItem"
3193+ }
3194+ }
3195+}
3196+
3197
3198=== added file 'tests/unit_x11/tst_bottomedge/LastItem.qml'
3199--- tests/unit_x11/tst_bottomedge/LastItem.qml 1970-01-01 00:00:00 +0000
3200+++ tests/unit_x11/tst_bottomedge/LastItem.qml 2015-11-27 10:36:32 +0000
3201@@ -0,0 +1,46 @@
3202+/*
3203+ * Copyright 2015 Canonical Ltd.
3204+ *
3205+ * This program is free software; you can redistribute it and/or modify
3206+ * it under the terms of the GNU Lesser General Public License as published by
3207+ * the Free Software Foundation; version 3.
3208+ *
3209+ * This program is distributed in the hope that it will be useful,
3210+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3211+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3212+ * GNU Lesser General Public License for more details.
3213+ *
3214+ * You should have received a copy of the GNU Lesser General Public License
3215+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3216+ *
3217+ */
3218+
3219+import QtQuick 2.4
3220+import Ubuntu.Components 1.3
3221+
3222+Item {
3223+ width: 100
3224+ height: 100
3225+
3226+ Item {
3227+ id: holder
3228+ width: 100
3229+ height: 50
3230+ BottomEdge {
3231+ objectName: "testItem"
3232+ contentComponent: Item {
3233+ width: holder.width
3234+ height: holder.height
3235+ }
3236+ }
3237+
3238+ Item {
3239+ anchors.fill: parent
3240+ }
3241+ }
3242+ Item {
3243+ objectName: "dynamicItem"
3244+ anchors.fill: parent
3245+ }
3246+}
3247+
3248
3249=== added file 'tests/unit_x11/tst_bottomedge/LeanActiveRegionChange.qml'
3250--- tests/unit_x11/tst_bottomedge/LeanActiveRegionChange.qml 1970-01-01 00:00:00 +0000
3251+++ tests/unit_x11/tst_bottomedge/LeanActiveRegionChange.qml 2015-11-27 10:36:32 +0000
3252@@ -0,0 +1,45 @@
3253+/*
3254+ * Copyright 2015 Canonical Ltd.
3255+ *
3256+ * This program is free software; you can redistribute it and/or modify
3257+ * it under the terms of the GNU Lesser General Public License as published by
3258+ * the Free Software Foundation; version 3.
3259+ *
3260+ * This program is distributed in the hope that it will be useful,
3261+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3262+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3263+ * GNU Lesser General Public License for more details.
3264+ *
3265+ * You should have received a copy of the GNU Lesser General Public License
3266+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3267+ *
3268+ */
3269+
3270+import QtQuick 2.4
3271+import Ubuntu.Components 1.3
3272+
3273+Item {
3274+ id: holder
3275+ width: units.gu(40)
3276+ height: units.gu(71)
3277+
3278+ BottomEdge {
3279+ id: bottomEdge
3280+ hint.text: "Test"
3281+ objectName: "testItem"
3282+ contentComponent: Rectangle {
3283+ width: bottomEdge.width
3284+ height: bottomEdge.height
3285+ color: UbuntuColors.lightGrey
3286+ }
3287+
3288+ BottomEdgeRegion {
3289+ from: 0.2
3290+ to: 0.5
3291+ }
3292+ BottomEdgeRegion {
3293+ from: 0.5
3294+ to: 0.7
3295+ }
3296+ }
3297+}
3298
3299=== added file 'tests/unit_x11/tst_bottomedge/OverlappingRegions.qml'
3300--- tests/unit_x11/tst_bottomedge/OverlappingRegions.qml 1970-01-01 00:00:00 +0000
3301+++ tests/unit_x11/tst_bottomedge/OverlappingRegions.qml 2015-11-27 10:36:32 +0000
3302@@ -0,0 +1,46 @@
3303+/*
3304+ * Copyright 2015 Canonical Ltd.
3305+ *
3306+ * This program is free software; you can redistribute it and/or modify
3307+ * it under the terms of the GNU Lesser General Public License as published by
3308+ * the Free Software Foundation; version 3.
3309+ *
3310+ * This program is distributed in the hope that it will be useful,
3311+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3312+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3313+ * GNU Lesser General Public License for more details.
3314+ *
3315+ * You should have received a copy of the GNU Lesser General Public License
3316+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3317+ *
3318+ */
3319+
3320+import QtQuick 2.4
3321+import Ubuntu.Components 1.3
3322+
3323+Item {
3324+ id: holder
3325+ width: units.gu(40)
3326+ height: units.gu(71)
3327+
3328+ BottomEdge {
3329+ hint.text: "Test"
3330+ objectName: "testItem"
3331+
3332+ BottomEdgeRegion {
3333+ from: 0.2
3334+ to: 0.5
3335+ }
3336+ BottomEdgeRegion {
3337+ from: 0.4
3338+ }
3339+ BottomEdgeRegion {
3340+ from: 0.4
3341+ to: 0.6
3342+ }
3343+ BottomEdgeRegion {
3344+ from: 0.6
3345+ }
3346+ }
3347+}
3348+
3349
3350=== added file 'tests/unit_x11/tst_bottomedge/ShorterBottomEdge.qml'
3351--- tests/unit_x11/tst_bottomedge/ShorterBottomEdge.qml 1970-01-01 00:00:00 +0000
3352+++ tests/unit_x11/tst_bottomedge/ShorterBottomEdge.qml 2015-11-27 10:36:32 +0000
3353@@ -0,0 +1,39 @@
3354+/*
3355+ * Copyright 2015 Canonical Ltd.
3356+ *
3357+ * This program is free software; you can redistribute it and/or modify
3358+ * it under the terms of the GNU Lesser General Public License as published by
3359+ * the Free Software Foundation; version 3.
3360+ *
3361+ * This program is distributed in the hope that it will be useful,
3362+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3363+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3364+ * GNU Lesser General Public License for more details.
3365+ *
3366+ * You should have received a copy of the GNU Lesser General Public License
3367+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3368+ *
3369+ */
3370+
3371+import QtQuick 2.4
3372+import Ubuntu.Components 1.3
3373+
3374+Item {
3375+ id: holder
3376+ width: units.gu(40)
3377+ height: units.gu(71)
3378+
3379+ BottomEdge {
3380+ id: bottomEdge
3381+ objectName: "testItem"
3382+ hint.text: "Test"
3383+ height: holder.height - units.gu(20)
3384+
3385+ contentComponent: Rectangle {
3386+ width: holder.width
3387+ height: bottomEdge.height
3388+ color: UbuntuColors.green
3389+ }
3390+ }
3391+}
3392+
3393
3394=== added file 'tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp'
3395--- tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp 1970-01-01 00:00:00 +0000
3396+++ tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp 2015-11-27 10:36:32 +0000
3397@@ -0,0 +1,856 @@
3398+/*
3399+ * Copyright 2015 Canonical Ltd.
3400+ *
3401+ * This program is free software; you can redistribute it and/or modify
3402+ * it under the terms of the GNU Lesser General Public License as published by
3403+ * the Free Software Foundation; version 3.
3404+ *
3405+ * This program is distributed in the hope that it will be useful,
3406+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3407+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3408+ * GNU Lesser General Public License for more details.
3409+ *
3410+ * You should have received a copy of the GNU Lesser General Public License
3411+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3412+ *
3413+ */
3414+
3415+#include <QtTest/QtTest>
3416+#include "ucbottomedge.h"
3417+#include "ucbottomedgeregion.h"
3418+#include "ucbottomedge_p.h"
3419+#include "ucbottomedgehint.h"
3420+#include "gestures/ucswipearea.h"
3421+#include "ucunits.h"
3422+#include "ucheader.h"
3423+#include "ucaction.h"
3424+#include "uctestcase.h"
3425+#include "uctestextras.h"
3426+#define private public
3427+#include "quickutils.h"
3428+#include "ucbottomedgestyle.h"
3429+#undef private
3430+
3431+#define QVERIFY_RETURN(statement, returnValue) \
3432+do {\
3433+ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\
3434+ return returnValue;\
3435+} while (0)
3436+
3437+class BottomEdgeTestCase : public UbuntuTestCase
3438+{
3439+ Q_OBJECT
3440+public:
3441+ BottomEdgeTestCase(const QString& file, ResizeMode resize = SizeViewToRootObject, bool assertOnFailure = true, QWindow* parent = 0)
3442+ : UbuntuTestCase(file, resize, assertOnFailure, parent)
3443+ {
3444+ // patch all BottomEdges' SwipeArea gesture recognition timer
3445+ QList<UCBottomEdge*> list = findChildren<UCBottomEdge*>();
3446+ for (int i = 0; i < list.size(); ++i) {
3447+ UCBottomEdge *bottomEdge = list[i];
3448+ UCSwipeArea *swipeArea = bottomEdge->hint()->swipeArea();
3449+ swipeArea->setImmediateRecognition(true);
3450+ }
3451+ }
3452+ ~BottomEdgeTestCase()
3453+ {
3454+ // add a small timeout after each run to have a proper cleanup
3455+ QTest::qWait(400);
3456+ }
3457+
3458+ UCBottomEdge *testItem(const QString &objectName = "testItem")
3459+ {
3460+ return findItem<UCBottomEdge*>(objectName);
3461+ }
3462+
3463+ typedef QList<UCBottomEdgeRegion*> RegionList;
3464+ RegionList *regions(const QString &testItem)
3465+ {
3466+ QQmlListProperty<UCBottomEdgeRegion> qmlRegions(this->testItem(testItem)->regions());
3467+ return reinterpret_cast<RegionList*>(qmlRegions.data);
3468+ }
3469+
3470+ UCBottomEdgeRegion *regionAt(const QString &testItem, int index)
3471+ {
3472+ QVERIFY_RETURN(regions(testItem), nullptr);
3473+ QVERIFY_RETURN(regions(testItem)->size() > index, nullptr);
3474+ return regions(testItem)->at(index);
3475+ }
3476+
3477+ bool hasContentAutoCollapse(const QString &testItem = "testItem")
3478+ {
3479+ UCBottomEdge *bottomEdge = this->testItem(testItem);
3480+ QQuickItem *contentItem = bottomEdge->contentItem();
3481+ if (!contentItem) {
3482+ return false;
3483+ }
3484+ // we do not have the PageHeader in cpp, therefore we need the same workaround we have in the code
3485+ UCHeader *header = contentItem->findChild<UCHeader*>();
3486+ if (!QuickUtils::inherits(header, "PageHeader")) {
3487+ return false;
3488+ }
3489+
3490+ QVariant list(header->property("navigationActions"));
3491+ QQmlListProperty<UCAction> actions = list.value< QQmlListProperty<UCAction> >();
3492+ QList<UCAction*> *navigationActions = reinterpret_cast<QList<UCAction*>*>(actions.data);
3493+ if (navigationActions->size() <= 0) {
3494+ return false;
3495+ }
3496+ return (qobject_cast<UCCollapseAction*>(navigationActions->at(0)) != Q_NULLPTR);
3497+ }
3498+};
3499+
3500+
3501+class tst_BottomEdge : public QObject
3502+{
3503+ Q_OBJECT
3504+private Q_SLOTS:
3505+
3506+ void initTestCase()
3507+ {
3508+ UCTestExtras::registerTouchDevice();
3509+ // make sure we disable the mouse
3510+ QuickUtils::instance().m_mouseAttached = false;
3511+ }
3512+
3513+ void test_defaults()
3514+ {
3515+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("Defaults.qml"));
3516+ QCOMPARE(test->testItem()->height(), test->rootObject()->height());
3517+ QCOMPARE(test->testItem()->dragProgress(), 0.0);
3518+ QCOMPARE(test->testItem()->dragDirection(), UCBottomEdge::Undefined);
3519+ QCOMPARE(test->testItem()->status(), UCBottomEdge::Hidden);
3520+ QCOMPARE(test->testItem()->contentUrl(), QUrl());
3521+ QVERIFY(!test->testItem()->contentComponent());
3522+ QVERIFY(!test->testItem()->contentItem());
3523+ QVERIFY(test->regions("testItem"));
3524+ QCOMPARE(test->regions("testItem")->size(), 1);
3525+ QCOMPARE(test->regionAt("testItem", 0)->objectName(), QString("default_BottomEdgeRegion"));
3526+ QCOMPARE(test->regionAt("testItem", 0)->m_from, 0.33);
3527+ QCOMPARE(test->regionAt("testItem", 0)->m_to, 1.0);
3528+ QVERIFY(!test->testItem()->activeRegion());
3529+ }
3530+
3531+ void test_height_moves_when_reparented()
3532+ {
3533+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("DifferentSizes.qml"));
3534+ QQuickItem *newParent = test->rootObject();
3535+ QQuickItem *oldParent = test->findItem<QQuickItem*>("oldParent");
3536+ UCBottomEdge *testItem = test->testItem();
3537+
3538+ QSignalSpy spy(testItem, SIGNAL(implicitHeightChanged()));
3539+ testItem->setParentItem(newParent);
3540+ UbuntuTestCase::waitForSignal(&spy);
3541+
3542+ // change the implicit height so we are sure we don't get the height change triggered
3543+ testItem->setImplicitHeight(0);
3544+ spy.clear();
3545+ oldParent->setHeight(20);
3546+ QEXPECT_FAIL(0, "no implicitHeight change is expected", Continue);
3547+ QVERIFY(spy.wait(400));
3548+ }
3549+
3550+ void test_panel_is_last_item_of_parent()
3551+ {
3552+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("LastItem.qml"));
3553+ QQuickItem *bottomEdgeParent = test->testItem()->parentItem();
3554+ UCBottomEdgePrivate *privateTestItem = UCBottomEdgePrivate::get(test->testItem());
3555+ QCOMPARE(bottomEdgeParent->childItems().last(), privateTestItem->styleItem);
3556+
3557+ QQuickItem *dynamicItem = test->findItem<QQuickItem*>("dynamicItem");
3558+ dynamicItem->setParentItem(bottomEdgeParent);
3559+ // still the last one
3560+ QCOMPARE(bottomEdgeParent->childItems().last(), privateTestItem->styleItem);
3561+ }
3562+
3563+ void test_commit_when_clicked()
3564+ {
3565+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3566+ test->testItem()->hint()->setStatus(UCBottomEdgeHint::Locked);
3567+ UCBottomEdgeHint *hint = test->testItem()->hint();
3568+ QTest::mouseClick(test->testItem()->hint()->window(), Qt::LeftButton, 0, UbuntuTestCase::centerOf(hint, true).toPoint());
3569+ QTRY_COMPARE_WITH_TIMEOUT(test->testItem()->status(), UCBottomEdge::Committed, 1000);
3570+ }
3571+
3572+ void test_commit_when_touch_clicked()
3573+ {
3574+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3575+ UCBottomEdgeHint *hint = test->testItem()->hint();
3576+ // swipe a bit to reveal
3577+
3578+ UCTestExtras::touchDrag(0, hint, QPoint(hint->width() / 2, hint->height()), QPoint(0, -hint->height()));
3579+ QTRY_COMPARE_WITH_TIMEOUT(hint->status(), UCBottomEdgeHint::Active, 1000);
3580+
3581+ UCTestExtras::touchClick(0, hint, UbuntuTestCase::centerOf(hint).toPoint());
3582+ QTRY_COMPARE_WITH_TIMEOUT(test->testItem()->status(), UCBottomEdge::Committed, 1000);
3583+ }
3584+
3585+ void test_revealed_when_hint_threshold_passed_data()
3586+ {
3587+ QTest::addColumn<bool>("withMouse");
3588+ QTest::addColumn<bool>("lockHint");
3589+ QTest::addColumn<bool>("xfail");
3590+
3591+ QTest::newRow("drag with mouse, unlocked hint") << true << false << true;
3592+ QTest::newRow("drag with mouse, locked hint") << true << true << false;
3593+ QTest::newRow("drag with touch, unlocked hint") << false << false << false;
3594+ QTest::newRow("drag with touch, locked hint") << false << true << false;
3595+ }
3596+ void test_revealed_when_hint_threshold_passed()
3597+ {
3598+ QFETCH(bool, withMouse);
3599+ QFETCH(bool, lockHint);
3600+ QFETCH(bool, xfail);
3601+
3602+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3603+ UCBottomEdge *bottomEdge = test->testItem();
3604+ UCBottomEdgeStyle *style = UCBottomEdgePrivate::get(bottomEdge)->bottomPanel;
3605+ QSignalSpy spy(bottomEdge, SIGNAL(statusChanged(UCBottomEdge::Status)));
3606+
3607+ // swipe till we reveal it
3608+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 1);
3609+ QPoint delta(0, -(2 * style->m_revealThreshold));
3610+ if (lockHint) {
3611+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3612+ }
3613+ if (withMouse) {
3614+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton);
3615+ } else {
3616+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 8);
3617+ }
3618+ if (xfail) {
3619+ QEXPECT_FAIL(0, "failure expected", Continue);
3620+ }
3621+ QVERIFY(spy.wait(500));
3622+ if (xfail) {
3623+ QCOMPARE(spy.count(), 0);
3624+ } else {
3625+ // there must be two state changes here, one Hidden->Revealed, and one Revealed->Hidden
3626+ QCOMPARE(spy.count(), 2);
3627+ QList<QVariant> state1 = spy.at(0);
3628+ QCOMPARE(state1[0].value<int>(), (int)UCBottomEdge::Revealed);
3629+ QList<QVariant> state2 = spy.at(1);
3630+ QCOMPARE(state2[0].value<int>(), (int)UCBottomEdge::Hidden);
3631+ }
3632+ }
3633+
3634+ void test_commit_when_onethird_passed_data()
3635+ {
3636+ QTest::addColumn<bool>("withMouse");
3637+
3638+ QTest::newRow("with mouse") << true;
3639+ QTest::newRow("with touch") << false;
3640+ }
3641+ void test_commit_when_onethird_passed()
3642+ {
3643+ QFETCH(bool, withMouse);
3644+
3645+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3646+ UCBottomEdge *bottomEdge = test->testItem();
3647+
3648+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 1);
3649+ // add some extra space for the touch
3650+ QPoint delta(0, -(bottomEdge->height() / 3 + UCUnits::instance().gu(6)));
3651+
3652+ if (withMouse) {
3653+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3654+ }
3655+ // we need to do the swipe in more steps
3656+ if (withMouse) {
3657+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton, 0, 20);
3658+ } else {
3659+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 20);
3660+ }
3661+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Committed, 1000);
3662+ }
3663+
3664+ void test_collapse_before_onethird_data()
3665+ {
3666+ QTest::addColumn<bool>("withMouse");
3667+
3668+ QTest::newRow("with mouse") << true;
3669+ QTest::newRow("with touch") << false;
3670+ }
3671+ void test_collapse_before_onethird()
3672+ {
3673+ QFETCH(bool, withMouse);
3674+
3675+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3676+ UCBottomEdge *bottomEdge = test->testItem();
3677+
3678+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 1);
3679+ QPoint delta(0, -(bottomEdge->height() / 4));
3680+
3681+ if (withMouse) {
3682+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3683+ }
3684+ // we need to do the swipe in more steps
3685+ if (withMouse) {
3686+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton, 0, 20);
3687+ } else {
3688+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 20);
3689+ }
3690+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Hidden, 1000);
3691+ }
3692+
3693+ void test_collapse_when_dragged_downwards_data()
3694+ {
3695+ // when onethird not passed
3696+ // when onethird was passed
3697+ QTest::addColumn<bool>("withMouse");
3698+ QTest::addColumn< QList<QPoint> >("moves");
3699+
3700+ QList<QPoint> shortPath, longPath;
3701+ // upwards
3702+ for (int i = 0; i < 10; i++) {
3703+ shortPath << QPointF(0, -UCUnits::instance().gu(3)).toPoint();
3704+ longPath << QPointF(0, -UCUnits::instance().gu(7)).toPoint();
3705+ }
3706+ // downwards
3707+ for (int i = 0; i < 5; i++) {
3708+ shortPath << QPointF(0, UCUnits::instance().gu(2)).toPoint();
3709+ longPath << QPointF(0, UCUnits::instance().gu(2)).toPoint();
3710+ }
3711+
3712+ QTest::newRow("with mouse, onethird not passed")
3713+ << true << shortPath;
3714+ QTest::newRow("with touch, onethird not passed")
3715+ << false << shortPath;
3716+ QTest::newRow("with mouse, onethird passed")
3717+ << true << longPath;
3718+ QTest::newRow("with touch, onethird passed")
3719+ << false << longPath;
3720+ }
3721+ void test_collapse_when_dragged_downwards()
3722+ {
3723+ QFETCH(bool, withMouse);
3724+ QFETCH(QList<QPoint>, moves);
3725+
3726+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3727+ UCBottomEdge *bottomEdge = test->testItem();
3728+
3729+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 1);
3730+ moves.prepend(from);
3731+
3732+ if (withMouse) {
3733+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3734+ }
3735+ if (withMouse) {
3736+ UCTestExtras::mouseDragWithPoints(bottomEdge, moves, Qt::LeftButton);
3737+ } else {
3738+ UCTestExtras::touchDragWithPoints(0, bottomEdge, moves);
3739+ }
3740+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Hidden, 1000);
3741+ }
3742+
3743+ void test_height_less_than_parent()
3744+ {
3745+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("ShorterBottomEdge.qml"));
3746+ UCBottomEdge *bottomEdge = test->testItem();
3747+ UCBottomEdgeStyle *style = UCBottomEdgePrivate::get(bottomEdge)->bottomPanel;
3748+ bottomEdge->commit();
3749+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Committed, 1000);
3750+ QCOMPARE(style->m_panel->y(), bottomEdge->y());
3751+ }
3752+
3753+ void test_do_not_overshoot_data()
3754+ {
3755+ QTest::addColumn<bool>("withMouse");
3756+
3757+ QTest::newRow("with touch") << false;
3758+ QTest::newRow("with mouse") << true;
3759+ }
3760+ void test_do_not_overshoot()
3761+ {
3762+ QFETCH(bool, withMouse);
3763+
3764+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("ShorterBottomEdge.qml"));
3765+ UCBottomEdge *bottomEdge = test->testItem();
3766+ UCBottomEdgeStyle *style = UCBottomEdgePrivate::get(bottomEdge)->bottomPanel;
3767+
3768+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 1);
3769+ QPoint to = from + QPoint(0, -(bottomEdge->parentItem()->height() - 1));
3770+
3771+ if (withMouse) {
3772+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3773+ from = bottomEdge->mapToScene(from).toPoint();
3774+ to = bottomEdge->mapToScene(to).toPoint();
3775+ QTest::mousePress(bottomEdge->window(), Qt::LeftButton, 0, from, 20);
3776+ QPoint movePos(from);
3777+ while (movePos.y() > to.y()) {
3778+ QTest::mouseMove(bottomEdge->window(), movePos, 20);
3779+ movePos += QPoint(0, -10);
3780+ QVERIFY(style->m_panel->y() >= bottomEdge->y());
3781+ }
3782+ QTest::mouseRelease(bottomEdge->window(),Qt::LeftButton, 0, movePos, 20);
3783+ } else {
3784+ UCTestExtras::touchPress(0, bottomEdge, from);
3785+ QPoint movePos(from);
3786+ while (movePos.y() > to.y()) {
3787+ QTest::qWait(20);
3788+ UCTestExtras::touchMove(0, bottomEdge, movePos);
3789+ movePos += QPoint(0, -10);
3790+ QVERIFY(style->m_panel->y() >= bottomEdge->y());
3791+ }
3792+ QTest::qWait(20);
3793+ UCTestExtras::touchRelease(0, bottomEdge, movePos);
3794+ }
3795+ }
3796+
3797+ void test_commitStarted_commitCompleted_emitted()
3798+ {
3799+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3800+ UCBottomEdge *bottomEdge = test->testItem();
3801+
3802+ QSignalSpy commitStartedSpy(bottomEdge, SIGNAL(commitStarted()));
3803+ QSignalSpy commitCompletedSpy(bottomEdge, SIGNAL(commitCompleted()));
3804+ bottomEdge->commit();
3805+ QTRY_COMPARE_WITH_TIMEOUT(commitStartedSpy.count(), 1, 1000);
3806+ QTRY_COMPARE_WITH_TIMEOUT(commitCompletedSpy.count(), 1, 1000);
3807+ }
3808+
3809+ void test_collapseStarted_collapseCompleted_emitted()
3810+ {
3811+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3812+ UCBottomEdge *bottomEdge = test->testItem();
3813+ bottomEdge->commit();
3814+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Committed, 1000);
3815+ // wait few milliseconds before we initiate collapse
3816+ QTest::qWait(200);
3817+
3818+ QSignalSpy collapseStartedSpy(bottomEdge, SIGNAL(collapseStarted()));
3819+ QSignalSpy collapseCompletedSpy(bottomEdge, SIGNAL(collapseCompleted()));
3820+ bottomEdge->collapse();
3821+ QTRY_COMPARE_WITH_TIMEOUT(collapseStartedSpy.count(), 1, 1000);
3822+ QTRY_COMPARE_WITH_TIMEOUT(collapseCompletedSpy.count(), 1, 1000);
3823+ }
3824+
3825+ void test_collapse_during_commit()
3826+ {
3827+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3828+ UCBottomEdge *bottomEdge = test->testItem();
3829+
3830+ QSignalSpy commitStartedSpy(bottomEdge, SIGNAL(commitStarted()));
3831+ QSignalSpy commitCompletedSpy(bottomEdge, SIGNAL(commitCompleted()));
3832+ QSignalSpy collapseStartedSpy(bottomEdge, SIGNAL(collapseStarted()));
3833+ QSignalSpy collapseCompletedSpy(bottomEdge, SIGNAL(collapseCompleted()));
3834+
3835+ connect(bottomEdge, SIGNAL(commitStarted()), bottomEdge, SLOT(collapse()), Qt::QueuedConnection);
3836+
3837+ bottomEdge->commit();
3838+
3839+ QTRY_COMPARE_WITH_TIMEOUT(commitStartedSpy.count(), 1, 1000);
3840+ QTRY_COMPARE_WITH_TIMEOUT(commitCompletedSpy.count(), 0, 1000);
3841+ QTRY_COMPARE_WITH_TIMEOUT(collapseStartedSpy.count(), 1, 1000);
3842+ QTRY_COMPARE_WITH_TIMEOUT(collapseCompletedSpy.count(), 1, 1000);
3843+ }
3844+
3845+ void test_commit_during_collapse()
3846+ {
3847+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3848+ UCBottomEdge *bottomEdge = test->testItem();
3849+ bottomEdge->commit();
3850+
3851+ QSignalSpy commitStartedSpy(bottomEdge, SIGNAL(commitStarted()));
3852+ QSignalSpy commitCompletedSpy(bottomEdge, SIGNAL(commitCompleted()));
3853+ QSignalSpy collapseStartedSpy(bottomEdge, SIGNAL(collapseStarted()));
3854+ QSignalSpy collapseCompletedSpy(bottomEdge, SIGNAL(collapseCompleted()));
3855+
3856+ connect(bottomEdge, SIGNAL(collapseStarted()), bottomEdge, SLOT(commit()), Qt::QueuedConnection);
3857+
3858+ bottomEdge->collapse();
3859+ QTRY_COMPARE_WITH_TIMEOUT(collapseStartedSpy.count(), 1, 1000);
3860+ QTRY_COMPARE_WITH_TIMEOUT(collapseCompletedSpy.count(), 0, 1000);
3861+ QTRY_COMPARE_WITH_TIMEOUT(commitStartedSpy.count(), 1, 1000);
3862+ QTRY_COMPARE_WITH_TIMEOUT(commitCompletedSpy.count(), 1, 1000);
3863+ }
3864+
3865+ // region tests
3866+
3867+ void test_region_operations_data()
3868+ {
3869+ QTest::addColumn<QString>("document");
3870+ QTest::addColumn<QString>("warning");
3871+ QTest::addColumn<qreal>("xFrom");
3872+ QTest::addColumn<qreal>("xTo");
3873+ QTest::addColumn<QString>("xName");
3874+
3875+ QTest::newRow("add through regions property")
3876+ << "AddCustomRegionUsingRegionsProperty.qml"
3877+ << QString()
3878+ << 0.0 << 1.0 << QString("customRegion");
3879+ QTest::newRow("add through data property")
3880+ << "AddCustomRegionUsingDataProperty.qml"
3881+ << QString()
3882+ << 0.0 << 1.0 << QString("customRegion");
3883+ QTest::newRow("add through Component.onCompleted")
3884+ << "AddCustomRegionOnCompleted.qml"
3885+ << QString()
3886+ << 0.0 << 1.0 << QString("customRegion");
3887+ QTest::newRow("add owned by other BottomEdge")
3888+ << "AddCustomRegionOwnedByOtherBottomEdge.qml"
3889+ << "QML BottomEdge: Cannot reuse region owned by other BottomEdge components"
3890+ // we should have the default region still
3891+ << 0.33 << 1.0 << "default_BottomEdgeRegion";
3892+ QTest::newRow("clear")
3893+ << "ClearCustomRegions.qml"
3894+ << QString()
3895+ // we should have the default region back
3896+ << 0.33 << 1.0 << "default_BottomEdgeRegion";
3897+ }
3898+
3899+ void test_region_operations()
3900+ {
3901+ QFETCH(QString, document);
3902+ QFETCH(QString, warning);
3903+ QFETCH(qreal, xFrom);
3904+ QFETCH(qreal, xTo);
3905+ QFETCH(QString, xName);
3906+
3907+ if (!warning.isEmpty()) {
3908+ UbuntuTestCase::ignoreWarning(document, 26, 5, warning, 1);
3909+ }
3910+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase(document));
3911+ UCBottomEdge *bottomEdge = test->testItem();
3912+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
3913+
3914+ // the regions has the custom one
3915+ QCOMPARE(privateBottomEdge->regions.size(), 1);
3916+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
3917+ QVERIFY(region);
3918+ QCOMPARE(region->m_from, xFrom);
3919+ QCOMPARE(region->m_to, xTo);
3920+ QCOMPARE(region->objectName(), xName);
3921+ }
3922+
3923+ void test_active_region_changes()
3924+ {
3925+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("LeanActiveRegionChange.qml"));
3926+ UCBottomEdge *bottomEdge = test->testItem();
3927+
3928+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
3929+ QPoint to = from + QPoint(0, -(bottomEdge->parentItem()->height() - 1));
3930+ QSignalSpy spy(bottomEdge, SIGNAL(activeRegionChanged(UCBottomEdgeRegion*)));
3931+
3932+ UCTestExtras::touchPress(0, bottomEdge, from);
3933+ QPoint movePos(from);
3934+ while (movePos.y() > to.y()) {
3935+ QTest::qWait(20);
3936+ UCTestExtras::touchMove(0, bottomEdge, movePos);
3937+ movePos += QPoint(0, -10);
3938+ }
3939+ QTest::qWait(20);
3940+ UCTestExtras::touchRelease(0, bottomEdge, movePos);
3941+ // we should have had 3 active region changes by now
3942+ // null -> region #0 -> region #1 -> null
3943+ QCOMPARE(spy.count(), 3);
3944+ }
3945+
3946+ void test_region_signals_emitted_data()
3947+ {
3948+ QTest::addColumn<bool>("withMouse");
3949+
3950+ QTest::newRow("with mouse") << true;
3951+ QTest::newRow("with touch") << false;
3952+ }
3953+ void test_region_signals_emitted()
3954+ {
3955+ QFETCH(bool, withMouse);
3956+
3957+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3958+ UCBottomEdge *bottomEdge = test->testItem();
3959+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
3960+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
3961+
3962+ // change the region so we can get the signals while dragged
3963+ region->m_from = 0.1;
3964+ region->m_to = 0.2;
3965+
3966+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
3967+ QPoint delta(0, -(bottomEdge->height() / 3 + UCUnits::instance().gu(6)));
3968+ QSignalSpy entered(region, SIGNAL(entered()));
3969+ QSignalSpy exited(region, SIGNAL(exited()));
3970+
3971+ if (withMouse) {
3972+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
3973+ }
3974+ if (withMouse) {
3975+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton, 0);
3976+ } else {
3977+ UCTestExtras::touchDrag(0, bottomEdge, from, delta);
3978+ }
3979+ QTRY_COMPARE_WITH_TIMEOUT(entered.count(), 1, 500);
3980+ QTRY_COMPARE_WITH_TIMEOUT(exited.count(), 1, 500);
3981+ }
3982+
3983+ void test_region_dragEnded_emitted_data()
3984+ {
3985+ QTest::addColumn<bool>("withMouse");
3986+
3987+ QTest::newRow("with mouse") << true;
3988+ QTest::newRow("with touch") << false;
3989+ }
3990+ void test_region_dragEnded_emitted()
3991+ {
3992+ QFETCH(bool, withMouse);
3993+
3994+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("BottomEdgeInItem.qml"));
3995+ UCBottomEdge *bottomEdge = test->testItem();
3996+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
3997+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
3998+
3999+ QSignalSpy dragEnded(region, SIGNAL(dragEnded()));
4000+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4001+ QPoint delta(0, -(bottomEdge->height() / 2.0f));
4002+ if (withMouse) {
4003+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
4004+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton, 0);
4005+ } else {
4006+ UCTestExtras::touchDrag(0, bottomEdge, from, delta);
4007+ }
4008+ UbuntuTestCase::waitForSignal(&dragEnded);
4009+ }
4010+
4011+ void test_alternative_content_for_default_commit_region()
4012+ {
4013+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("AlternateDefaultRegionContent.qml"));
4014+ UCBottomEdge *bottomEdge = test->testItem();
4015+
4016+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4017+ QPoint delta(0, -(bottomEdge->height() / 2));
4018+
4019+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 20);
4020+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Committed, 1000);
4021+ QCOMPARE(bottomEdge->contentItem()->objectName(), QString("regionContent"));
4022+ }
4023+
4024+ void test_end_drag_in_region_commits_to_the_region_data()
4025+ {
4026+ QTest::addColumn<bool>("withMouse");
4027+
4028+ QTest::newRow("with mouse") << true;
4029+ QTest::newRow("with touch") << false;
4030+ }
4031+ void test_end_drag_in_region_commits_to_the_region()
4032+ {
4033+ QFETCH(bool, withMouse);
4034+
4035+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("AlternateDefaultRegionContent.qml"));
4036+ UCBottomEdge *bottomEdge = test->testItem();
4037+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
4038+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
4039+ UCBottomEdgeStyle *style = UCBottomEdgePrivate::get(bottomEdge)->bottomPanel;
4040+
4041+ // alter default region for testing
4042+ region->m_from = 0.1;
4043+ region->m_to = 0.8;
4044+
4045+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4046+ QPoint to = from + QPoint(0, -(bottomEdge->parentItem()->height() - 1));
4047+
4048+ if (withMouse) {
4049+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
4050+ from = bottomEdge->mapToScene(from).toPoint();
4051+ to = bottomEdge->mapToScene(to).toPoint();
4052+ QTest::mousePress(bottomEdge->window(), Qt::LeftButton, 0, from, 20);
4053+ QPoint movePos(from);
4054+ while (movePos.y() > to.y() && !bottomEdge->activeRegion()) {
4055+ QTest::mouseMove(bottomEdge->window(), movePos, 20);
4056+ movePos += QPoint(0, -10);
4057+ }
4058+ QTest::mouseRelease(bottomEdge->window(),Qt::LeftButton, 0, movePos, 20);
4059+ } else {
4060+ UCTestExtras::touchPress(0, bottomEdge, from);
4061+ QPoint movePos(from);
4062+ while (movePos.y() > to.y() && !bottomEdge->activeRegion()) {
4063+ QTest::qWait(20);
4064+ UCTestExtras::touchMove(0, bottomEdge, movePos);
4065+ movePos += QPoint(0, -10);
4066+ }
4067+ QTest::qWait(20);
4068+ UCTestExtras::touchRelease(0, bottomEdge, movePos);
4069+ }
4070+
4071+ QVERIFY(bottomEdge->activeRegion());
4072+ // the top of the committed content should not be the top of the bottom edge
4073+ QVERIFY(style->m_panel->y() > bottomEdge->y());
4074+ QCOMPARE(bottomEdge->status(), UCBottomEdge::Revealed);
4075+ }
4076+
4077+ void test_drag_ends_in_uncovered_region_collapses_data()
4078+ {
4079+ QTest::addColumn<bool>("withMouse");
4080+
4081+ QTest::newRow("with mouse") << true;
4082+ QTest::newRow("with touch") << false;
4083+ }
4084+ void test_drag_ends_in_uncovered_region_collapses()
4085+ {
4086+ QFETCH(bool, withMouse);
4087+
4088+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("AlternateDefaultRegionContent.qml"));
4089+ UCBottomEdge *bottomEdge = test->testItem();
4090+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
4091+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
4092+
4093+ // alter region values to adjust to the test
4094+ region->m_from = 0.1;
4095+ region->m_to = 0.5;
4096+
4097+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4098+ QPoint to = from + QPoint(0, -(bottomEdge->parentItem()->height() - 1));
4099+ // let us know when we are out of the region
4100+ QSignalSpy exitRegion(region, SIGNAL(exited()));
4101+
4102+ if (withMouse) {
4103+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
4104+ from = bottomEdge->mapToScene(from).toPoint();
4105+ to = bottomEdge->mapToScene(to).toPoint();
4106+ QTest::mousePress(bottomEdge->window(), Qt::LeftButton, 0, from, 20);
4107+ QPoint movePos(from);
4108+ while (movePos.y() > to.y() && !exitRegion.count()) {
4109+ QTest::mouseMove(bottomEdge->window(), movePos, 20);
4110+ movePos += QPoint(0, -10);
4111+ }
4112+ QTest::mouseRelease(bottomEdge->window(),Qt::LeftButton, 0, movePos, 20);
4113+ } else {
4114+ UCTestExtras::touchPress(0, bottomEdge, from);
4115+ QPoint movePos(from);
4116+ while (movePos.y() > to.y() && !exitRegion.count()) {
4117+ QTest::qWait(20);
4118+ UCTestExtras::touchMove(0, bottomEdge, movePos);
4119+ movePos += QPoint(0, -10);
4120+ }
4121+ QTest::qWait(20);
4122+ UCTestExtras::touchRelease(0, bottomEdge, movePos);
4123+ }
4124+
4125+ QVERIFY(!bottomEdge->activeRegion());
4126+ // we should be collapsing!
4127+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Hidden, 1000);
4128+ }
4129+
4130+ void test_commit_region_content_data()
4131+ {
4132+ QTest::addColumn<bool>("withMouse");
4133+
4134+ QTest::newRow("with mouse") << true;
4135+ QTest::newRow("with touch") << false;
4136+ }
4137+ void test_commit_region_content()
4138+ {
4139+ QFETCH(bool, withMouse);
4140+
4141+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("AlternateRegionContent.qml"));
4142+ UCBottomEdge *bottomEdge = test->testItem();
4143+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
4144+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
4145+
4146+ // alter region for testing
4147+ region->m_from = 0.1;
4148+ region->m_to = 0.8;
4149+ // and connect commit to dragEnded
4150+ connect(region, &UCBottomEdgeRegion::dragEnded, bottomEdge, &UCBottomEdge::commit);
4151+
4152+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4153+ QPoint to = from + QPoint(0, -(bottomEdge->parentItem()->height() - 1));
4154+
4155+ if (withMouse) {
4156+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
4157+ from = bottomEdge->mapToScene(from).toPoint();
4158+ to = bottomEdge->mapToScene(to).toPoint();
4159+ QTest::mousePress(bottomEdge->window(), Qt::LeftButton, 0, from, 20);
4160+ QPoint movePos(from);
4161+ while (movePos.y() > to.y() && !bottomEdge->activeRegion()) {
4162+ QTest::mouseMove(bottomEdge->window(), movePos, 20);
4163+ movePos += QPoint(0, -10);
4164+ }
4165+ QTest::mouseRelease(bottomEdge->window(),Qt::LeftButton, 0, movePos, 20);
4166+ } else {
4167+ UCTestExtras::touchPress(0, bottomEdge, from);
4168+ QPoint movePos(from);
4169+ while (movePos.y() > to.y() && !bottomEdge->activeRegion()) {
4170+ QTest::qWait(20);
4171+ UCTestExtras::touchMove(0, bottomEdge, movePos);
4172+ movePos += QPoint(0, -10);
4173+ }
4174+ QTest::qWait(20);
4175+ UCTestExtras::touchRelease(0, bottomEdge, movePos);
4176+ }
4177+ QTRY_COMPARE_WITH_TIMEOUT(bottomEdge->status(), UCBottomEdge::Committed, 1000);
4178+ QCOMPARE(bottomEdge->contentItem()->objectName(), QString("regionContent"));
4179+ }
4180+
4181+ void test_overlapping_regions()
4182+ {
4183+ QString document("OverlappingRegions.qml");
4184+ UbuntuTestCase::ignoreWarning(document, 34, 9, "QML BottomEdgeRegion: Region intersects the one from index 0 having from: 0.2 and to: 0.5", 1);
4185+ UbuntuTestCase::ignoreWarning(document, 37, 9, "QML BottomEdgeRegion: Region intersects the one from index 0 having from: 0.2 and to: 0.5", 1);
4186+ UbuntuTestCase::ignoreWarning(document, 37, 9, "QML BottomEdgeRegion: Region at index 1 contains this region. This region will never activate.", 1);
4187+ UbuntuTestCase::ignoreWarning(document, 41, 9, "QML BottomEdgeRegion: Region at index 1 contains this region. This region will never activate.", 1);
4188+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase(document));
4189+ }
4190+
4191+ void test_region_does_not_activate_when_from_greater_than_to_data()
4192+ {
4193+ QTest::addColumn<bool>("withMouse");
4194+
4195+ QTest::newRow("with mouse") << true;
4196+ QTest::newRow("with touch") << false;
4197+ }
4198+ void test_region_does_not_activate_when_from_greater_than_to()
4199+ {
4200+ QFETCH(bool, withMouse);
4201+
4202+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("AlternateRegionContent.qml"));
4203+ UCBottomEdge *bottomEdge = test->testItem();
4204+ UCBottomEdgePrivate *privateBottomEdge = UCBottomEdgePrivate::get(bottomEdge);
4205+ UCBottomEdgeRegion *region = privateBottomEdge->regions[0];
4206+
4207+ // adjust region data for the test
4208+ region->m_from = 0.4;
4209+ region->m_to = 0.2;
4210+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4211+ QPoint delta(0, -(bottomEdge->height() / 2.0f));
4212+ QSignalSpy activeRegion(bottomEdge, SIGNAL(activeRegionChanged(UCBottomEdgeRegion*)));
4213+ if (withMouse) {
4214+ bottomEdge->hint()->setStatus(UCBottomEdgeHint::Locked);
4215+ UCTestExtras::mouseDrag(bottomEdge, from, delta, Qt::LeftButton, 0, 10);
4216+ } else {
4217+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 10);
4218+ }
4219+ QEXPECT_FAIL(0, "region should not activate", Continue);
4220+ QVERIFY(activeRegion.wait(400));
4221+ }
4222+
4223+ void test_autocollapse_navigation_action_on_commit_completed_data()
4224+ {
4225+ QTest::addColumn<QString>("document");
4226+
4227+ QTest::newRow("content has PageHeader") << "AutoCollapseInPageHeader.qml";
4228+ QTest::newRow("content is Page with PageHeader") << "AutoCollapseInPageWithPageHeader.qml";
4229+ }
4230+ void test_autocollapse_navigation_action_on_commit_completed()
4231+ {
4232+ QFETCH(QString, document);
4233+
4234+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase(document));
4235+ UCBottomEdge *bottomEdge = test->testItem();
4236+
4237+ BottomEdgeTestCase *testCase = test.data();
4238+ connect(bottomEdge, &UCBottomEdge::contentItemChanged, [=]() {
4239+ QVERIFY(!testCase->hasContentAutoCollapse());
4240+ });
4241+ connect(bottomEdge, &UCBottomEdge::commitCompleted, [=]() {
4242+ QVERIFY(testCase->hasContentAutoCollapse());
4243+ });
4244+ // drag slowly
4245+ QPoint from(bottomEdge->width() / 2.0f, bottomEdge->height() - 5);
4246+ QPoint delta(0, -bottomEdge->height());
4247+ UCTestExtras::touchDrag(0, bottomEdge, from, delta, 20);
4248+ }
4249+};
4250+
4251+QTEST_MAIN(tst_BottomEdge)
4252+
4253+#include "tst_bottomedge.moc"
4254
4255=== added file 'tests/unit_x11/tst_bottomedge/tst_bottomedge.pro'
4256--- tests/unit_x11/tst_bottomedge/tst_bottomedge.pro 1970-01-01 00:00:00 +0000
4257+++ tests/unit_x11/tst_bottomedge/tst_bottomedge.pro 2015-11-27 10:36:32 +0000
4258@@ -0,0 +1,23 @@
4259+include(../test-include.pri)
4260+QT += core-private qml-private quick-private gui-private UbuntuGestures
4261+
4262+SOURCES += \
4263+ tst_bottomedge.cpp
4264+
4265+DISTFILES += \
4266+ Defaults.qml \
4267+ DifferentSizes.qml \
4268+ LastItem.qml \
4269+ BottomEdgeInItem.qml \
4270+ ShorterBottomEdge.qml \
4271+ AlternateRegionContent.qml \
4272+ AddCustomRegionUsingRegionsProperty.qml \
4273+ AddCustomRegionUsingDataProperty.qml \
4274+ AddCustomRegionOnCompleted.qml \
4275+ AddCustomRegionOwnedByOtherBottomEdge.qml \
4276+ ClearCustomRegions.qml \
4277+ AlternateDefaultRegionContent.qml \
4278+ OverlappingRegions.qml \
4279+ AutoCollapseInPageHeader.qml \
4280+ AutoCollapseInPageWithPageHeader.qml \
4281+ LeanActiveRegionChange.qml
4282
4283=== modified file 'tests/unit_x11/unit_x11.pro'
4284--- tests/unit_x11/unit_x11.pro 2015-11-09 15:46:45 +0000
4285+++ tests/unit_x11/unit_x11.pro 2015-11-27 10:36:32 +0000
4286@@ -16,4 +16,5 @@
4287 tst_serviceproperties \
4288 tst_subtheming \
4289 tst_swipearea \
4290- tst_touchregistry
4291+ tst_touchregistry \
4292+ tst_bottomedge

Subscribers

People subscribed via source and target branches