Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/multiColumnView into lp:ubuntu-ui-toolkit/staging

Proposed by Christian Dywan on 2015-06-22
Status: Merged
Approved by: Zsombor Egri on 2015-07-29
Approved revision: 1588
Merged at revision: 1583
Proposed branch: lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/multiColumnView
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 2056 lines (+1532/-182)
23 files modified
components.api (+6/-0)
examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml (+1/-1)
examples/ubuntu-ui-toolkit-gallery/Popover.qml (+4/-7)
examples/ubuntu-ui-toolkit-gallery/Sections.qml (+1/-1)
examples/ubuntu-ui-toolkit-gallery/Template.qml (+3/-8)
examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml (+46/-131)
src/Ubuntu/Components/1.2/PageWrapperUtils.js (+1/-2)
src/Ubuntu/Components/1.3/AdaptivePageLayout.qml (+563/-0)
src/Ubuntu/Components/1.3/ColumnMetrics.qml (+53/-0)
src/Ubuntu/Components/1.3/Page.qml (+1/-0)
src/Ubuntu/Components/1.3/PageHeadConfiguration.qdoc (+7/-0)
src/Ubuntu/Components/1.3/PageHeadConfiguration.qml (+2/-0)
src/Ubuntu/Components/1.3/PageWrapper.qml (+37/-0)
src/Ubuntu/Components/1.3/tree.js (+182/-0)
src/Ubuntu/Components/ComponentModule.pro (+3/-0)
src/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml (+47/-24)
src/Ubuntu/Components/qmldir (+2/-0)
tests/autopilot/ubuntuuitoolkit/tests/__init__.py (+4/-4)
tests/resources/navigation/MyCustomPage.qml (+1/-1)
tests/unit_x11/tst_components/ListItemWithLabel.qml (+30/-0)
tests/unit_x11/tst_components/MyExternalPage.qml (+7/-3)
tests/unit_x11/tst_components/tst_adaptivepagelayout.qml (+187/-0)
tests/unit_x11/tst_components/tst_multicolumnheader.qml (+344/-0)
To merge this branch: bzr merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/multiColumnView
Reviewer Review Type Date Requested Status
Zsombor Egri (community) 2015-07-28 Approve on 2015-07-29
Christian Dywan 2015-07-28 Approve on 2015-07-29
Tim Peeters 2015-06-22 Approve on 2015-07-29
PS Jenkins bot continuous-integration Approve on 2015-07-29
Review via email: mp+262591@code.launchpad.net

Commit Message

Implement AdaptivePageLayout

To post a comment you must log in.
1579. By Tim Peeters on 2015-07-21

sync staging

1580. By Tim Peeters on 2015-07-23

merge staging

1581. By Tim Peeters on 2015-07-24

merge view branch

1582. By Tim Peeters on 2015-07-24

sync staging

1583. By Tim Peeters on 2015-07-27

sync staging

1584. By Tim Peeters on 2015-07-28

merge clearNext

1585. By Tim Peeters on 2015-07-28

merge no-view

Tim Peeters (tpeeters) wrote :

./tests/autopilot/ubuntuuitoolkit/tests/__init__.py:208:80: E501 line too long (103 > 79 characters)
        header_label = orientationHelper.select_single(objectName="header_title_label", text=pageTitle)
                                                                               ^
./debian/tmp/usr/lib/python3/dist-packages/ubuntuuitoolkit/tests/__init__.py:208:80: E501 line too long (103 > 79 characters)
        header_label = orientationHelper.select_single(objectName="header_title_label", text=pageTitle)
                                                                               ^
./debian/ubuntu-ui-toolkit-autopilot/usr/lib/python3/dist-packages/ubuntuuitoolkit/tests/__init__.py:208:80: E501 line too long (103 > 79 characters)
        header_label = orientationHelper.select_single(objectName="header_title_label", text=pageTitle)
                                                                               ^

we still need to reformat some py code.

1586. By Tim Peeters on 2015-07-29

flake8

1587. By Tim Peeters on 2015-07-29

clean

Christian Dywan (kalikiana) wrote :

primaryPage not undefined by default Actual (): null Expected (): undefined

1588. By Tim Peeters on 2015-07-29

fix unit test

Tim Peeters (tpeeters) wrote :

Ready!

review: Approve
Christian Dywan (kalikiana) wrote :

Good to go!

review: Approve
Zsombor Egri (zsombi) wrote :

Go, Johnny, go!

review: Approve

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-07-27 10:33:47 +0000
3+++ components.api 2015-07-29 12:04:55 +0000
4@@ -81,6 +81,11 @@
5 Ubuntu.Components.ActivityIndicator 1.3: AnimatedItem
6 property bool onScreen
7 property bool running
8+Ubuntu.Components.AdaptivePageLayout 1.3: PageTreeNode
9+ function var addPageToCurrentColumn(var sourcePage, var page, var properties)
10+ function var addPageToNextColumn(var sourcePage, var page, var properties)
11+ function var removePages(var page)
12+ property Page primaryPage
13 Ubuntu.Components.Alarm 1.0 0.1: QtObject
14 property QDateTime date
15 property DaysOfWeek daysOfWeek
16@@ -603,6 +608,7 @@
17 property bool locked
18 property string preset
19 readonly property PageHeadSections sections
20+ property string title
21 property bool visible
22 Ubuntu.Components.PageHeadSections 1.1: QtObject
23 property bool enabled
24
25=== modified file 'examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml'
26--- examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-04-25 08:18:45 +0000
27+++ examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-07-29 12:04:55 +0000
28@@ -14,7 +14,7 @@
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 */
31
32-import QtQuick 2.2
33+import QtQuick 2.4
34 import Ubuntu.Components 1.3
35
36 ListItem {
37
38=== modified file 'examples/ubuntu-ui-toolkit-gallery/Popover.qml'
39--- examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-04-29 08:55:31 +0000
40+++ examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-07-29 12:04:55 +0000
41@@ -21,17 +21,14 @@
42 Template {
43 objectName: "popoversTemplate"
44
45- tools: ToolbarItems {
46- ToolbarButton {
47- id: actionsButton
48+ head.actions: [
49+ Action {
50 text: "Actions"
51 iconSource: "call_icon.png"
52- onTriggered: PopupUtils.open(actionSelectionPopover, actionsButton)
53+ onTriggered: PopupUtils.open(actionSelectionPopover)
54 visible: true
55 }
56- locked: true
57- opened: true
58- }
59+ ]
60
61 TemplateSection {
62 className: "Popover"
63
64=== modified file 'examples/ubuntu-ui-toolkit-gallery/Sections.qml'
65--- examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-06-17 12:13:40 +0000
66+++ examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-07-29 12:04:55 +0000
67@@ -19,7 +19,7 @@
68
69 Template {
70 objectName: "sectionsTemplate"
71-
72+ head.sections.model: ["first", "second", "third"]
73 TemplateSection {
74 title: "Sections"
75 className: "Sections"
76
77=== modified file 'examples/ubuntu-ui-toolkit-gallery/Template.qml'
78--- examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-04-25 08:18:45 +0000
79+++ examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-07-29 12:04:55 +0000
80@@ -1,5 +1,5 @@
81 /*
82- * Copyright 2013 Canonical Ltd.
83+ * Copyright 2015 Canonical Ltd.
84 *
85 * This program is free software; you can redistribute it and/or modify
86 * it under the terms of the GNU Lesser General Public License as published by
87@@ -14,19 +14,14 @@
88 * along with this program. If not, see <http://www.gnu.org/licenses/>.
89 */
90
91-import QtQuick 2.0
92+import QtQuick 2.4
93 import Ubuntu.Components 1.3
94
95-Item {
96+Page {
97 id: template
98
99- width: units.gu(40)
100- height: units.gu(75)
101-
102 default property alias content: layout.children
103 property alias spacing: layout.spacing
104- property Item tools: null
105- property Flickable flickable: flickable
106
107 Flickable {
108 id: flickable
109
110=== modified file 'examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml'
111--- examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-06-23 17:40:21 +0000
112+++ examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-07-29 12:04:55 +0000
113@@ -1,5 +1,5 @@
114 /*
115- * Copyright 2013 Canonical Ltd.
116+ * Copyright 2015 Canonical Ltd.
117 *
118 * This program is free software; you can redistribute it and/or modify
119 * it under the terms of the GNU Lesser General Public License as published by
120@@ -29,116 +29,55 @@
121 width: units.gu(120)
122 height: units.gu(75)
123
124- /*
125- This property enables the application to change orientation
126- when the device is rotated. The default is false.
127- */
128- automaticOrientation: true
129-
130 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
131 LayoutMirroring.childrenInherit: true
132
133- state: width >= units.gu(80) ? "wide" : "narrow"
134- states: [
135- State {
136- name: "narrow"
137- StateChangeScript {
138- script: {
139- pageStack.push(mainPage);
140- if (selectedWidget) {
141- pageStack.push(contentPage);
142- }
143- }
144- }
145- PropertyChanges {
146- target: mainPage
147- flickable: widgetList
148- }
149- PropertyChanges {
150- target: contentPage
151- flickable: contentLoader.item ? contentLoader.item.flickable : null
152- }
153- },
154- State {
155- name: "wide"
156- StateChangeScript {
157- script: {
158- pageStack.clear();
159-
160- /* When pushing Pages into a PageStack they are reparented
161- to internally created PageWrappers. This undoes it as to
162- allow us to anchor the Pages freely again.
163- */
164- mainPage.parent = gallery;
165- contentPage.parent = gallery;
166- }
167- }
168- PropertyChanges {
169- target: mainPage
170- width: units.gu(40)
171- clip: true
172- }
173- AnchorChanges {
174- target: mainPage
175- anchors.right: undefined
176- }
177- PropertyChanges {
178- target: contentPage
179- clip: true
180- }
181- AnchorChanges {
182- target: contentPage
183- anchors.left: mainPage.right
184- }
185- }
186- ]
187-
188- property var selectedWidget: null
189-
190- Page {
191- id: mainPage
192- active: selectedWidget == null
193-
194- title: "Ubuntu UI Toolkit"
195- /* Page internally sets the topMargin of its flickable to account for
196- the height of the header. Undo it when unsetting the flickable.
197- */
198- onFlickableChanged: if (!flickable) widgetList.topMargin = 0;
199-
200- head.actions: [
201- Action {
202- text: i18n.tr('Use dark theme')
203- iconName: 'torch-on'
204- visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
205- onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
206- },
207- Action {
208- text: i18n.tr('Use light theme')
209- iconName: 'torch-off'
210- visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
211- onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
212- }
213- ]
214-
215- Rectangle {
216- color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
217- anchors.fill: parent
218-
219- ListView {
220- id: widgetList
221- objectName: "widgetList"
222+ AdaptivePageLayout {
223+ id: layout
224+ anchors.fill: parent
225+ primaryPage: mainPage
226+
227+ Page {
228+ id: mainPage
229+ title: "Ubuntu UI Toolkit"
230+
231+ head.actions: [
232+ Action {
233+ text: i18n.tr('Use dark theme')
234+ iconName: 'torch-on'
235+ visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
236+ onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
237+ },
238+ Action {
239+ text: i18n.tr('Use light theme')
240+ iconName: 'torch-off'
241+ visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
242+ onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
243+ }
244+ ]
245+
246+ Rectangle {
247+ color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
248 anchors.fill: parent
249- model: widgetsModel
250- delegate: ListItem.Standard {
251- text: model.label
252- objectName: model.objectName
253- enabled: model.source != ""
254- progression: true
255- selected: enabled && selectedWidget == model
256- onClicked: {
257- selectedWidget = model;
258- if (gallery.state == "narrow") {
259- pageStack.push(contentPage);
260+
261+ ListView {
262+ id: widgetList
263+ objectName: "widgetList"
264+ anchors.fill: parent
265+ model: widgetsModel
266+ currentIndex: -1
267+ delegate: ListItem.Standard {
268+ text: model.label
269+ objectName: model.objectName
270+ enabled: model.source != ""
271+ progression: true
272+ selected: index === widgetList.currentIndex
273+ onClicked: {
274+ var source = Qt.resolvedUrl(model.source);
275+ var newPage = layout.addPageToNextColumn(mainPage, source);
276+
277+ newPage.title = model.label;
278+ widgetList.currentIndex = index;
279 }
280 }
281 }
282@@ -146,30 +85,6 @@
283 }
284 }
285
286- Page {
287- id: contentPage
288- active: selectedWidget != null
289- title: selectedWidget ? selectedWidget.label : ""
290- /* Page internally sets the topMargin of its flickable to account for
291- the height of the header. Undo it when unsetting the flickable.
292- */
293- onFlickableChanged: if (!flickable && contentLoader.item) contentLoader.item.flickable.topMargin = 0;
294- onActiveChanged: if (gallery.state == "narrow" && !active) {
295- selectedWidget = null;
296- }
297-
298- Loader {
299- id: contentLoader
300- objectName: "contentLoader"
301- anchors.fill: parent
302- source: selectedWidget ? selectedWidget.source : ""
303- }
304- }
305-
306- PageStack {
307- id: pageStack
308- }
309-
310 WidgetsModel {
311 id: widgetsModel
312 }
313
314=== modified file 'src/Ubuntu/Components/1.2/PageWrapperUtils.js'
315--- src/Ubuntu/Components/1.2/PageWrapperUtils.js 2015-04-30 08:32:44 +0000
316+++ src/Ubuntu/Components/1.2/PageWrapperUtils.js 2015-07-29 12:04:55 +0000
317@@ -30,8 +30,7 @@
318 if (pageWrapper.reference.createObject) {
319 // page reference is a component
320 pageComponent = pageWrapper.reference;
321- }
322- else if (typeof pageWrapper.reference == "string") {
323+ } else if (typeof pageWrapper.reference == "string") {
324 // page reference is a string (url)
325 pageComponent = Qt.createComponent(pageWrapper.reference);
326 }
327
328=== added file 'src/Ubuntu/Components/1.3/AdaptivePageLayout.qml'
329--- src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 1970-01-01 00:00:00 +0000
330+++ src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 2015-07-29 12:04:55 +0000
331@@ -0,0 +1,563 @@
332+/*
333+ * Copyright 2015 Canonical Ltd.
334+ *
335+ * This program is free software; you can redistribute it and/or modify
336+ * it under the terms of the GNU Lesser General Public License as published by
337+ * the Free Software Foundation; version 3.
338+ *
339+ * This program is distributed in the hope that it will be useful,
340+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
341+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
342+ * GNU Lesser General Public License for more details.
343+ *
344+ * You should have received a copy of the GNU Lesser General Public License
345+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
346+ */
347+
348+import QtQuick 2.4
349+import QtQuick.Layouts 1.1
350+import Ubuntu.Components 1.3
351+import "tree.js" as Tree
352+
353+/*!
354+ \qmltype AdaptivePageLayout
355+ \inqmlmodule Ubuntu.Components 1.3
356+ \since Ubuntu.Components 1.3
357+ \ingroup ubuntu
358+ \brief View with multiple columns of Pages.
359+
360+ The component provides a flexible way of viewing a stack of pages in one or
361+ more columns. Unlike in PageStack, there can be more than one Page active at
362+ a time, depending on the number of the columns in the view.
363+
364+ AdaptivePageLayout stores pages added in a tree. Pages are added relative to a
365+ given page, either as sibling (\l addPageToCurrentColumn) or as child
366+ (\l addPageToNextColumn). This means that removing a non-leaf page from the Page
367+ tree will remove all its children from the page tree.
368+
369+ The columns are populated from left to right. The column a page is added to is
370+ detected based on the source page that is given to the functions adding the page.
371+ The pages can be added either to the same column the source page resides or to
372+ the column next to the source page. Giving a null value to the source page will
373+ add the page to the leftmost column of the view.
374+
375+ The primary page, the very first page must be specified through the \l primaryPage
376+ property. The property cannot be changed after component completion and can hold
377+ a Page instance, a Component or a url to a document defining a Page. The page
378+ cannot be removed from the view.
379+
380+ \qml
381+ import QtQuick 2.4
382+ import Ubuntu.Components 1.3
383+
384+ MainView {
385+ width: units.gu(100)
386+ height: units.gu(60)
387+
388+ AdaptivePageLayout {
389+ anchors.fill: parent
390+ primaryPage: page1
391+ Page {
392+ id: page1
393+ title: "Main page"
394+ Column {
395+ Button {
396+ text: "Add Page2 above " + page1.title
397+ onClicked: page1.pageStack.addPageToCurrentColumn(page1, page2)
398+ }
399+ Button {
400+ text: "Add Page3 next to " + page1.title
401+ onClicked: page1.pageStack.addPageToNextColumn(page1, page3)
402+ }
403+ }
404+ }
405+ Page {
406+ id: page2
407+ title: "Page #2"
408+ }
409+ Page {
410+ id: page3
411+ title: "Page #3"
412+ }
413+ }
414+ }
415+ \endqml
416+
417+ AdaptivePageLayout supports adaptive column handling. When the number of columns changes at
418+ runtime the pages are automatically rearranged.
419+
420+ \sa PageStack
421+*/
422+
423+PageTreeNode {
424+ id: layout
425+
426+ Page {
427+ // AdaptivePageLayout has its own split headers, so
428+ // disable the application header.
429+ id: appHeaderControlPage
430+ head {
431+ locked: true
432+ visible: false
433+ }
434+ // title is set in attachPage() when the attached Page.column === 0
435+ }
436+
437+ /*!
438+ The property holds the first Page which will be added to the view. If the
439+ view has more than one column, the page will be added to the leftmost column.
440+ The property can hold either a Page instance, a component holding a Page
441+ or a QML document defining the Page. The property cannot be changed after
442+ component completion.
443+ */
444+ property Page primaryPage
445+
446+ /*!
447+ \qmlmethod Item addPageToCurrentColumn(Item sourcePage, var page[, var properties])
448+ Adds a \c page to the column the \c sourcePage resides in and removes all pages
449+ from the higher columns. \c page can be a Component or a file.
450+ \c properties is a JSON object containing properties
451+ to be set when page is created. \c sourcePage must be active. Returns the
452+ instance of the page created.
453+ */
454+ function addPageToCurrentColumn(sourcePage, page, properties) {
455+ var nextColumn = d.columnForPage(sourcePage) + 1;
456+ // Clear all following columns. If we do not do this, we have no way to
457+ // determine which page needs to be on top when the view is resized
458+ // to have a single column.
459+ d.tree.prune(nextColumn);
460+ for (var i = nextColumn; i < d.columns; i++) {
461+ d.updatePageForColumn(i);
462+ }
463+ return d.addPageToColumn(nextColumn - 1, sourcePage, page, properties);
464+ }
465+
466+ /*!
467+ \qmlmethod Item addPageToNextColumn(Item sourcePage, var page[, var properties])
468+ Remove all previous pages from the next column (relative to the column that
469+ holds \c sourcePage) and all following columns, and then add \c page to the next column.
470+ If \c sourcePage is located in the
471+ rightmost column, the new page will be pushed to the same column as \c sourcePage.
472+ */
473+ function addPageToNextColumn(sourcePage, page, properties) {
474+ var nextColumn = d.columnForPage(sourcePage) + 1;
475+ d.tree.prune(nextColumn);
476+ for (var i = nextColumn; i < d.columns; i++) {
477+ d.updatePageForColumn(i);
478+ }
479+ return d.addPageToColumn(nextColumn, sourcePage, page, properties);
480+ }
481+
482+ /*!
483+ \qmlmethod void removePages(Item page)
484+ The function removes and deletes all pages up to and including \c page
485+ is reached. If the \a page is the same as the \l primaryPage, only its child
486+ pages will be removed.
487+ */
488+ function removePages(page) {
489+ var nodeToRemove = d.getWrapper(page);
490+ var removedNodes = d.tree.chop(nodeToRemove, page != layout.primaryPage);
491+ for (var i = removedNodes.length-1; i >= 0; i--) {
492+ var node = removedNodes[i];
493+ d.updatePageForColumn(node.column);
494+ }
495+ }
496+
497+ /*
498+ internals
499+ */
500+
501+ Component.onCompleted: {
502+ d.relayout();
503+ d.completed = true;
504+ if (primaryPage) {
505+ var wrapper = d.createWrapper(primaryPage);
506+ d.addWrappedPage(wrapper);
507+ } else {
508+ console.warn("No primary page set. No pages can be added without a primary page.");
509+ }
510+ }
511+ onPrimaryPageChanged: {
512+ if (d.completed) {
513+ console.warn("Cannot change primaryPage after completion.");
514+ return;
515+ }
516+ }
517+
518+ QtObject {
519+ id: d
520+
521+ property bool completed: false
522+ property var tree: new Tree.Tree()
523+
524+ property int columns: layout.width >= units.gu(80) ? 2 : 1
525+ /*! internal */
526+ onColumnsChanged: {
527+ if (columns <= 0) {
528+ console.warn("There must be a minimum of one column set.");
529+ columns = 1;
530+ }
531+ d.relayout();
532+ }
533+ property real defaultColumnWidth: units.gu(40)
534+ onDefaultColumnWidthChanged: body.applyMetrics()
535+ property list<ColumnMetrics> columnMetrics
536+
537+ function createWrapper(page, properties) {
538+ var wrapperComponent = Qt.createComponent("PageWrapper.qml");
539+ var wrapperObject = wrapperComponent.createObject(hiddenPages);
540+ wrapperObject.pageStack = layout;
541+ wrapperObject.properties = properties;
542+ // set reference last because it will trigger creation of the object
543+ // with specified properties.
544+ wrapperObject.reference = page;
545+ return wrapperObject;
546+ }
547+
548+ function addWrappedPage(pageWrapper) {
549+ pageWrapper.parentWrapper = d.getWrapper(pageWrapper.parentPage);
550+ tree.add(pageWrapper.column, pageWrapper.parentWrapper, pageWrapper);
551+ var targetColumn = MathUtils.clamp(pageWrapper.column, 0, d.columns - 1);
552+ // replace page holder's child
553+ var holder = body.children[targetColumn];
554+ holder.detachCurrentPage();
555+ holder.attachPage(pageWrapper)
556+ }
557+
558+ function getWrapper(page) {
559+ if (page && page.hasOwnProperty("parentNode")) {
560+ var w = page.parentNode;
561+ if (w && w.hasOwnProperty("object") && w.hasOwnProperty("reference")) {
562+ if (w.object == page) {
563+ return w;
564+ } else {
565+ print("Page is not wrapped by its parentNode. This should not happen!");
566+ return null;
567+ }
568+ } else {
569+ // invalid wrapper
570+ return null;
571+ }
572+ } else {
573+ // invalid page
574+ return null;
575+ }
576+ }
577+
578+ function columnForPage(page) {
579+ var wrapper = d.getWrapper(page);
580+ return wrapper ? wrapper.column : 0;
581+ }
582+
583+ function addPageToColumn(column, sourcePage, page, properties) {
584+ if (column < 0) {
585+ console.warn("Column must be >= 0.");
586+ return;
587+ }
588+ if (!sourcePage) {
589+ console.warn("No sourcePage specified. Page will not be added.");
590+ return;
591+ }
592+ var sourceWrapper = d.getWrapper(sourcePage);
593+ if (d.tree.index(sourceWrapper) === -1) {
594+ console.warn("sourcePage must be added to the view to add new page.");
595+ return;
596+ }
597+
598+ // Check that the Page was not already added.
599+ if (typeof page !== "string" && !page.createObject) {
600+ // page is neither a url or a Component so it must be a Page object.
601+
602+ var oldWrapper = getWrapper(page);
603+ if (oldWrapper && d.tree.index(oldWrapper) !== -1) {
604+ console.warn("Cannot add a Page that was already added.");
605+ return null;
606+ }
607+ }
608+
609+ var newWrapper = d.createWrapper(page, properties);
610+ newWrapper.parentPage = sourcePage;
611+ newWrapper.column = column;
612+ d.addWrappedPage(newWrapper);
613+ return newWrapper.object;
614+ }
615+
616+ // update the page for the specified column
617+ function updatePageForColumn(column) {
618+ var effectiveColumn = MathUtils.clamp(column, 0, d.columns - 1);
619+ var columnHolder = body.children[effectiveColumn];
620+ var newWrapper = tree.top(effectiveColumn, effectiveColumn < d.columns - 1);
621+ var oldWrapper = columnHolder.pageWrapper;
622+
623+ if (newWrapper != oldWrapper) {
624+ columnHolder.detachCurrentPage();
625+ oldWrapper.parent = null;
626+ if (newWrapper) {
627+ columnHolder.attachPage(newWrapper);
628+ }
629+ if (oldWrapper.canDestroy) {
630+ oldWrapper.destroyObject();
631+ }
632+ }
633+ }
634+
635+ // relayouts when column count changes
636+ function relayout() {
637+ if (body.children.length == d.columns) return;
638+ if (body.children.length > d.columns) {
639+ // need to remove few columns, the last ones
640+ while (body.children.length > d.columns) {
641+ var holder = body.children[body.children.length - 1];
642+ holder.detachCurrentPage();
643+ holder.parent = null;
644+ holder.destroy();
645+ }
646+ } else {
647+ var prevColumns = body.children.length;
648+
649+ // add columns
650+ for (var i = 0; i < d.columns - prevColumns; i++) {
651+ pageHolderComponent.createObject(body);
652+ }
653+ }
654+ rearrangePages();
655+ }
656+
657+ function rearrangePages() {
658+ for (var column = d.columns - 1; column >= 0; column--) {
659+ var holder = body.children[column];
660+ var pageWrapper = tree.top(column, column < (d.columns - 1));
661+ if (!pageWrapper) {
662+ continue;
663+ }
664+ if (!pageWrapper.parent) {
665+ // this should never happen, so if it does, we have a bug!
666+ console.error("Found a page which wasn't parented anywhere!", pageWrapper.object.title);
667+ continue;
668+ }
669+ // detach current page from holder if differs
670+ if (holder.pageWrapper != pageWrapper) {
671+ holder.detachCurrentPage();
672+ }
673+ if (pageWrapper.parent == hiddenPages) {
674+ // add the page to the column
675+ holder.attachPage(pageWrapper);
676+ } else if (pageWrapper.pageHolder != holder) {
677+ // detach the pageWrapper from its holder
678+ if (pageWrapper.pageHolder) {
679+ pageWrapper.pageHolder.detachCurrentPage();
680+ }
681+ // then attach to this holder
682+ holder.attachPage(pageWrapper);
683+ }
684+ }
685+ }
686+ }
687+
688+ // default metrics
689+ Component {
690+ id: defaultMetrics
691+ ColumnMetrics {
692+ fillWidth: column == d.columns
693+ minimumWidth: d.defaultColumnWidth
694+ }
695+ }
696+
697+ // Page holder component, can have only one Page as child at a time, all stacked pages
698+ // will be parented into hiddenPages
699+ Component {
700+ id: pageHolderComponent
701+ // Page uses the height of the parentNode for its height, so make
702+ // the holder a PageTreeNode that determines the Page height.
703+ PageTreeNode {
704+ id: holder
705+ active: false
706+ objectName: "ColumnHolder" + column
707+ property PageWrapper pageWrapper
708+ property int column
709+ property alias config: subHeader.config
710+ property ColumnMetrics metrics: setDefaultMetrics()
711+
712+ Layout.fillWidth: metrics.fillWidth
713+ Layout.fillHeight: true
714+ Layout.preferredWidth: metrics.maximumWidth > 0 ?
715+ MathUtils.clamp(d.defaultColumnWidth, metrics.minimumWidth, metrics.maximumWidth) :
716+ d.defaultColumnWidth
717+ Layout.minimumWidth: metrics.minimumWidth
718+ Layout.maximumWidth: metrics.maximumWidth
719+
720+ // prevent the pages from taking the app header height into account.
721+ __propagated: null
722+ Item {
723+ id: holderBody
724+ objectName: parent.objectName + "Body"
725+ anchors {
726+ top: subHeader.bottom
727+ bottom: parent.bottom
728+ left: parent.left
729+ right: parent.right
730+ rightMargin: verticalDivider.width
731+ }
732+ // we need to clip because the header does not have a background
733+ clip: true
734+ }
735+
736+ property alias head: subHeader
737+ StyledItem {
738+ id: subHeader
739+ anchors {
740+ left: parent.left
741+ top: parent.top
742+ right: parent.right
743+ }
744+ height: body.headerHeight
745+
746+ styleName: config ? "PageHeadStyle" : ""
747+ theme.version: Ubuntu.toolkitVersion
748+ objectName: "Header" + column
749+
750+ property real preferredHeight: subHeader.__styleInstance ?
751+ subHeader.__styleInstance.implicitHeight :
752+ 0
753+ onPreferredHeightChanged: {
754+ body.updateHeaderHeight(preferredHeight);
755+ }
756+
757+ property PageHeadConfiguration config: null
758+ property Item contents: null
759+
760+ property color dividerColor: layout.__propagated.header.dividerColor
761+ property color panelColor: layout.__propagated.header.panelColor
762+
763+ visible: holder.pageWrapper && holder.pageWrapper.active
764+
765+ // The multiColumn, page and showBackButton properties are used in
766+ // PageHeadStyle to show/hide the back button.
767+ property var multiColumn: layout
768+ property var page: holder.pageWrapper ? holder.pageWrapper.object : null
769+ property bool showBackButton: {
770+ if (!page) {
771+ return false;
772+ }
773+ var parentWrapper;
774+ try {
775+ parentWrapper = d.tree.parent(holder.pageWrapper);
776+ } catch(err) {
777+ // Root node has no parent node.
778+ return false;
779+ }
780+ var nextInColumn = d.tree.top(holder.column, holder.column < d.columns - 1, 1);
781+ return parentWrapper === nextInColumn;
782+ }
783+ }
784+
785+ Rectangle {
786+ id: verticalDivider
787+ anchors {
788+ top: parent.top
789+ bottom: parent.bottom
790+ right: parent.right
791+ }
792+ width: (column == (d.columns - 1)) || !pageWrapper ? 0 : units.dp(1)
793+ color: subHeader.dividerColor
794+ }
795+
796+ function attachPage(page) {
797+ pageWrapper = page;
798+ pageWrapper.parent = holderBody;
799+ pageWrapper.pageHolder = holder;
800+ pageWrapper.active = true;
801+
802+ if (pageWrapper.object.hasOwnProperty("head")) {
803+ subHeader.config = pageWrapper.object.head;
804+ }
805+ if (pageWrapper.column === 0) {
806+ // set the application title
807+ appHeaderControlPage.title = pageWrapper.object.title;
808+ }
809+ }
810+ function detachCurrentPage() {
811+ if (!pageWrapper) return undefined;
812+ var wrapper = pageWrapper;
813+ // remove header
814+ wrapper.active = false;
815+ subHeader.config = null;
816+ pageWrapper = null;
817+ wrapper.parent = hiddenPages;
818+ wrapper.pageHolder = null;
819+ return wrapper;
820+ }
821+
822+ function setDefaultMetrics() {
823+ var result = defaultMetrics.createObject(holder);
824+ result.column = Qt.binding(function() { return holder.column + 1; });
825+ return result;
826+ }
827+ }
828+ }
829+
830+ /*! \internal */
831+ // Pages declared as children will be placed directly into hiddenPages
832+ default property alias data: hiddenPages.data
833+ Item {
834+ id: hiddenPages
835+ objectName: "HiddenPagePool"
836+ visible: false
837+ // make sure nothing is shown eventually
838+ clip: true
839+ }
840+
841+ // Holds the columns holding the pages visible. Each column has only one page
842+ // as child, the invisible stacked ones are all stored in the hiddenPages
843+ // component. The stack keeps the column index onto which those should be moved
844+ // once they become visible.
845+ RowLayout {
846+ id: body
847+ objectName: "body"
848+ anchors.fill: parent
849+ spacing: 0
850+
851+ property real headerHeight: 0
852+
853+ function updateHeaderHeight(newHeight) {
854+ if (newHeight > body.headerHeight) {
855+ body.headerHeight = newHeight;
856+ } else {
857+ var h = 0;
858+ var subHeight = 0;
859+ for (var i = 0; i < children.length; i++) {
860+ subHeight = children[i].head.preferredHeight;
861+ if (subHeight > h) h = subHeight;
862+ }
863+ body.headerHeight = h;
864+ }
865+ }
866+
867+ onChildrenChanged: {
868+ // all children should have Layout.fillWidth false, except the last one
869+ for (var i = 0; i < children.length; i++) {
870+ children[i].column = i;
871+ }
872+ applyMetrics();
873+ }
874+
875+ function applyMetrics() {
876+ for (var i = 0; i < children.length; i++) {
877+ var holder = children[i];
878+ // search for the column metrics
879+ var metrics = null;
880+ for (var j = 0; j < d.columnMetrics.length; j++) {
881+ if (d.columnMetrics[j].column == (i + 1)) {
882+ metrics = d.columnMetrics[j];
883+ break;
884+ }
885+ }
886+ if (!metrics) {
887+ metrics = holder.setDefaultMetrics();
888+ }
889+ holder.metrics = metrics;
890+ updateHeaderHeight(0);
891+ }
892+ }
893+ }
894+}
895
896=== added file 'src/Ubuntu/Components/1.3/ColumnMetrics.qml'
897--- src/Ubuntu/Components/1.3/ColumnMetrics.qml 1970-01-01 00:00:00 +0000
898+++ src/Ubuntu/Components/1.3/ColumnMetrics.qml 2015-07-29 12:04:55 +0000
899@@ -0,0 +1,53 @@
900+/*
901+ * Copyright 2015 Canonical Ltd.
902+ *
903+ * This program is free software; you can redistribute it and/or modify
904+ * it under the terms of the GNU Lesser General Public License as published by
905+ * the Free Software Foundation; version 3.
906+ *
907+ * This program is distributed in the hope that it will be useful,
908+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
909+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
910+ * GNU Lesser General Public License for more details.
911+ *
912+ * You should have received a copy of the GNU Lesser General Public License
913+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
914+ */
915+
916+import QtQuick 2.4
917+
918+/*!
919+ \qmltype ColumnMetrics
920+ \inqmlmodule Ubuntu.Components 1.3
921+ \since Ubuntu.Components 1.3
922+ \ingroup ubuntu
923+ \brief Component configuring the metrics of a column in MultiColumnView.
924+ \internal
925+
926+ */
927+QtObject {
928+ /*!
929+ 1-based value identifying the column the metrics to be applied to.
930+ */
931+ property int column
932+
933+ /*!
934+ Specifies whether the width of the column should fill the available space
935+ of the MultiColumnView column or not. Defaults to \a false.
936+ */
937+ property bool fillWidth: false
938+
939+ /*!
940+ Specifies the minimum width of the column. If the value is greater than
941+ \b MultiColumnView::defaultColumnWidth, the value will be set as width for
942+ the column.
943+ */
944+ property real minimumWidth: 0
945+
946+ /*!
947+ Specifies the maximum width of the column. If the value is smaller than
948+ \b MultiColumnView::defaultColumnWidth, the value will be set as width for
949+ the column. A maximum value of 0 will be ignored.
950+ */
951+ property real maximumWidth: Number.POSITIVE_INFINITY
952+}
953
954=== modified file 'src/Ubuntu/Components/1.3/Page.qml'
955--- src/Ubuntu/Components/1.3/Page.qml 2015-07-02 20:40:01 +0000
956+++ src/Ubuntu/Components/1.3/Page.qml 2015-07-29 12:04:55 +0000
957@@ -43,6 +43,7 @@
958 readonly property alias head: headerConfig
959 Toolkit13.PageHeadConfiguration {
960 id: headerConfig
961+ title: page.title
962 }
963
964 Toolkit13.Object {
965
966=== modified file 'src/Ubuntu/Components/1.3/PageHeadConfiguration.qdoc'
967--- src/Ubuntu/Components/1.3/PageHeadConfiguration.qdoc 2015-04-25 08:54:58 +0000
968+++ src/Ubuntu/Components/1.3/PageHeadConfiguration.qdoc 2015-07-29 12:04:55 +0000
969@@ -200,3 +200,10 @@
970 active \l Page's flickable. The value of the visible property will be
971 updated at the end of the showing/hiding animation of the header.
972 */
973+
974+/*!
975+ \qmlproperty string PageHeadConfiguration::title
976+ \since 1.3
977+ The title to show in the header. This is automatically copied from
978+ the \l Page title.
979+ */
980
981=== modified file 'src/Ubuntu/Components/1.3/PageHeadConfiguration.qml'
982--- src/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-04-30 08:32:44 +0000
983+++ src/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-07-29 12:04:55 +0000
984@@ -56,4 +56,6 @@
985
986 // auto-updated by AppHeader, but may be set by the developer
987 property bool visible
988+
989+ property string title
990 }
991
992=== modified file 'src/Ubuntu/Components/1.3/PageWrapper.qml'
993--- src/Ubuntu/Components/1.3/PageWrapper.qml 2015-05-05 16:23:29 +0000
994+++ src/Ubuntu/Components/1.3/PageWrapper.qml 2015-07-29 12:04:55 +0000
995@@ -46,6 +46,43 @@
996 property bool canDestroy: false
997
998 /*!
999+ Column number in MultiColumnView.
1000+ */
1001+ property int column: 0
1002+
1003+ /*!
1004+ Parent page.
1005+ */
1006+ property Item parentPage
1007+
1008+ /*!
1009+ Parent PageWrapper or the parentPage.
1010+ */
1011+ property Item parentWrapper
1012+
1013+ /*!
1014+ Page holder in MultiColumnView
1015+ */
1016+ property Item pageHolder
1017+
1018+ /*!
1019+ Returns true if the current PageWrapper is a child of the given page
1020+ */
1021+ function childOf(page) {
1022+ if (parentPage == page) return true;
1023+ if (page && parentWrapper) {
1024+ var wrapper = parentWrapper;
1025+ while (wrapper) {
1026+ if (wrapper.object == page) {
1027+ return true;
1028+ }
1029+ wrapper = wrapper.parentWrapper;
1030+ }
1031+ }
1032+ return false;
1033+ }
1034+
1035+ /*!
1036 This value is updated when a PageWrapper is pushed to/popped from a PageStack.
1037 */
1038 active: false
1039
1040=== added file 'src/Ubuntu/Components/1.3/tree.js'
1041--- src/Ubuntu/Components/1.3/tree.js 1970-01-01 00:00:00 +0000
1042+++ src/Ubuntu/Components/1.3/tree.js 2015-07-29 12:04:55 +0000
1043@@ -0,0 +1,182 @@
1044+/*
1045+ * Copyright 2015 Canonical Ltd.
1046+ *
1047+ * This program is free software; you can redistribute it and/or modify
1048+ * it under the terms of the GNU Lesser General Public License as published by
1049+ * the Free Software Foundation; version 3.
1050+ *
1051+ * This program is distributed in the hope that it will be useful,
1052+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1053+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1054+ * GNU Lesser General Public License for more details.
1055+ *
1056+ * You should have received a copy of the GNU Lesser General Public License
1057+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1058+ */
1059+
1060+.pragma library
1061+
1062+function Tree() {
1063+ // list<object> of nodes
1064+ var nodes = [];
1065+
1066+ // list<int> of stems
1067+ var stems = [];
1068+
1069+ // list<int> of indices of parent nodes
1070+ var parents = [];
1071+
1072+ // int number of nodes in the tree
1073+ var size = 0;
1074+
1075+ // Return the index of the given node.
1076+ // Returns -1 the node was not found.
1077+ this.index = function(node) {
1078+ return nodes.indexOf(node);
1079+ }
1080+
1081+ // Add newNode to the tree in the specified stem, with the specified parent node.
1082+ // The root node should be added with parentNode null. All other nodes must be added
1083+ // with a parentNode that is already in the tree.
1084+ this.add = function(stem, parentNode, newNode) {
1085+ if (this.index(newNode) !== -1) {
1086+ throw "Cannot add the same node twice to a tree.";
1087+ }
1088+ if (size === 0) {
1089+ // adding root node
1090+ if (parentNode !== null) {
1091+ throw "Root node must have parentNode null.";
1092+ }
1093+ } else {
1094+ // adding non-root node
1095+ if (parentNode === null) {
1096+ throw "Only root node has parentNode null."
1097+ }
1098+ if (this.index(parentNode) === -1) {
1099+ throw "Cannot add non-root node if parentNode is not in the tree.";
1100+ }
1101+ }
1102+ nodes.push(newNode);
1103+ stems.push(stem);
1104+ parents.push(parentNode);
1105+ size++;
1106+ }
1107+
1108+ // Remove all nodes from the specified stem and higher stems.
1109+ //
1110+ // Returns the removed nodes.
1111+ this.prune = function(stem) {
1112+ var newNodes = [];
1113+ var newStems = [];
1114+ var newParents = [];
1115+ var removedNodes = [];
1116+ for (var i = 0; i < nodes.length; i++) {
1117+ if (stems[i] < stem) {
1118+ newNodes.push(nodes[i]);
1119+ newStems.push(stems[i]);
1120+ newParents.push(parents[i]);
1121+ } else {
1122+ removedNodes.push(nodes[i]);
1123+ }
1124+ }
1125+ nodes = newNodes;
1126+ stems = newStems;
1127+ parents = newParents;
1128+ size = nodes.length;
1129+ return removedNodes;
1130+ }
1131+
1132+ // Chops all nodes with an index higher than the given node which
1133+ // are in the same stem or a higher stem.
1134+ //
1135+ // If, and only if, (inclusive) then also chop the given node.
1136+ //
1137+ // Default values for node and inclusive are top() and true.
1138+ // Returns a list that contains the nodes that were chopped.
1139+ this.chop = function(node, inclusive) {
1140+ node = typeof node !== 'undefined' ? node : this.top();
1141+ inclusive = typeof inclusive !== 'undefined' ? inclusive : true
1142+ var nodeIndex = this.index(node);
1143+ if (nodeIndex < 0) {
1144+ // given node is not in the tree.
1145+ return [];
1146+ }
1147+ if (inclusive) {
1148+ size = nodeIndex;
1149+ } else {
1150+ size = nodeIndex + 1;
1151+ }
1152+
1153+ // Nodes with index(node) >= nodeIndex && stem >= stems[nodeIndex];
1154+ var badNodes = []; // to fill below
1155+
1156+ // Nodes with index(node) >= nodeIndex (any stem).
1157+ // Potential bad nodes to be removed.
1158+ var uglyNodes = nodes.slice(size);
1159+ var uglyStems = stems.slice(size); // to check below
1160+ var uglyParents = parents.slice(size);
1161+
1162+ var stem = stems[nodeIndex];
1163+ // Good nodes, with index(node) < nodeIndex && stem < stems[nodeIndex]:
1164+ nodes = nodes.slice(0, size);
1165+ stems = stems.slice(0, size);
1166+ parents = parents.slice(0, size);
1167+
1168+ // Add nodes with index(node) > nodeIndex && stem < stems[nodeIndex] back:
1169+ for (var i = 0; i < uglyNodes.length; i++) {
1170+ if (uglyStems[i] < stem) {
1171+ // Because the stem of the parentNode <= stem of the node,
1172+ // the node that is added back has the parentNode in nodes.
1173+ nodes.push(uglyNodes[i]);
1174+ stems.push(uglyStems[i]);
1175+ parents.push(uglyParents[i]);
1176+ size++;
1177+ } else {
1178+ badNodes.push(uglyNodes[i]);
1179+ }
1180+ }
1181+ return badNodes;
1182+ }
1183+
1184+ // Returns the n'th node when traversing one or more stems from the
1185+ // top down. When exactMatch, only the specified stem is traversed, and
1186+ // when !exactMatch the specified stem and all higher stems are traversed.
1187+ //
1188+ // Returns null if no matching node was found.
1189+ //
1190+ // Default value for stem: 0
1191+ // Default value for exactMatch: false
1192+ // Default value for n: 0 (first node)
1193+ //
1194+ // Calling top() with no parameters returns top(0, false, 0) which is the
1195+ // last node that was added to the tree.
1196+ this.top = function(stem, exactMatch, n) {
1197+ stem = typeof stem !== 'undefined' ? stem : 0
1198+ exactMatch = typeof exactMatch !== 'undefined' ? exactMatch : false
1199+ n = typeof n !== 'undefined' ? n : 0
1200+
1201+ var st;
1202+ var count = n;
1203+ for (var i = size - 1; i >= 0; i--) {
1204+ st = stems[i];
1205+ if ((exactMatch && st === stem) || (!exactMatch && st >= stem)) {
1206+ count--;
1207+ }
1208+ if (count < 0) {
1209+ return nodes[i];
1210+ }
1211+ }
1212+ return null;
1213+ }
1214+
1215+ // Return the parent node of the specified node in the tree
1216+ this.parent = function(node) {
1217+ var i = nodes.indexOf(node);
1218+ if (i === -1) {
1219+ throw "Specified node not found in tree.";
1220+ } else if (i === 0) {
1221+ throw "Root node has no parent node.";
1222+ }
1223+ return parents[i];
1224+ }
1225+}
1226
1227=== modified file 'src/Ubuntu/Components/ComponentModule.pro'
1228--- src/Ubuntu/Components/ComponentModule.pro 2015-07-24 17:22:32 +0000
1229+++ src/Ubuntu/Components/ComponentModule.pro 2015-07-29 12:04:55 +0000
1230@@ -86,11 +86,13 @@
1231 1.3/ActionItem.qml \
1232 1.3/ActionList.qml \
1233 1.3/ActivityIndicator.qml \
1234+ 1.3/AdaptivePageLayout.qml \
1235 1.3/AnimatedItem.qml \
1236 1.3/AppHeader.qml \
1237 1.3/Button.qml \
1238 1.3/Captions.qml \
1239 1.3/CheckBox.qml \
1240+ 1.3/ColumnMetrics.qml \
1241 1.3/ComboButton.qml \
1242 1.3/CrossFadeImage.qml \
1243 1.3/dateUtils.js \
1244@@ -129,6 +131,7 @@
1245 1.3/ToolbarButton.qml \
1246 1.3/ToolbarItems.qml \
1247 1.3/Toolbar.qml \
1248+ 1.3/tree.js \
1249 1.3/UbuntuColors.qml \
1250 1.3/UbuntuListView11.qml \
1251 1.3/UbuntuListView.qml \
1252
1253=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml'
1254--- src/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-06-23 13:22:10 +0000
1255+++ src/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-07-29 12:04:55 +0000
1256@@ -27,15 +27,22 @@
1257 textLeftMargin: units.gu(2)
1258 maximumNumberOfActions: 3
1259
1260+ PageHeadConfiguration {
1261+ id: defaultConfig
1262+ }
1263+
1264+ property PageHeadConfiguration config: styledItem.config ?
1265+ styledItem.config :
1266+ defaultConfig
1267 /*!
1268 The color of the buttons in the header.
1269 */
1270- property color buttonColor: styledItem.config.foregroundColor
1271+ property color buttonColor: headerStyle.config.foregroundColor
1272
1273 /*!
1274 The color of the title text.
1275 */
1276- property color titleColor: styledItem.config.foregroundColor
1277+ property color titleColor: headerStyle.config.foregroundColor
1278
1279 // FIXME: When the three panel color properties below are removed,
1280 // update unity8/Dash/PageHeader to use the new theming (currently
1281@@ -103,13 +110,19 @@
1282 leftMargin: units.gu(2)
1283 bottom: divider.top
1284 }
1285+ visible: model && model.length > 0
1286 enabled: sections.enabled
1287- height: model && model.length > 0 ? implicitHeight : 0
1288-
1289- property PageHeadSections sections: styledItem.config.sections
1290- model: sections.model
1291-
1292- onSelectedIndexChanged: sections.selectedIndex = sectionsItem.selectedIndex
1293+ height: visible ? implicitHeight : 0
1294+
1295+ property PageHeadSections sections: headerStyle.config.sections
1296+ model: sections ? sections.model : null
1297+
1298+ onSelectedIndexChanged: {
1299+ if (sections) {
1300+ sections.selectedIndex = sectionsItem.selectedIndex;
1301+ }
1302+ }
1303+
1304 Connections {
1305 target: sectionsItem.sections
1306 onSelectedIndexChanged: sectionsItem.selectedIndex = sectionsItem.sections.selectedIndex
1307@@ -244,10 +257,10 @@
1308 PageHeadButton {
1309 id: customBackButton
1310 objectName: "customBackButton"
1311- action: styledItem.config.backAction
1312- visible: null !== styledItem.config.backAction &&
1313- styledItem.config.backAction.visible
1314- color: styledItem.config.foregroundColor
1315+ action: headerStyle.config.backAction
1316+ visible: null !== headerStyle.config.backAction &&
1317+ headerStyle.config.backAction.visible
1318+ color: headerStyle.config.foregroundColor
1319 }
1320
1321 PageHeadButton {
1322@@ -255,16 +268,26 @@
1323 objectName: "backButton"
1324
1325 iconName: "back"
1326- visible: styledItem.pageStack !== null &&
1327- styledItem.pageStack !== undefined &&
1328- styledItem.pageStack.depth > 1 &&
1329- !styledItem.config.backAction
1330+ property bool stackBack: styledItem.pageStack !== null &&
1331+ styledItem.pageStack !== undefined &&
1332+ styledItem.pageStack.depth > 1
1333+
1334+ // MultiColumnView adds the following properties: multiColumn, page, showBackButton.
1335+ property bool treeBack: styledItem.hasOwnProperty("multiColumn") &&
1336+ styledItem.showBackButton
1337+
1338+ visible: !headerStyle.config.backAction && (stackBack || treeBack)
1339
1340 text: "back"
1341- color: styledItem.config.foregroundColor
1342+ color: headerStyle.config.foregroundColor
1343
1344 onTriggered: {
1345- styledItem.pageStack.pop();
1346+ if (stackBack) {
1347+ styledItem.pageStack.pop();
1348+ } else {
1349+ // treeBack
1350+ styledItem.multiColumn.removePages(styledItem.page);
1351+ }
1352 }
1353 }
1354
1355@@ -327,13 +350,13 @@
1356 Label {
1357 objectName: "header_title_label"
1358 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
1359- visible: !contentsContainer.visible && styledItem.config.preset === ""
1360+ visible: !contentsContainer.visible && headerStyle.config.preset === ""
1361 anchors {
1362 left: parent.left
1363 right: parent.right
1364 verticalCenter: parent.verticalCenter
1365 }
1366- text: styledItem.title
1367+ text: headerStyle.config.title
1368 font.weight: headerStyle.fontWeight
1369 fontSize: headerStyle.fontSize
1370 color: headerStyle.titleColor
1371@@ -346,7 +369,7 @@
1372 // when the bindings below is no longer active
1373 id: contentsContainer
1374 anchors.fill: parent
1375- visible: styledItem.contents || styledItem.config.contents
1376+ visible: styledItem.contents || headerStyle.config.contents
1377 }
1378 Binding {
1379 target: styledItem.contents
1380@@ -361,10 +384,10 @@
1381 when: styledItem.contents
1382 }
1383 Binding {
1384- target: styledItem.config.contents
1385+ target: headerStyle.config.contents
1386 property: "parent"
1387 value: contentsContainer
1388- when: styledItem.config.contents && !styledItem.contents
1389+ when: headerStyle.config.contents && !styledItem.contents
1390 }
1391 }
1392
1393@@ -378,7 +401,7 @@
1394 }
1395 height: headerStyle.contentHeight
1396
1397- actions: styledItem.config.actions
1398+ actions: headerStyle.config.actions
1399 numberOfSlots: 3
1400 }
1401 }
1402
1403=== modified file 'src/Ubuntu/Components/qmldir'
1404--- src/Ubuntu/Components/qmldir 2015-07-24 17:22:32 +0000
1405+++ src/Ubuntu/Components/qmldir 2015-07-29 12:04:55 +0000
1406@@ -107,6 +107,7 @@
1407 ActionBar 1.3 1.3/ActionBar.qml
1408 ActionItem 1.3 1.3/ActionItem.qml
1409 ActionList 1.3 1.3/ActionList.qml
1410+AdaptivePageLayout 1.3 1.3/AdaptivePageLayout.qml
1411 ToolbarItems 1.3 1.3/ToolbarItems.qml
1412 ToolbarButton 1.3 1.3/ToolbarButton.qml
1413 MainView 1.3 1.3/MainView.qml
1414@@ -141,6 +142,7 @@
1415 PullToRefresh 1.3 1.3/PullToRefresh.qml
1416 UbuntuListView 1.3 1.3/UbuntuListView11.qml
1417 Captions 1.3 1.3/Captions.qml
1418+internal ColumnMetrics 1.3/ColumnMetrics.qml
1419 MathUtils 1.3 1.3/mathUtils.js
1420 internal ColorUtils 1.3/colorUtils.js
1421 DateUtils 1.3 1.3/dateUtils.js
1422
1423=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/__init__.py'
1424--- tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-04-14 21:02:06 +0000
1425+++ tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-07-29 12:04:55 +0000
1426@@ -205,10 +205,10 @@
1427
1428 def checkPageHeader(self, pageTitle):
1429 orientationHelper = self.getOrientationHelper()
1430- header = orientationHelper.select_single("AppHeader", title=pageTitle)
1431- self.assertThat(header, Not(Is(None)))
1432- self.assertThat(header.visible, Eventually(Equals(True)))
1433- return header
1434+ header_label = orientationHelper.select_single(
1435+ objectName="header_title_label", text=pageTitle)
1436+ self.assertThat(header_label, Not(Is(None)))
1437+ self.assertThat(header_label.visible, Eventually(Equals(True)))
1438
1439 def getObject(self, objectName):
1440 obj = self.app.select_single(objectName=objectName)
1441
1442=== modified file 'tests/resources/navigation/MyCustomPage.qml'
1443--- tests/resources/navigation/MyCustomPage.qml 2015-03-03 13:20:06 +0000
1444+++ tests/resources/navigation/MyCustomPage.qml 2015-07-29 12:04:55 +0000
1445@@ -15,7 +15,7 @@
1446 */
1447
1448 import QtQuick 2.2
1449-import Ubuntu.Components 1.1
1450+import Ubuntu.Components 1.3
1451
1452 Page {
1453 title: i18n.tr("My custom page")
1454
1455=== added file 'tests/unit_x11/tst_components/ListItemWithLabel.qml'
1456--- tests/unit_x11/tst_components/ListItemWithLabel.qml 1970-01-01 00:00:00 +0000
1457+++ tests/unit_x11/tst_components/ListItemWithLabel.qml 2015-07-29 12:04:55 +0000
1458@@ -0,0 +1,30 @@
1459+/*
1460+ * Copyright 2015 Canonical Ltd.
1461+ *
1462+ * This program is free software; you can redistribute it and/or modify
1463+ * it under the terms of the GNU Lesser General Public License as published by
1464+ * the Free Software Foundation; version 3.
1465+ *
1466+ * This program is distributed in the hope that it will be useful,
1467+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1468+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1469+ * GNU Lesser General Public License for more details.
1470+ *
1471+ * You should have received a copy of the GNU Lesser General Public License
1472+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1473+ */
1474+
1475+import QtQuick 2.4
1476+import Ubuntu.Components 1.3
1477+
1478+ListItem {
1479+ property alias text: label.text
1480+ Label {
1481+ id: label
1482+ anchors {
1483+ left: parent.left
1484+ leftMargin: units.gu(2)
1485+ verticalCenter: parent.verticalCenter
1486+ }
1487+ }
1488+}
1489
1490=== modified file 'tests/unit_x11/tst_components/MyExternalPage.qml'
1491--- tests/unit_x11/tst_components/MyExternalPage.qml 2015-03-03 13:20:06 +0000
1492+++ tests/unit_x11/tst_components/MyExternalPage.qml 2015-07-29 12:04:55 +0000
1493@@ -1,5 +1,5 @@
1494 /*
1495- * Copyright 2012-2014 Canonical Ltd.
1496+ * Copyright 2012-2015 Canonical Ltd.
1497 *
1498 * This program is free software; you can redistribute it and/or modify
1499 * it under the terms of the GNU Lesser General Public License as published by
1500@@ -14,9 +14,13 @@
1501 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1502 */
1503
1504-import QtQuick 2.2
1505-import Ubuntu.Components 1.1
1506+import QtQuick 2.4
1507+import Ubuntu.Components 1.3
1508
1509 Page {
1510 title: "Page from QML file"
1511+ Label {
1512+ anchors.centerIn: parent
1513+ text: "This page was created from MyExternalPage.qml."
1514+ }
1515 }
1516
1517=== added file 'tests/unit_x11/tst_components/tst_adaptivepagelayout.qml'
1518--- tests/unit_x11/tst_components/tst_adaptivepagelayout.qml 1970-01-01 00:00:00 +0000
1519+++ tests/unit_x11/tst_components/tst_adaptivepagelayout.qml 2015-07-29 12:04:55 +0000
1520@@ -0,0 +1,187 @@
1521+/*
1522+ * Copyright 2015 Canonical Ltd.
1523+ *
1524+ * This program is free software; you can redistribute it and/or modify
1525+ * it under the terms of the GNU Lesser General Public License as published by
1526+ * the Free Software Foundation; version 3.
1527+ *
1528+ * This program is distributed in the hope that it will be useful,
1529+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1530+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1531+ * GNU Lesser General Public License for more details.
1532+ *
1533+ * You should have received a copy of the GNU Lesser General Public License
1534+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1535+ */
1536+
1537+import QtQuick 2.4
1538+import QtTest 1.0
1539+import Ubuntu.Test 1.0
1540+import Ubuntu.Components 1.3
1541+
1542+MainView {
1543+ id: root
1544+ width: units.gu(120)
1545+ height: units.gu(71)
1546+
1547+ // 2 on desktop, 1 on phone.
1548+ property int columns: width >= units.gu(80) ? 2 : 1
1549+
1550+ AdaptivePageLayout {
1551+ id: layout
1552+ width: parent.width
1553+ height: parent.height
1554+
1555+ primaryPage: page1
1556+
1557+ Page {
1558+ id: page1
1559+ title: "Page1"
1560+
1561+ Column {
1562+ anchors.centerIn: parent
1563+ width: childrenRect.width
1564+ Button {
1565+ text: "Page 2 left"
1566+ onTriggered: layout.addPageToCurrentColumn(page1, page2)
1567+ }
1568+ Button {
1569+ text: "Page 3 right"
1570+ onTriggered: layout.addPageToNextColumn(page1, page3);
1571+ }
1572+ }
1573+ }
1574+ Page {
1575+ id: page2
1576+ title: "Page2"
1577+ }
1578+ Page {
1579+ id: page3
1580+ title: "Page3"
1581+ }
1582+ Page {
1583+ id: page4
1584+ title: "Page4"
1585+ }
1586+ }
1587+
1588+ AdaptivePageLayout {
1589+ id: defaults
1590+ }
1591+
1592+ UbuntuTestCase {
1593+ when: windowShown
1594+
1595+ function resize_single_column() {
1596+ layout.width = units.gu(40);
1597+ }
1598+
1599+ // resize to use the full window width
1600+ function resize_multiple_columns() {
1601+ layout.width = root.width;
1602+ }
1603+
1604+ function cleanup() {
1605+ resize_multiple_columns();
1606+ layout.removePages(page1);
1607+ }
1608+
1609+ function test_0_API() {
1610+ compare(defaults.primaryPage, null, "primaryPage not null by default");
1611+ }
1612+
1613+ function test_zzz_change_primaryPage() {
1614+ // this prints the warning but still changes the primary page,
1615+ // so the test must be executed last not to mess up the other tests.
1616+ ignoreWarning("Cannot change primaryPage after completion.");
1617+ layout.primaryPage = page3;
1618+ }
1619+
1620+ function test_add_page_when_source_page_not_in_stack() {
1621+ ignoreWarning("sourcePage must be added to the view to add new page.");
1622+ layout.addPageToCurrentColumn(page2, page3);
1623+ ignoreWarning("sourcePage must be added to the view to add new page.");
1624+ layout.addPageToNextColumn(page2, page3);
1625+ }
1626+
1627+ function test_add_page_with_null_sourcePage() {
1628+ ignoreWarning("No sourcePage specified. Page will not be added.")
1629+ layout.addPageToCurrentColumn(null, page1);
1630+ ignoreWarning("No sourcePage specified. Page will not be added.")
1631+ layout.addPageToNextColumn(null, page2);
1632+ }
1633+
1634+ function test_add_same_page_twice() {
1635+ layout.addPageToCurrentColumn(page1, page2);
1636+ layout.addPageToCurrentColumn(page2, page3);
1637+ ignoreWarning("Cannot add a Page that was already added.");
1638+ layout.addPageToCurrentColumn(page3, page2);
1639+ ignoreWarning("Cannot add a Page that was already added.");
1640+ layout.addPageToNextColumn(page3, page2);
1641+ }
1642+
1643+ function test_page_visible() {
1644+ // Two columns on desktop, one on phone
1645+ compare(page1.visible, true, "Primary page not initially visible.");
1646+ compare(page2.visible, false, "Page 2 visible before it was added.");
1647+ compare(page3.visible, false, "Page 3 visible before it was added.");
1648+
1649+ layout.addPageToCurrentColumn(page1, page2);
1650+ compare(page1.visible, false, "Page still visible after adding new page in current column.");
1651+ compare(page2.visible, true, "Page invisible after adding it to current column.");
1652+ layout.addPageToNextColumn(page2, page3);
1653+ if (root.columns === 2) {
1654+ compare(page2.visible, true, "Page in first column became invisible after adding to next column.");
1655+ } else { // root.columns === 1
1656+ compare(page2.visible, false, "Page in single column still visible after adding page to next column.");
1657+ }
1658+ compare(page3.visible, true, "Page invisible after adding it to next column.");
1659+
1660+ // One column
1661+ resize_single_column();
1662+ compare(page3.visible, true, "Top page in last column invisible when resizing to one column.");
1663+ compare(page2.visible, false, "Top page in first column visible when resizing to one column.");
1664+
1665+ layout.removePages(page3);
1666+ compare(page3.visible, false, "Page 3 visible after it was removed.");
1667+ compare(page2.visible, true, "New top page in single column not visible.");
1668+
1669+ layout.removePages(page1);
1670+ compare(page1.visible, true, "Primary page not visible in single column.");
1671+ compare(page2.visible, false, "Page 2 visible while it is not added.");
1672+ layout.addPageToNextColumn(page1, page4);
1673+ compare(page1.visible, false, "Page remains visible after adding to next column in single column view.");
1674+ compare(page4.visible, true, "Page added to next column with single column view is not visible.");
1675+
1676+ // Two columns on desktop, one on phone
1677+ resize_multiple_columns();
1678+ if (root.columns === 2) {
1679+ compare(page1.visible, true, "Page in left column did not become visible when switching to multi-column view.");
1680+ compare(page4.visible, true, "Page in right column became invisible when switching to multi-column view.");
1681+ }
1682+ }
1683+
1684+ function test_add_to_current_prunes_next() {
1685+ layout.addPageToNextColumn(page1, page2);
1686+ compare(page2.visible, true, "Adding page to next column does not show that page.");
1687+ layout.addPageToCurrentColumn(page1, page3);
1688+ compare(page2.visible, false, "Adding page to current column does not clear next column.");
1689+ layout.removePages(page3);
1690+ compare(page2.visible, false, "Removing page from first column shows previous page in next column.");
1691+ }
1692+
1693+ function test_add_to_next_first_prunes_next() {
1694+ layout.addPageToNextColumn(page1, page2);
1695+ layout.addPageToNextColumn(page1, page3);
1696+ layout.removePages(page3);
1697+ compare(page2.visible, false, "Adding page to next column did not prune that column.");
1698+ }
1699+
1700+ function test_add_page_to_current_does_not_prune_current() {
1701+ layout.addPageToCurrentColumn(page1, page2);
1702+ layout.addPageToCurrentColumn(page2, page3);
1703+ layout.removePages(page3);
1704+ compare(page2.visible, true, "Adding page to current column pruned that column.");
1705+ }
1706+ }
1707+}
1708
1709=== added file 'tests/unit_x11/tst_components/tst_multicolumnheader.qml'
1710--- tests/unit_x11/tst_components/tst_multicolumnheader.qml 1970-01-01 00:00:00 +0000
1711+++ tests/unit_x11/tst_components/tst_multicolumnheader.qml 2015-07-29 12:04:55 +0000
1712@@ -0,0 +1,344 @@
1713+/*
1714+ * Copyright 2015 Canonical Ltd.
1715+ *
1716+ * This program is free software; you can redistribute it and/or modify
1717+ * it under the terms of the GNU Lesser General Public License as published by
1718+ * the Free Software Foundation; version 3.
1719+ *
1720+ * This program is distributed in the hope that it will be useful,
1721+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1722+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1723+ * GNU Lesser General Public License for more details.
1724+ *
1725+ * You should have received a copy of the GNU Lesser General Public License
1726+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1727+ */
1728+
1729+import QtQuick 2.4
1730+import Ubuntu.Test 1.0
1731+import Ubuntu.Components 1.3
1732+
1733+MainView {
1734+ id: root
1735+ width: units.gu(120)
1736+ height: units.gu(71)
1737+
1738+ // 2 on desktop, 1 on phone.
1739+ property int columns: width >= units.gu(80) ? 2 : 1
1740+
1741+ AdaptivePageLayout {
1742+ id: layout
1743+ width: parent.width
1744+ height: parent.height
1745+ primaryPage: rootPage
1746+
1747+ Page {
1748+ id: rootPage
1749+ title: "Root"
1750+
1751+ Column {
1752+ anchors {
1753+ top: parent.top
1754+ left: parent.left
1755+ right: parent.right
1756+ }
1757+ height: childrenRect.height
1758+
1759+ ListItemWithLabel {
1760+ text: "Add page left"
1761+ onClicked: layout.addPageToCurrentColumn(rootPage, leftPage)
1762+ }
1763+ ListItemWithLabel {
1764+ text: "Add page right"
1765+ onClicked: layout.addPageToNextColumn(rootPage, rightPage)
1766+ }
1767+ ListItemWithLabel {
1768+ text: "Add sections page right"
1769+ onClicked: layout.addPageToNextColumn(rootPage, sectionsPage)
1770+ }
1771+ ListItemWithLabel {
1772+ text: "Add external page right"
1773+ onClicked: layout.addPageToNextColumn(
1774+ rootPage, Qt.resolvedUrl("MyExternalPage.qml"))
1775+ }
1776+ }
1777+ }
1778+ Page {
1779+ id: leftPage
1780+ title: "First column"
1781+ Rectangle {
1782+ anchors {
1783+ fill: parent
1784+ margins: units.gu(2)
1785+ }
1786+ color: "orange"
1787+ Button {
1788+ anchors.centerIn: parent
1789+ text: "right"
1790+ onTriggered: layout.addPageToNextColumn(leftPage, rightPage)
1791+ }
1792+ }
1793+ }
1794+ Page {
1795+ id: rightPage
1796+ title: "Second column"
1797+ Rectangle {
1798+ anchors {
1799+ fill: parent
1800+ margins: units.gu(2)
1801+ }
1802+ color: "green"
1803+ Button {
1804+ anchors.centerIn: parent
1805+ text: "Another page!"
1806+ onTriggered: layout.addPageToCurrentColumn(rightPage, sectionsPage)
1807+ }
1808+ }
1809+ }
1810+ Page {
1811+ id: sectionsPage
1812+ title: "Page with sections"
1813+ head.sections.model: ["uno", "dos", "tres"]
1814+
1815+ Rectangle {
1816+ anchors {
1817+ fill: parent
1818+ margins: units.gu(2)
1819+ }
1820+ color: "blue"
1821+ }
1822+ }
1823+ }
1824+
1825+ UbuntuTestCase {
1826+ when: windowShown
1827+
1828+ function resize_single_column_width() {
1829+ layout.width = units.gu(40);
1830+ }
1831+
1832+ // resize to use the full window width
1833+ function resize_full_width() {
1834+ layout.width = root.width;
1835+ }
1836+
1837+ function get_number_of_columns() {
1838+ var body = findChild(layout, "body");
1839+ return body.children.length;
1840+ }
1841+
1842+ function get_header(column) {
1843+ return findChild(layout, "Header" + column);
1844+ }
1845+
1846+ function get_number_of_headers() {
1847+ // FIXME: With only one column, revert to using the AppHeader
1848+ // so layout will not include any headers.
1849+ var numHeaders = 0;
1850+ var header = get_header(0);
1851+ verify(header !== null, "No header found!");
1852+ while (header !== null) {
1853+ numHeaders++;
1854+ header = get_header(numHeaders);
1855+ }
1856+ return numHeaders;
1857+ }
1858+
1859+ function get_back_button_visible(column) {
1860+ var header = get_header(column);
1861+ var back_button = findChild(header, "backButton");
1862+ return back_button.visible;
1863+ }
1864+
1865+ function cleanup() {
1866+ layout.removePages(rootPage);
1867+ resize_full_width();
1868+ }
1869+
1870+ function test_number_of_headers_equals_number_of_columns_wide() {
1871+ if (root.columns !== 2) {
1872+ skip("Only for wide view.");
1873+ }
1874+ compare(get_number_of_columns(), 2, "Number of columns is not 2.");
1875+ compare(get_number_of_headers(), 2, "Number of headers is not 2.");
1876+ }
1877+
1878+ function test_number_of_headers_equals_number_of_columns_narrow() {
1879+ if (root.columns !== 1) {
1880+ resize_single_column_width();
1881+ }
1882+ compare(get_number_of_columns(), 1, "Number of columns is not 1 on narrow screen.");
1883+ compare(get_number_of_headers(), 1, "Number of headers is not 1 on narrow screen.");
1884+ }
1885+
1886+ function test_header_configuration_equals_column_page_configuration_wide() {
1887+ if (root.columns !== 2) {
1888+ skip("Only for wide view.");
1889+ }
1890+ compare(get_number_of_headers(), 2, "Number of headers is not 2 initially.");
1891+ compare(get_header(0).config, rootPage.head,
1892+ "First column header is not initialized with primaryPage header config.");
1893+ compare(get_header(1).config, null,
1894+ "Second column header is not initalized with null.");
1895+
1896+ layout.addPageToCurrentColumn(rootPage, leftPage);
1897+ compare(get_header(0).config, leftPage.head,
1898+ "First column header is not updated properly.");
1899+ compare(get_header(1).config, null,
1900+ "Second column header is updated when it should not be.");
1901+ layout.removePages(leftPage);
1902+ compare(get_header(0).config, rootPage.head,
1903+ "First column header is not reverted properly.");
1904+
1905+ layout.addPageToNextColumn(rootPage, rightPage);
1906+ compare(get_header(0).config, rootPage.head,
1907+ "First column header is updated when it should not be.");
1908+ compare(get_header(1).config, rightPage.head,
1909+ "Second column header is not updated properly.");
1910+ layout.removePages(rightPage);
1911+ compare(get_header(1).config, null,
1912+ "Second column header is not reverted properly.");
1913+ }
1914+
1915+ function test_header_configuration_equals_column_page_configuration_narrow() {
1916+ if (root.columns !== 1) {
1917+ resize_single_column_width();
1918+ }
1919+ compare(get_number_of_headers(), 1, "Number of headers is not 1.");
1920+ compare(get_header(0).config, rootPage.head,
1921+ "First column header is not initialized with primaryPage header config.");
1922+
1923+ layout.addPageToCurrentColumn(rootPage, leftPage);
1924+ compare(get_header(0).config, leftPage.head,
1925+ "Single column header is not updated properly.");
1926+ layout.removePages(leftPage);
1927+ compare(get_header(0).config, rootPage.head,
1928+ "Single column header is not reverted properly.");
1929+
1930+ layout.addPageToNextColumn(rootPage, rightPage);
1931+ compare(get_header(0).config, rightPage.head,
1932+ "Single column header is not updated properly when adding to next column.");
1933+ layout.removePages(rightPage);
1934+ compare(get_header(0).config, rootPage.head,
1935+ "Single column header is not reverted properly after adding to next column.");
1936+ }
1937+
1938+ function test_header_title_for_external_page() {
1939+ layout.addPageToNextColumn(rootPage, Qt.resolvedUrl("MyExternalPage.qml"));
1940+ var n = root.columns === 2 ? 1 : 0
1941+ compare(get_header(n).config.title, "Page from QML file",
1942+ "Adding external Page does not update the header title.");
1943+ }
1944+
1945+ function test_header_height() {
1946+ // contentHeight + divider height
1947+ var baseHeight = units.gu(6) + units.dp(1);
1948+ var withSectionsHeight = baseHeight + units.gu(4);
1949+ var n = get_number_of_headers();
1950+ var i;
1951+ for (i = 0; i < n; i++) {
1952+ compare(get_header(i).height, baseHeight,
1953+ "Header " + i + " height is not initialized correctly.");
1954+ }
1955+ layout.addPageToNextColumn(rootPage, rightPage);
1956+ for (i = 0; i < n; i++) {
1957+ compare(get_header(i).height, baseHeight,
1958+ "Header " + i + " height is incorrect after adding Page.");
1959+ }
1960+ layout.removePages(rightPage);
1961+ layout.addPageToNextColumn(rootPage, sectionsPage);
1962+ for (i = 0; i < n; i++) {
1963+ compare(get_header(i).height, withSectionsHeight,
1964+ "Header " + i + " height is incorrect after adding single Page with sections.");
1965+ }
1966+ layout.removePages(sectionsPage);
1967+ for (i = 0; i < n; i++) {
1968+ compare(get_header(i).height, baseHeight,
1969+ "Header " + i +
1970+ " height is not correctly reverted after removing Page with sections.");
1971+ }
1972+ }
1973+
1974+ function test_back_button_wide() {
1975+ if (root.columns !== 2) {
1976+ skip("Only for wide view.");
1977+ }
1978+ // A is the first column, B is the second column.
1979+ // A:i, B:j = i pages in A, j pages in B.
1980+
1981+ // primary page has no back button
1982+ // A:1, B:0
1983+ compare(get_back_button_visible(0), false,
1984+ "Back button is visible for primary page.");
1985+ layout.addPageToCurrentColumn(rootPage, leftPage);
1986+ // A:2, B:0
1987+ compare(get_back_button_visible(0), true,
1988+ "Adding page 2 to column A does not show back button.");
1989+
1990+ layout.removePages(leftPage);
1991+ // A:1, B:0
1992+ compare(get_back_button_visible(0), false,
1993+ "Removing page 2 from column A does not hide back button.");
1994+
1995+ layout.addPageToNextColumn(rootPage, rightPage);
1996+ // A:1, B:1
1997+ compare(get_back_button_visible(0), false,
1998+ "Adding page 1 to column B shows back button in column A.");
1999+ compare(get_back_button_visible(1), false,
2000+ "Adding page 1 to column B shows back button in column B.");
2001+
2002+ layout.addPageToCurrentColumn(rootPage, leftPage);
2003+ // A:2, B:0
2004+ compare(get_back_button_visible(0), true,
2005+ "Adding page 2 to column A not show back button when column B has a page.");
2006+ layout.removePages(leftPage);
2007+ // A:1, B:0
2008+
2009+ layout.addPageToNextColumn(rootPage, rightPage);
2010+ layout.addPageToCurrentColumn(rightPage, sectionsPage);
2011+ // A:1, B:2
2012+ compare(get_back_button_visible(0), false,
2013+ "Adding page 2 to column B shows back button in column A.");
2014+ compare(get_back_button_visible(1), true,
2015+ "Adding page 2 to column B does not show back button in column B.");
2016+
2017+ layout.addPageToCurrentColumn(rootPage, leftPage);
2018+ // A:2, B:0
2019+ layout.addPageToNextColumn(leftPage, rightPage);
2020+ layout.addPageToCurrentColumn(rightPage, sectionsPage);
2021+ compare(get_back_button_visible(0), true,
2022+ "Adding page 2 to column A does not show back button in column A when column B has 2 pages.");
2023+ compare(get_back_button_visible(1), true,
2024+ "Adding page 2 to column A hides back button in column B.");
2025+
2026+ layout.removePages(sectionsPage);
2027+ // A:2, B:1
2028+ compare(get_back_button_visible(0), true,
2029+ "Removing page 2 from column B hides back button in column A.");
2030+ compare(get_back_button_visible(1), false,
2031+ "Removing page 2 from column B does not hide back button when column A has 2 pages.");
2032+ }
2033+
2034+ function test_back_button_narrow() {
2035+ if (root.columns !== 1) {
2036+ resize_single_column_width();
2037+ }
2038+
2039+ compare(get_back_button_visible(0), false,
2040+ "Back button is visible for primary page.");
2041+ layout.addPageToCurrentColumn(rootPage, leftPage);
2042+ compare(get_back_button_visible(0), true,
2043+ "No back button visible with two pages in single column.");
2044+ layout.removePages(leftPage);
2045+ compare(get_back_button_visible(0), false,
2046+ "Back button remains visible after removing second page from column.");
2047+
2048+ layout.addPageToNextColumn(rootPage, rightPage);
2049+ compare(get_back_button_visible(0), true,
2050+ "No back button visible after pushing to next column when viewing single column.");
2051+ layout.removePages(rightPage);
2052+ compare(get_back_button_visible(0), false,
2053+ "Back button remains visible after removing page from following column.");
2054+ }
2055+ }
2056+}

Subscribers

People subscribed via source and target branches