Merge lp:~faenil/ubuntu-ui-toolkit/ContactsAdaptive_UIToolkit into lp:ubuntu-ui-toolkit/staging

Proposed by Andrea Bernabei
Status: Rejected
Rejected by: Zsombor Egri
Proposed branch: lp:~faenil/ubuntu-ui-toolkit/ContactsAdaptive_UIToolkit
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 2335 lines (+1712/-203)
27 files modified
components.api (+7/-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 (+7/-7)
examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml (+45/-125)
modules/Ubuntu/Components/1.2/stack.js (+1/-0)
modules/Ubuntu/Components/1.3/ColumnMetrics.qml (+53/-0)
modules/Ubuntu/Components/1.3/MultiColumnView.qml (+553/-0)
modules/Ubuntu/Components/1.3/OrientationHelper.qml (+1/-1)
modules/Ubuntu/Components/1.3/Page.qml (+1/-0)
modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml (+2/-0)
modules/Ubuntu/Components/1.3/PageWrapper.qml (+37/-0)
modules/Ubuntu/Components/1.3/stack.js (+75/-0)
modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml (+1/-1)
modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml (+51/-22)
modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml (+1/-1)
modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml (+362/-28)
modules/Ubuntu/Components/qmldir (+1/-0)
tests/autopilot/ubuntuuitoolkit/tests/__init__.py (+3/-4)
tests/resources/navigation/MyCustomPage.qml (+1/-1)
tests/resources/navigation/SplitViewStack.qml (+137/-0)
tests/unit_x11/tst_components/ListItemWithLabel.qml (+30/-0)
tests/unit_x11/tst_components/MyExternalPage.qml (+7/-3)
tests/unit_x11/tst_components/tst_components.pro (+2/-1)
tests/unit_x11/tst_components/tst_multicolumnheader.qml (+200/-0)
tests/unit_x11/tst_components/tst_multicolumnview.qml (+128/-0)
To merge this branch: bzr merge lp:~faenil/ubuntu-ui-toolkit/ContactsAdaptive_UIToolkit
Reviewer Review Type Date Requested Status
Zsombor Egri Disapprove
PS Jenkins bot continuous-integration Needs Fixing
Review via email: mp+264424@code.launchpad.net

Description of the change

Changes needed for UI Convergency prototype

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

This was a demo MR to convert AddressBook to use AdaptivePageLayout. No need to land.

review: Disapprove

Unmerged revisions

1566. By Andrea Bernabei

merge multiColumnView branch

1565. By Andrea Bernabei

OrientationHelper: check if a valid inputMethod exists before evaluating its props

1564. By Andrea Bernabei

merge

1563. By Andrea Bernabei

change default color of IconButtonStyle

1562. By Andrea Bernabei

change backgroundText in theme Palette

1561. By Andrea Bernabei

add white background to header

1560. By Andrea Bernabei

update backgroundText palette value

1559. By Andrea Bernabei

PageHeadStyle: take number of actions available into account when computing numberOfSlots

1558. By Andrea Bernabei

merge tims branch

1557. By Andrea Bernabei

Implement dynamic number of slots in PageHeadStyle

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'components.api'
--- components.api 2015-07-10 13:05:26 +0000
+++ components.api 2015-07-10 13:56:40 +0000
@@ -522,6 +522,12 @@
522Ubuntu.Components.Mouse.Priority: Enum522Ubuntu.Components.Mouse.Priority: Enum
523 AfterItem523 AfterItem
524 BeforeItem524 BeforeItem
525Ubuntu.Components.MultiColumnView 1.3: PageTreeNode
526 default readonly property QtObject data
527 function var addPageToCurrentColumn(var sourcePage, var page, var properties)
528 function var addPageToNextColumn(var sourcePage, var page, var properties)
529 function var removePages(var page)
530 property var primaryPage
525Ubuntu.Components.ListItems.MultiValue 1.0 0.1: Base531Ubuntu.Components.ListItems.MultiValue 1.0 0.1: Base
526 property var values532 property var values
527Ubuntu.Components.ListItems.MultiValue 1.3: Base533Ubuntu.Components.ListItems.MultiValue 1.3: Base
@@ -606,6 +612,7 @@
606 property bool locked612 property bool locked
607 property string preset613 property string preset
608 readonly property PageHeadSections sections614 readonly property PageHeadSections sections
615 property string title
609 property bool visible616 property bool visible
610Ubuntu.Components.PageHeadSections 1.1: QtObject617Ubuntu.Components.PageHeadSections 1.1: QtObject
611 property bool enabled618 property bool enabled
612619
=== modified file 'examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml'
--- examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-04-25 08:18:45 +0000
+++ examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-07-10 13:56:40 +0000
@@ -14,7 +14,7 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.217import QtQuick 2.4
18import Ubuntu.Components 1.318import Ubuntu.Components 1.3
1919
20ListItem {20ListItem {
2121
=== modified file 'examples/ubuntu-ui-toolkit-gallery/Popover.qml'
--- examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-04-29 08:55:31 +0000
+++ examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-07-10 13:56:40 +0000
@@ -21,17 +21,14 @@
21Template {21Template {
22 objectName: "popoversTemplate"22 objectName: "popoversTemplate"
2323
24 tools: ToolbarItems {24 head.actions: [
25 ToolbarButton {25 Action {
26 id: actionsButton
27 text: "Actions"26 text: "Actions"
28 iconSource: "call_icon.png"27 iconSource: "call_icon.png"
29 onTriggered: PopupUtils.open(actionSelectionPopover, actionsButton)28 onTriggered: PopupUtils.open(actionSelectionPopover)
30 visible: true29 visible: true
31 }30 }
32 locked: true31 ]
33 opened: true
34 }
3532
36 TemplateSection {33 TemplateSection {
37 className: "Popover"34 className: "Popover"
3835
=== modified file 'examples/ubuntu-ui-toolkit-gallery/Sections.qml'
--- examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-06-17 12:13:40 +0000
+++ examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-07-10 13:56:40 +0000
@@ -19,7 +19,7 @@
1919
20Template {20Template {
21 objectName: "sectionsTemplate"21 objectName: "sectionsTemplate"
2222 head.sections.model: ["first", "second", "third"]
23 TemplateSection {23 TemplateSection {
24 title: "Sections"24 title: "Sections"
25 className: "Sections"25 className: "Sections"
2626
=== modified file 'examples/ubuntu-ui-toolkit-gallery/Template.qml'
--- examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-04-25 08:18:45 +0000
+++ examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-07-10 13:56:40 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2013 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -14,19 +14,19 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.4
18import Ubuntu.Components 1.318import Ubuntu.Components 1.3
1919
20Item {20Page {
21 id: template21 id: template
2222
23 width: units.gu(40)23 head.backAction: Action {
24 height: units.gu(75)24 iconName: 'back'
25 onTriggered: columns.removePages(template)
26 }
2527
26 default property alias content: layout.children28 default property alias content: layout.children
27 property alias spacing: layout.spacing29 property alias spacing: layout.spacing
28 property Item tools: null
29 property Flickable flickable: flickable
3030
31 Flickable {31 Flickable {
32 id: flickable32 id: flickable
3333
=== modified file 'examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml'
--- examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-06-23 17:40:21 +0000
+++ examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-07-10 13:56:40 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2013 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -38,107 +38,51 @@
38 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft38 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
39 LayoutMirroring.childrenInherit: true39 LayoutMirroring.childrenInherit: true
4040
41 state: width >= units.gu(80) ? "wide" : "narrow"41 MultiColumnView {
42 states: [42 id: columns
43 State {43 anchors.fill: parent
44 name: "narrow"44 primaryPage: mainPage
45 StateChangeScript {45
46 script: {46 Page {
47 pageStack.push(mainPage);47 id: mainPage
48 if (selectedWidget) {48 title: "Ubuntu UI Toolkit"
49 pageStack.push(contentPage);49
50 }50 head.actions: [
51 }51 Action {
52 }52 text: i18n.tr('Use dark theme')
53 PropertyChanges {53 iconName: 'torch-on'
54 target: mainPage54 visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
55 flickable: widgetList55 onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
56 }56 },
57 PropertyChanges {57 Action {
58 target: contentPage58 text: i18n.tr('Use light theme')
59 flickable: contentLoader.item ? contentLoader.item.flickable : null59 iconName: 'torch-off'
60 }60 visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
61 },61 onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
62 State {62 }
63 name: "wide"63 ]
64 StateChangeScript {64
65 script: {65 Rectangle {
66 pageStack.clear();66 color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
67
68 /* When pushing Pages into a PageStack they are reparented
69 to internally created PageWrappers. This undoes it as to
70 allow us to anchor the Pages freely again.
71 */
72 mainPage.parent = gallery;
73 contentPage.parent = gallery;
74 }
75 }
76 PropertyChanges {
77 target: mainPage
78 width: units.gu(40)
79 clip: true
80 }
81 AnchorChanges {
82 target: mainPage
83 anchors.right: undefined
84 }
85 PropertyChanges {
86 target: contentPage
87 clip: true
88 }
89 AnchorChanges {
90 target: contentPage
91 anchors.left: mainPage.right
92 }
93 }
94 ]
95
96 property var selectedWidget: null
97
98 Page {
99 id: mainPage
100 active: selectedWidget == null
101
102 title: "Ubuntu UI Toolkit"
103 /* Page internally sets the topMargin of its flickable to account for
104 the height of the header. Undo it when unsetting the flickable.
105 */
106 onFlickableChanged: if (!flickable) widgetList.topMargin = 0;
107
108 head.actions: [
109 Action {
110 text: i18n.tr('Use dark theme')
111 iconName: 'torch-on'
112 visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
113 onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
114 },
115 Action {
116 text: i18n.tr('Use light theme')
117 iconName: 'torch-off'
118 visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
119 onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
120 }
121 ]
122
123 Rectangle {
124 color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
125 anchors.fill: parent
126
127 ListView {
128 id: widgetList
129 objectName: "widgetList"
130 anchors.fill: parent67 anchors.fill: parent
131 model: widgetsModel68
132 delegate: ListItem.Standard {69 ListView {
133 text: model.label70 id: widgetList
134 objectName: model.objectName71 objectName: "widgetList"
135 enabled: model.source != ""72 anchors.fill: parent
136 progression: true73 model: widgetsModel
137 selected: enabled && selectedWidget == model74 currentIndex: -1
138 onClicked: {75 delegate: ListItem.Standard {
139 selectedWidget = model;76 text: model.label
140 if (gallery.state == "narrow") {77 objectName: model.objectName
141 pageStack.push(contentPage);78 enabled: model.source != ""
79 progression: true
80 selected: index === widgetList.currentIndex
81 onClicked: {
82 var source = Qt.resolvedUrl(model.source);
83 var newPage = columns.addPageToNextColumn(mainPage, source);
84 newPage.title = model.label;
85 widgetList.currentIndex = index;
142 }86 }
143 }87 }
144 }88 }
@@ -146,30 +90,6 @@
146 }90 }
147 }91 }
14892
149 Page {
150 id: contentPage
151 active: selectedWidget != null
152 title: selectedWidget ? selectedWidget.label : ""
153 /* Page internally sets the topMargin of its flickable to account for
154 the height of the header. Undo it when unsetting the flickable.
155 */
156 onFlickableChanged: if (!flickable && contentLoader.item) contentLoader.item.flickable.topMargin = 0;
157 onActiveChanged: if (gallery.state == "narrow" && !active) {
158 selectedWidget = null;
159 }
160
161 Loader {
162 id: contentLoader
163 objectName: "contentLoader"
164 anchors.fill: parent
165 source: selectedWidget ? selectedWidget.source : ""
166 }
167 }
168
169 PageStack {
170 id: pageStack
171 }
172
173 WidgetsModel {93 WidgetsModel {
174 id: widgetsModel94 id: widgetsModel
175 }95 }
17696
=== modified file 'modules/Ubuntu/Components/1.2/stack.js'
--- modules/Ubuntu/Components/1.2/stack.js 2015-04-30 08:32:44 +0000
+++ modules/Ubuntu/Components/1.2/stack.js 2015-07-10 13:56:40 +0000
@@ -41,4 +41,5 @@
41 if (this.size() < 1) return undefined;41 if (this.size() < 1) return undefined;
42 return elements[elements.length-1];42 return elements[elements.length-1];
43 }43 }
44
44}45}
4546
=== added file 'modules/Ubuntu/Components/1.3/ColumnMetrics.qml'
--- modules/Ubuntu/Components/1.3/ColumnMetrics.qml 1970-01-01 00:00:00 +0000
+++ modules/Ubuntu/Components/1.3/ColumnMetrics.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,53 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18
19/*!
20 \qmltype ColumnMetrics
21 \inqmlmodule Ubuntu.Components 1.3
22 \since Ubuntu.Components 1.3
23 \ingroup ubuntu
24 \brief Component configuring the metrics of a column in MultiColumnView.
25 \internal
26
27 */
28QtObject {
29 /*!
30 1-based value identifying the column the metrics to be applied to.
31 */
32 property int column
33
34 /*!
35 Specifies whether the width of the column should fill the available space
36 of the MultiColumnView column or not. Defaults to \a false.
37 */
38 property bool fillWidth: false
39
40 /*!
41 Specifies the minimum width of the column. If the value is greater than
42 \b MultiColumnView::defaultColumnWidth, the value will be set as width for
43 the column.
44 */
45 property real minimumWidth: 0
46
47 /*!
48 Specifies the maximum width of the column. If the value is smaller than
49 \b MultiColumnView::defaultColumnWidth, the value will be set as width for
50 the column. A maximum value of 0 will be ignored.
51 */
52 property real maximumWidth: Number.POSITIVE_INFINITY
53}
054
=== added file 'modules/Ubuntu/Components/1.3/MultiColumnView.qml'
--- modules/Ubuntu/Components/1.3/MultiColumnView.qml 1970-01-01 00:00:00 +0000
+++ modules/Ubuntu/Components/1.3/MultiColumnView.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,553 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
20import "stack.js" as Stack
21
22/*!
23 \qmltype MultiColumnView
24 \inqmlmodule Ubuntu.Components 1.3
25 \since Ubuntu.Components 1.3
26 \ingroup ubuntu
27 \brief View with multiple columns of Pages.
28
29 The component provides a flexible way of viewing a stack of pages in one or
30 more columns. Unlike in PageStack, there can be more than one Page active at
31 a time, depending on the number of the columns in the view.
32
33 MultiColumnView stores pages added in a tree. Pages are added relative to a
34 given page, either as sibling (\l addPageToCurrentColumn) or as child
35 (\l addPageToNextColumn). This means that removing a non-leaf page from the Page
36 tree will remove all its children from the page tree.
37
38 The columns are populated from left to right. The column a page is added to is
39 detected based on the source page that is given to the functions adding the page.
40 The pages can be added either to the same column the source page resides or to
41 the column next to the source page. Giving a null value to the source page will
42 add the page to the leftmost column of the view.
43
44 The primary page, the very first page must be specified through the \l primaryPage
45 property. The property cannot be changed after component completion and can hold
46 a Page instance, a Component or a url to a document defining a Page. The page
47 cannot be removed from the view.
48
49 \note Unlike PageStack or Page the component does not fill its parent by default.
50
51 \qml
52 import QtQuick 2.4
53 import Ubuntu.Components 1.3
54
55 MainView {
56 width: units.gu(80)
57 height: units.gu(71)
58
59 MultiColumnView {
60 anchors.fill: parent
61 primaryPage: page1
62 Page {
63 id: page1
64 title: "Main page"
65 Column {
66 Button {
67 text: "Add Page2 above " + title
68 onClicked: page1.pageStack.addPageToCurrentColumn(page1, page2)
69 }
70 Button {
71 text: "Add Page3 next to " + title
72 onClicked: page1.pageStack.addPageToNextColumn(page1, page3)
73 }
74 }
75 }
76 Page {
77 id: page2
78 title: "Page #2"
79 }
80 Page {
81 id: page3
82 title: "Page #3"
83 }
84 }
85 }
86 \endqml
87
88 MultiColumnView supports adaptive column handling. When the number of columns changes at
89 runtime the pages are automatically rearranged. To understand it better, let's take the following example:
90 \qml
91 import QtQuick 2.4
92 import Ubuntu.Components 1.3
93
94 MainView {
95 width: units.gu(120)
96 height: units.gu(71)
97
98 MultiColumnView {
99 anchors.fill: parent
100 primaryPage: page1
101 Page {
102 id: page1
103 title: "Main page"
104 Button {
105 text: "Add Page2 next to " + title
106 onClicked: page1.pageStack.addPageToNextColumn(page1, page2)
107 }
108 }
109 Page {
110 id: page2
111 title: "Page #2"
112 Button {
113 text: "Add Page3 next to " + title
114 onClicked: page2.pageStack.addPageToNextColumn(page2, page3)
115 }
116 }
117 Page {
118 id: page3
119 title: "Page #3"
120 }
121 }
122 }
123 \endqml
124
125 When the code is run on sufficiently wide screen, like a desktop or TV,
126 it will launch with multiple columns.
127
128 \c page1 is set to be the primary page, \c page2 will be added to column next to
129 \c page1 (to column 2) and \c page3 next to \c page2 (column 3). When the window
130 is resized to have its size below 80 GU, the component will switch to 1 column
131 mode, and \c page3 will be placed in the last column, and the header for \c page2
132 will have a back button, indicating that there is a page below it. If the window
133 is resized to contain only one column, all pages will be shown in that column, so
134 the component will act as PageStack. Resizing the window back to 2 columns will place the pages side-by-side.
135
136 \note In the above example if \c page2 is removed, that will remove all its child
137 pages, meaning \c page3 will also be removed.
138
139 \sa PageStack
140*/
141
142PageTreeNode {
143 id: multiColumnView
144
145 Page {
146 // MultiColumnView has its own split headers, so
147 // disable the application header.
148 id: appHeaderControlPage
149 head {
150 locked: true
151 visible: false
152 }
153 // title is set in attachPage() when the attached Page.column === 0
154 }
155
156 /*!
157 The property holds the first Page which will be added to the view. If the
158 view has more than one column, the page will be added to the leftmost column.
159 The property can hold either a Page instance, a component holding a Page
160 or a QML document defining the Page. The property cannot be changed after
161 component completion.
162 */
163 property var primaryPage
164
165 /*!
166 \qmlmethod Item addPageToCurrentColumn(Item sourcePage, var page[, var properties])
167 Adds a \c page to the column the \c sourcePage resides in. \c page can be a
168 Component or a file. \c properties is a JSON object containing properties
169 to be set when page is created. \c sourcePage must be active. Returns the
170 instance of the page created.
171 */
172 function addPageToCurrentColumn(sourcePage, page, properties) {
173 return d.addPageToColumn(d.columnForPage(sourcePage), sourcePage, page, properties);
174 }
175
176 /*!
177 \qmlmethod Item addPageToNextColumn(Item sourcePage, var page[, var properties])
178 Same as \l addPageToCurrentColumn except that the \c page is added to the column
179 next to the one the \c sourcePage resides. If \c sourcePage is null, the new
180 page will be added to the leftmost column. If \c sourcePage is located in the
181 rightmost column, the new page will be pushed to the same column as \c sourcePage.
182 */
183 function addPageToNextColumn(sourcePage, page, properties) {
184 return d.addPageToColumn(d.columnForPage(sourcePage) + 1, sourcePage, page, properties);
185 }
186
187 /*!
188 \qmlmethod void removePages(Item page)
189 The function removes and deletes all pages up to and including \c page
190 is reached. If the \a page is the same as the \l primaryPage, only its child
191 pages will be removed.
192 */
193 function removePages(page) {
194 // remove nodes which have page as ascendant
195 var node = d.stack.top();
196 while (node && node.childOf(page)) {
197 d.popAndSetPageForColumn(node);
198 node = d.stack.top();
199 }
200 while (node && node.object == page && node != d.stack.first()) {
201 d.popAndSetPageForColumn(node);
202 node = d.stack.top();
203 }
204 }
205
206 /*
207 internals
208 */
209
210 Component.onCompleted: {
211 d.relayout();
212 d.completed = true;
213 if (primaryPage) {
214 var wrapper = d.createWrapper(primaryPage);
215 d.addWrappedPage(wrapper);
216 } else {
217 console.warn("No primary page set. No pages can be added without a primary page.");
218 }
219 }
220 onPrimaryPageChanged: {
221 if (d.completed) {
222 console.warn("Cannot change primaryPage after completion.");
223 return;
224 }
225 }
226
227 QtObject {
228 id: d
229
230 property bool completed: false
231 property var stack: new Stack.Stack()
232
233 property int columns: multiColumnView.width >= units.gu(80) ? 2 : 1
234 /*! internal */
235 onColumnsChanged: {
236 if (columns <= 0) {
237 console.warn("There must be a minimum of one column set.");
238 columns = 1;
239 }
240 d.relayout();
241 }
242 property real defaultColumnWidth: units.gu(40)
243 onDefaultColumnWidthChanged: body.applyMetrics()
244 property list<ColumnMetrics> columnMetrics
245
246 function createWrapper(page, properties) {
247 var wrapperComponent = Qt.createComponent("PageWrapper.qml");
248 var wrapperObject = wrapperComponent.createObject(hiddenPages);
249 wrapperObject.pageStack = multiColumnView;
250 wrapperObject.properties = properties;
251 // set reference last because it will trigger creation of the object
252 // with specified properties.
253 wrapperObject.reference = page;
254 return wrapperObject;
255 }
256
257 function addWrappedPage(pageWrapper) {
258 stack.push(pageWrapper);
259 pageWrapper.parentWrapper = stack.find(pageWrapper.parentPage);
260 var targetColumn = MathUtils.clamp(pageWrapper.column, 0, d.columns - 1);
261 // replace page holder's child
262 var holder = body.children[targetColumn];
263 holder.detachCurrentPage();
264 holder.attachPage(pageWrapper)
265 }
266
267 function columnForPage(page) {
268 var wrapper = stack.find(page);
269 return wrapper ? wrapper.column : 0;
270 }
271
272 function addPageToColumn(column, sourcePage, page, properties) {
273 if (column < 0) {
274 console.warn("Column must be >= 0.");
275 return;
276 }
277 if (!sourcePage) {
278 console.warn("No sourcePage specified. Page will not be added.");
279 return;
280 }
281 if (!d.stack.find(sourcePage)) {
282 console.warn("sourcePage must be added to the view to add new page.");
283 return;
284 }
285
286 var wrapper = d.createWrapper(page, properties);
287 wrapper.parentPage = sourcePage;
288 wrapper.column = column;
289 d.addWrappedPage(wrapper);
290 return wrapper.object;
291 }
292
293 // node is a triplet of {page, column, parentPage}
294 function popAndSetPageForColumn(node) {
295 stack.pop();
296 var effectiveColumn = MathUtils.clamp(node.column, 0, d.columns - 1);
297 var columnHolder = body.children[effectiveColumn];
298 // is the page in a column?
299 if (node == columnHolder.pageWrapper) {
300 // detach page from column
301 columnHolder.detachCurrentPage();
302 }
303 node.parent = null;
304 var prevPage = stack.topForColumn(effectiveColumn, effectiveColumn < d.columns - 1);
305 if (prevPage) {
306 columnHolder.attachPage(prevPage);
307 }
308 if (node.canDestroy) {
309 node.destroyObject();
310 }
311 }
312
313 // relayouts when column count changes
314 function relayout() {
315 if (body.children.length == d.columns) return;
316 if (body.children.length > d.columns) {
317 // need to remove few columns, the last ones
318 while (body.children.length > d.columns) {
319 var holder = body.children[body.children.length - 1];
320 holder.detachCurrentPage();
321 holder.parent = null;
322 holder.destroy();
323 }
324 } else {
325 var prevColumns = body.children.length;
326
327 // add columns
328 for (var i = 0; i < d.columns - prevColumns; i++) {
329 pageHolderComponent.createObject(body);
330 }
331 }
332 rearrangePages();
333 }
334
335 function rearrangePages() {
336 for (var column = d.columns - 1; column >= 0; column--) {
337 var holder = body.children[column];
338 var pageWrapper = stack.topForColumn(column, column < (d.columns - 1));
339 if (!pageWrapper) {
340 continue;
341 }
342 if (!pageWrapper.parent) {
343 // this should never happen, so if it does, we have a bug!
344 console.error("Found a page which wasn't parented anywhere!", pageWrapper.object.title);
345 continue;
346 }
347 // detach current page from holder if differs
348 if (holder.pageWrapper != pageWrapper) {
349 holder.detachCurrentPage();
350 }
351 if (pageWrapper.parent == hiddenPages) {
352 // add the page to the column
353 holder.attachPage(pageWrapper);
354 } else if (pageWrapper.pageHolder != holder) {
355 // detach the pageWrapper from its holder
356 if (pageWrapper.pageHolder) {
357 pageWrapper.pageHolder.detachCurrentPage();
358 }
359 // then attach to this holder
360 holder.attachPage(pageWrapper);
361 }
362 }
363 }
364 }
365
366 // default metrics
367 Component {
368 id: defaultMetrics
369 ColumnMetrics {
370 fillWidth: column == d.columns
371 minimumWidth: d.defaultColumnWidth
372 }
373 }
374
375 // Page holder component, can have only one Page as child at a time, all stacked pages
376 // will be parented into hiddenPages
377 Component {
378 id: pageHolderComponent
379 // Page uses the height of the parentNode for its height, so make
380 // the holder a PageTreeNode that determines the Page height.
381 PageTreeNode {
382 id: holder
383 active: false
384 objectName: "ColumnHolder" + column
385 property PageWrapper pageWrapper
386 property int column
387 property alias config: subHeader.config
388 property ColumnMetrics metrics: setDefaultMetrics()
389
390 Layout.fillWidth: metrics.fillWidth
391 Layout.fillHeight: true
392 Layout.preferredWidth: metrics.maximumWidth > 0 ?
393 MathUtils.clamp(d.defaultColumnWidth, metrics.minimumWidth, metrics.maximumWidth) :
394 d.defaultColumnWidth
395 Layout.minimumWidth: metrics.minimumWidth
396 Layout.maximumWidth: metrics.maximumWidth
397
398 // prevent the pages from taking the app header height into account.
399 __propagated: null
400 Item {
401 id: holderBody
402 objectName: parent.objectName + "Body"
403 anchors {
404 top: subHeader.bottom
405 bottom: parent.bottom
406 left: parent.left
407 right: parent.right
408 rightMargin: verticalDivider.width
409 }
410 // we need to clip because the header does not have a background
411 clip: true
412 }
413
414 property alias head: subHeader
415 StyledItem {
416 id: subHeader
417 anchors {
418 left: parent.left
419 top: parent.top
420 right: parent.right
421 }
422 height: body.headerHeight
423
424 styleName: config ? "PageHeadStyle" : ""
425 theme.version: Ubuntu.toolkitVersion
426 objectName: "Header" + column
427
428 property real preferredHeight: subHeader.__styleInstance ?
429 subHeader.__styleInstance.implicitHeight :
430 0
431 onPreferredHeightChanged: {
432 body.updateHeaderHeight(preferredHeight);
433 }
434
435 property PageHeadConfiguration config: null
436 property Item contents: null
437
438 property color dividerColor: multiColumnView.__propagated.header.dividerColor
439 property color panelColor: multiColumnView.__propagated.header.panelColor
440
441 visible: holder.pageWrapper && holder.pageWrapper.active
442 }
443
444 Rectangle {
445 id: verticalDivider
446 anchors {
447 top: parent.top
448 bottom: parent.bottom
449 right: parent.right
450 }
451 width: (column == (d.columns - 1)) || !pageWrapper ? 0 : units.dp(1)
452 color: subHeader.dividerColor
453 }
454
455 function attachPage(page) {
456 pageWrapper = page;
457 pageWrapper.parent = holderBody;
458 pageWrapper.pageHolder = holder;
459 pageWrapper.active = true;
460
461 if (pageWrapper.object.hasOwnProperty("head")) {
462 subHeader.config = pageWrapper.object.head;
463 }
464 if (pageWrapper.column === 0 && pageWrapper.object.hasOwnProperty("title")) {
465 // set the application title
466 appHeaderControlPage.title = pageWrapper.object.title;
467 }
468 }
469 function detachCurrentPage() {
470 if (!pageWrapper) return undefined;
471 var wrapper = pageWrapper;
472 // remove header
473 wrapper.active = false;
474 subHeader.config = null;
475 pageWrapper = null;
476 wrapper.parent = hiddenPages;
477 wrapper.pageHolder = null;
478 return wrapper;
479 }
480
481 function setDefaultMetrics() {
482 var result = defaultMetrics.createObject(holder);
483 result.column = Qt.binding(function() { return holder.column + 1; });
484 return result;
485 }
486 }
487 }
488
489 /*! \internal */
490 // Pages declared as children will be placed directly into hiddenPages
491 default property alias data: hiddenPages.data
492 Item {
493 id: hiddenPages
494 objectName: "HiddenPagePool"
495 visible: false
496 // make sure nothing is shown eventually
497 clip: true
498 }
499
500 // Holds the columns holding the pages visible. Each column has only one page
501 // as child, the invisible stacked ones are all stored in the hiddenPages
502 // component. The stack keeps the column index onto which those should be moved
503 // once they become visible.
504 RowLayout {
505 id: body
506 objectName: "body"
507 anchors.fill: parent
508 spacing: 0
509
510 property real headerHeight: 0
511
512 function updateHeaderHeight(newHeight) {
513 if (newHeight > body.headerHeight) {
514 body.headerHeight = newHeight;
515 } else {
516 var h = 0;
517 var subHeight = 0;
518 for (var i = 0; i < children.length; i++) {
519 subHeight = children[i].head.preferredHeight;
520 if (subHeight > h) h = subHeight;
521 }
522 body.headerHeight = h;
523 }
524 }
525
526 onChildrenChanged: {
527 // all children should have Layout.fillWidth false, except the last one
528 for (var i = 0; i < children.length; i++) {
529 children[i].column = i;
530 }
531 applyMetrics();
532 }
533
534 function applyMetrics() {
535 for (var i = 0; i < children.length; i++) {
536 var holder = children[i];
537 // search for the column metrics
538 var metrics = null;
539 for (var j = 0; j < d.columnMetrics.length; j++) {
540 if (d.columnMetrics[j].column == (i + 1)) {
541 metrics = d.columnMetrics[j];
542 break;
543 }
544 }
545 if (!metrics) {
546 metrics = holder.setDefaultMetrics();
547 }
548 holder.metrics = metrics;
549 updateHeaderHeight(0);
550 }
551 }
552 }
553}
0554
=== modified file 'modules/Ubuntu/Components/1.3/OrientationHelper.qml'
--- modules/Ubuntu/Components/1.3/OrientationHelper.qml 2015-07-02 17:39:03 +0000
+++ modules/Ubuntu/Components/1.3/OrientationHelper.qml 2015-07-10 13:56:40 +0000
@@ -117,7 +117,7 @@
117 return 0;117 return 0;
118118
119 var availableHeight = orientationHelper.parent.height;119 var availableHeight = orientationHelper.parent.height;
120 if (d.stateAngle === 0 && UbuntuApplication.inputMethod.visible && anchorToKeyboard)120 if (d.stateAngle === 0 && UbuntuApplication.inputMethod && UbuntuApplication.inputMethod.visible && anchorToKeyboard)
121 availableHeight -= UbuntuApplication.inputMethod.keyboardRectangle.height;121 availableHeight -= UbuntuApplication.inputMethod.keyboardRectangle.height;
122 return availableHeight;122 return availableHeight;
123 }123 }
124124
=== modified file 'modules/Ubuntu/Components/1.3/Page.qml'
--- modules/Ubuntu/Components/1.3/Page.qml 2015-07-02 20:40:01 +0000
+++ modules/Ubuntu/Components/1.3/Page.qml 2015-07-10 13:56:40 +0000
@@ -43,6 +43,7 @@
43 readonly property alias head: headerConfig43 readonly property alias head: headerConfig
44 Toolkit13.PageHeadConfiguration {44 Toolkit13.PageHeadConfiguration {
45 id: headerConfig45 id: headerConfig
46 title: page.title
46 }47 }
4748
48 Toolkit13.Object {49 Toolkit13.Object {
4950
=== modified file 'modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml'
--- modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-04-30 08:32:44 +0000
+++ modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-07-10 13:56:40 +0000
@@ -56,4 +56,6 @@
5656
57 // auto-updated by AppHeader, but may be set by the developer57 // auto-updated by AppHeader, but may be set by the developer
58 property bool visible58 property bool visible
59
60 property string title
59}61}
6062
=== modified file 'modules/Ubuntu/Components/1.3/PageWrapper.qml'
--- modules/Ubuntu/Components/1.3/PageWrapper.qml 2015-05-05 16:23:29 +0000
+++ modules/Ubuntu/Components/1.3/PageWrapper.qml 2015-07-10 13:56:40 +0000
@@ -46,6 +46,43 @@
46 property bool canDestroy: false46 property bool canDestroy: false
4747
48 /*!48 /*!
49 Column number in MultiColumnView.
50 */
51 property int column: 0
52
53 /*!
54 Parent page.
55 */
56 property Item parentPage
57
58 /*!
59 Parent PageWrapper or the parentPage.
60 */
61 property Item parentWrapper
62
63 /*!
64 Page holder in MultiColumnView
65 */
66 property Item pageHolder
67
68 /*!
69 Returns true if the current PageWrapper is a child of the given page
70 */
71 function childOf(page) {
72 if (parentPage == page) return true;
73 if (page && parentWrapper) {
74 var wrapper = parentWrapper;
75 while (wrapper) {
76 if (wrapper.object == page) {
77 return true;
78 }
79 wrapper = wrapper.parentWrapper;
80 }
81 }
82 return false;
83 }
84
85 /*!
49 This value is updated when a PageWrapper is pushed to/popped from a PageStack.86 This value is updated when a PageWrapper is pushed to/popped from a PageStack.
50 */87 */
51 active: false88 active: false
5289
=== added file 'modules/Ubuntu/Components/1.3/stack.js'
--- modules/Ubuntu/Components/1.3/stack.js 1970-01-01 00:00:00 +0000
+++ modules/Ubuntu/Components/1.3/stack.js 2015-07-10 13:56:40 +0000
@@ -0,0 +1,75 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17.pragma library
18// By defining Stack as a function, we can make its variables private,
19// and force calls to Stack to make use of the functions we define.
20function Stack() {
21 var elements;
22 this.clear = function() {
23 elements = [];
24 }
25
26 this.clear();
27
28 this.push = function(element) {
29 elements.push(element);
30 };
31
32 this.pop = function() {
33 elements.pop();
34 };
35
36 this.size = function() {
37 return elements.length;
38 }
39
40 this.first = function() {
41 return this.size() > 0 ? elements[0] : null;
42 }
43
44 this.top = function() {
45 if (this.size() < 1) return undefined;
46 return elements[elements.length-1];
47 }
48
49 // returns the topmost element for the column, using exact column match
50 // in case the column to be extracted is less than the columns available
51 this.topForColumn = function(column, exactMatch) {
52 if (exactMatch == undefined) {
53 exactMatch = false;
54 }
55
56 for (var i = elements.length - 1; i >= 0; i--) {
57 var node = elements[i];
58 if ((exactMatch && elements[i].column == column) || (!exactMatch && elements[i].column >= column)) {
59 return elements[i];
60 }
61 }
62 return undefined;
63 }
64
65 // returns the node the Page is stored; undefined if not found
66 this.find = function(page) {
67 if (!page) return null;
68 for (var i = elements.length - 1; i >= 0; i--) {
69 if (elements[i].object == page) {
70 return elements[i];
71 }
72 }
73 return null;
74 }
75}
076
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml 2015-05-26 10:54:14 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml 2015-07-10 13:56:40 +0000
@@ -26,7 +26,7 @@
26 /*!26 /*!
27 The color of the icons.27 The color of the icons.
28 */28 */
29 property color foregroundColor: "#808080"29 property color foregroundColor: "#333333"
3030
31 /*!31 /*!
32 The background color of the button.32 The background color of the button.
3333
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-06-23 13:22:10 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-07-10 13:56:40 +0000
@@ -27,15 +27,22 @@
27 textLeftMargin: units.gu(2)27 textLeftMargin: units.gu(2)
28 maximumNumberOfActions: 328 maximumNumberOfActions: 3
2929
30 PageHeadConfiguration {
31 id: defaultConfig
32 }
33
34 property PageHeadConfiguration config: styledItem.config ?
35 styledItem.config :
36 defaultConfig
30 /*!37 /*!
31 The color of the buttons in the header.38 The color of the buttons in the header.
32 */39 */
33 property color buttonColor: styledItem.config.foregroundColor40 property color buttonColor: headerStyle.config.foregroundColor
3441
35 /*!42 /*!
36 The color of the title text.43 The color of the title text.
37 */44 */
38 property color titleColor: styledItem.config.foregroundColor45 property color titleColor: headerStyle.config.foregroundColor
3946
40 // FIXME: When the three panel color properties below are removed,47 // FIXME: When the three panel color properties below are removed,
41 // update unity8/Dash/PageHeader to use the new theming (currently48 // update unity8/Dash/PageHeader to use the new theming (currently
@@ -84,6 +91,12 @@
84 // have a separator.91 // have a separator.
85 property alias __separator_visible: divider.visible92 property alias __separator_visible: divider.visible
8693
94 //Default background for the header is white according to latest visuals
95 Rectangle {
96 anchors.fill: parent
97 color: "white"
98 }
99
87 Rectangle {100 Rectangle {
88 id: divider101 id: divider
89 anchors {102 anchors {
@@ -100,16 +113,23 @@
100 objectName: "headerSectionsItem"113 objectName: "headerSectionsItem"
101 anchors {114 anchors {
102 left: parent.left115 left: parent.left
103 leftMargin: units.gu(2)116 right: parent.right
117 //leftMargin: units.gu(2)
104 bottom: divider.top118 bottom: divider.top
105 }119 }
120 visible: model && model.length > 0
106 enabled: sections.enabled121 enabled: sections.enabled
107 height: model && model.length > 0 ? implicitHeight : 0122 height: visible ? implicitHeight : 0
108123
109 property PageHeadSections sections: styledItem.config.sections124 property PageHeadSections sections: headerStyle.config.sections
110 model: sections.model125 model: sections ? sections.model : null
111126
112 onSelectedIndexChanged: sections.selectedIndex = sectionsItem.selectedIndex127 onSelectedIndexChanged: {
128 if (sections) {
129 sections.selectedIndex = sectionsItem.selectedIndex;
130 }
131 }
132
113 Connections {133 Connections {
114 target: sectionsItem.sections134 target: sectionsItem.sections
115 onSelectedIndexChanged: sectionsItem.selectedIndex = sectionsItem.sections.selectedIndex135 onSelectedIndexChanged: sectionsItem.selectedIndex = sectionsItem.sections.selectedIndex
@@ -244,10 +264,10 @@
244 PageHeadButton {264 PageHeadButton {
245 id: customBackButton265 id: customBackButton
246 objectName: "customBackButton"266 objectName: "customBackButton"
247 action: styledItem.config.backAction267 action: headerStyle.config.backAction
248 visible: null !== styledItem.config.backAction &&268 visible: null !== headerStyle.config.backAction &&
249 styledItem.config.backAction.visible269 headerStyle.config.backAction.visible
250 color: styledItem.config.foregroundColor270 color: headerStyle.config.foregroundColor
251 }271 }
252272
253 PageHeadButton {273 PageHeadButton {
@@ -258,10 +278,10 @@
258 visible: styledItem.pageStack !== null &&278 visible: styledItem.pageStack !== null &&
259 styledItem.pageStack !== undefined &&279 styledItem.pageStack !== undefined &&
260 styledItem.pageStack.depth > 1 &&280 styledItem.pageStack.depth > 1 &&
261 !styledItem.config.backAction281 !headerStyle.config.backAction
262282
263 text: "back"283 text: "back"
264 color: styledItem.config.foregroundColor284 color: headerStyle.config.foregroundColor
265285
266 onTriggered: {286 onTriggered: {
267 styledItem.pageStack.pop();287 styledItem.pageStack.pop();
@@ -327,13 +347,13 @@
327 Label {347 Label {
328 objectName: "header_title_label"348 objectName: "header_title_label"
329 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft349 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
330 visible: !contentsContainer.visible && styledItem.config.preset === ""350 visible: !contentsContainer.visible && headerStyle.config.preset === ""
331 anchors {351 anchors {
332 left: parent.left352 left: parent.left
333 right: parent.right353 right: parent.right
334 verticalCenter: parent.verticalCenter354 verticalCenter: parent.verticalCenter
335 }355 }
336 text: styledItem.title356 text: headerStyle.config.title
337 font.weight: headerStyle.fontWeight357 font.weight: headerStyle.fontWeight
338 fontSize: headerStyle.fontSize358 fontSize: headerStyle.fontSize
339 color: headerStyle.titleColor359 color: headerStyle.titleColor
@@ -346,7 +366,7 @@
346 // when the bindings below is no longer active366 // when the bindings below is no longer active
347 id: contentsContainer367 id: contentsContainer
348 anchors.fill: parent368 anchors.fill: parent
349 visible: styledItem.contents || styledItem.config.contents369 visible: styledItem.contents || headerStyle.config.contents
350 }370 }
351 Binding {371 Binding {
352 target: styledItem.contents372 target: styledItem.contents
@@ -361,16 +381,23 @@
361 when: styledItem.contents381 when: styledItem.contents
362 }382 }
363 Binding {383 Binding {
364 target: styledItem.config.contents384 target: headerStyle.config.contents
365 property: "parent"385 property: "parent"
366 value: contentsContainer386 value: contentsContainer
367 when: styledItem.config.contents && !styledItem.contents387 when: headerStyle.config.contents && !styledItem.contents
368 }388 }
369 }389 }
370390
371 ActionBar {391 ActionBar {
372 id: actionsContainer392 id: actionsContainer
373 objectName: "headerActionBar"393 objectName: "headerActionBar"
394
395 //currently hardcoded maximum number of actions we want to show in the header
396 property int maxActions: 6
397
398 //UX: we reserver max 30% of the header width for actions
399 property real headerPortionDedicatedToActions: 0.3
400
374 anchors {401 anchors {
375 top: parent.top402 top: parent.top
376 right: rightAnchor.left403 right: rightAnchor.left
@@ -378,8 +405,10 @@
378 }405 }
379 height: headerStyle.contentHeight406 height: headerStyle.contentHeight
380407
381 actions: styledItem.config.actions408 actions: headerStyle.config.actions
382 numberOfSlots: 3409
410 //FIXME: currently hardcoded icon width!
411 numberOfSlots: Math.min(Math.min(Math.floor(headerStyle.width * headerPortionDedicatedToActions / units.gu(4)), actions.length), maxActions)
383 }412 }
384 }413 }
385}414}
386415
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml 2015-04-27 07:19:22 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml 2015-07-10 13:56:40 +0000
@@ -33,7 +33,7 @@
33 }33 }
34 selected {34 selected {
35 background: Qt.rgba(0, 0, 0, 0.05)35 background: Qt.rgba(0, 0, 0, 0.05)
36 backgroundText: UbuntuColors.darkGrey36 backgroundText: "#333333"//"#5D5D5D"//UbuntuColors.darkGrey
37 selection: selected.foreground37 selection: selected.foreground
38 foreground: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.2)38 foreground: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.2)
39 foregroundText: UbuntuColors.darkGrey39 foregroundText: UbuntuColors.darkGrey
4040
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-06-16 23:18:45 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-07-10 13:56:40 +0000
@@ -1,3 +1,20 @@
1/* !!!!!!!!!! THIS IS A WIP SECTION HEADER !!!!!!!!!!!!!!!
2
3 * Copyright 2015 Canonical Ltd.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
1/*18/*
2 * Copyright 2015 Canonical Ltd.19 * Copyright 2015 Canonical Ltd.
3 *20 *
@@ -15,20 +32,33 @@
15 */32 */
16import QtQuick 2.433import QtQuick 2.4
17import Ubuntu.Components 1.334import Ubuntu.Components 1.3
35import QtGraphicalEffects 1.0
1836
19Item {37Item {
20 id: sectionsStyle38 id: sectionsStyle
2139
22 implicitWidth: sectionsRow.width40 //VISUAL CHANGES:
41 //- sectionColor
42 //- added underlineColor
43 //- fontsize to medium
44 //- font weight to Light
45
46 implicitWidth: units.gu(50) //sectionsRow.width
23 implicitHeight: units.gu(4)47 implicitHeight: units.gu(4)
2448
25 enabled: styledItem.enabled49 enabled: styledItem.enabled
26 opacity: enabled ? 1.0 : 0.550 opacity: enabled ? 1.0 : 0.5
27
28 /*!51 /*!
29 The foreground color of unselected sections.52 The foreground color of unselected sections.
30 */53 */
31 property color sectionColor: theme.palette.selected.backgroundText54 //FIXME: hardcoded color
55 property color sectionColor: "#888888"//theme.palette.selected.backgroundText
56
57 /*!
58 The foreground color of underline rectangle of unselected sections.
59 */
60 //FIXME: hardcoded color
61 property color underlineColor: Qt.rgba(0,0,0,0.2)//theme.palette.selected.backgroundText
3262
33 /*!63 /*!
34 The foreground color of the selected section.64 The foreground color of the selected section.
@@ -43,7 +73,7 @@
43 /*!73 /*!
44 The font size for the text in the buttons.74 The font size for the text in the buttons.
45 */75 */
46 property string fontSize: "small"76 property string fontSize: "medium"
4777
48 /*!78 /*!
49 The spacing on the left and right sides of the label79 The spacing on the left and right sides of the label
@@ -56,30 +86,174 @@
56 */86 */
57 property real underlineHeight: units.dp(2)87 property real underlineHeight: units.dp(2)
5888
59 Row {89 property real __listViewMargin: units.gu(3)
60 id: sectionsRow90 property bool __hoveringLeft: false
61 anchors {91 property bool __hoveringRight: false
62 top: parent.top92
63 bottom: parent.bottom93 onStateChanged: console.log(state)
64 horizontalCenter: parent.horizontalCenter94
65 }95 clip: true
66 width: childrenRect.width96
6797 //This item is needed for the OpacityMask feature. It is needed to make sure that when we
68 Repeater {98 //bring a list element into view, that element won't be covered by the opacity mask. So we
69 id: sectionsRepeater99 //disable clipping on the list but we give it margins. This way when an item is repositioned
100 //to be within the listview, that item will not be positioned under the opacity mask. (which is
101 //what would have happened if the listview were filling the parent)
102 Item {
103 id: listviewcontainer
104 anchors.fill: parent
105
106 //We need this because OpacityMask will draw this listview for us, but at the
107 //same time we still want to get the input events!
108 opacity: 0
109
110 ListView {
111 id: sectionsListView
112 objectName: "section_listview"
113
114 property bool animateContentX: false
115
116 //this is just to disable keyboard navigation to avoid messing with contentX/contentWidth while
117 //the view is moving
118 focus: !moving
119
120 SmoothedAnimation {
121 id: contentXAnim
122 target: sectionsListView
123 property: "contentX"
124 duration: UbuntuAnimation.FastDuration
125 velocity: units.gu(10)
126 //alwaysRunToEnd: true
127 }
128
129 function ensureItemIsInTheMiddle(currentItem) {
130 if (currentItem !== null) {
131 //stop the flick before doing computations
132 if (moving) {
133 return
134 }
135 if (dragging || flicking) {
136 cancelFlick()
137 }
138
139 console.log("CONTENTX", contentX)
140 if (contentXAnim.running) { contentXAnim.stop() }
141 console.log("CONTENTXAFTER", contentX)
142
143 var pos = currentItem.mapToItem(sectionsListView.contentItem, 0, 0)
144
145 //In the case where we're moving to an item which is outside of the screen (this
146 //happens when using Keyboard navigation after scrolling the view), we
147 //try working around the fact that contentWidth/X keeps changing
148 //by using listview's functions (WHICH DON'T HAVE ANIMATIONS though) just to position the
149 //view more or less at the right place. After that, we'll fake an animation from one side to the middle of the
150 //screen.
151 /*if (pos.x + pos.width <= sectionsListView.x) {
152 positionViewAtIndex(currentIndex, ListView.Beginning)
153 //refresh the mapping as we've moved the view
154 pos = currentItem.mapToItem(sectionsListView, 0, 0)
155
156 } else if (pos.x >= sectionsListView.x + sectionsListView.width) {
157 positionViewAtIndex(currentIndex, ListView.End)
158 //refresh the mapping as we've moved the view
159 pos = currentItem.mapToItem(sectionsListView, 0, 0)
160 }*/
161
162 var newContentX = pos.x - sectionsListView.width/2 + currentItem.width/2
163 contentXAnim.from = contentX
164 //make sure we don't overshoot bounds
165 contentXAnim.to = Math.max(originX, Math.min(newContentX, originX + contentWidth - width))
166 contentXAnim.start()
167
168 console.log("OUR VALUES", contentX, originX, contentWidth, contentXAnim.to)
169 //FIXME: due to listview internal implementation and the fact that delegates don't have a fixed size,
170 //this breaks in some cases, due to originX changing *after* we start the
171 //animation. In those cases the current item doesn't align to the horizontal center because we compute
172 //contentXAnim.to based on an originX which is then changed by the internals after we have already
173 //started the animation. We could fixup the position at the end of the animation,
174 //but that would just be a workaround.
175 }
176 }
177
178 onContentXChanged: console.log(contentX, originX, contentWidth)
179
180 anchors.fill: parent
181 anchors.leftMargin: __listViewMargin
182 anchors.rightMargin: __listViewMargin
183
184 //FIXME: this means when we resize the window it will refocus on the current item even if it's outside of the view!
185 //NOTE: removing this also breaks the alignment when the sections are initialized, because of contentX/contentWidth changing
186 onWidthChanged: ensureItemIsInTheMiddle(currentItem)
187
188 orientation: ListView.Horizontal
189
190 //We want to block dragging but at the same time we want the keyboard to work!!!
191 //interactive: contentWidth > width
192 boundsBehavior: Flickable.StopAtBounds
193
70 model: styledItem.model194 model: styledItem.model
71 objectName: "sections_repeater"195
72 AbstractButton {196 //We need this to make sure that we have delegates for the whole width, since we have
197 //clip disabled. Read more
198 displayMarginBeginning: __listViewMargin
199 displayMarginEnd: __listViewMargin
200
201 //make sure that the currentItem is in the middle when everything is initialized
202 Component.onCompleted: ensureItemIsInTheMiddle(currentItem)
203
204 //FIXME: keyboard navigation offered by ListView will break this, won't it?
205 currentIndex: styledItem.selectedIndex
206 onCurrentIndexChanged: {
207 styledItem.selectedIndex = currentIndex
208 }
209 onCurrentItemChanged: {
210 //adjust contentX so that the item is kept in the middle
211 //don't use ListView.ApplyRange because that does an awkward animation when you select an item
212 //*while* the current item is outside of screen
213 ensureItemIsInTheMiddle(currentItem)
214 }
215
216 //highlightMoveDuration: UbuntuAnimation.BriskDuration
217 //highlightResizeDuration: UbuntuAnimation.BriskDuration
218
219 highlightFollowsCurrentItem: false
220 //DON'T use ApplyRange, because it will cause a weird animation when you select an item which is on screen
221 //with the previously selected item being out of screen
222 //highlightRangeMode: ListView.ApplyRange
223 //preferredHighlightEnd: width/2 + currentItem.width/2
224 //preferredHighlightBegin: width/2 - currentItem.width/2
225
226 //highlightFollowsCurrentItem will resize the highlight element to fill the delegate,
227 //so we need an Item in the middle to set a height for the highlight rectangle different
228 //from the delegate size
229 highlight: Item {
230 //show the highlight on top of the delegate
231 z: 2
232
233 x: sectionsListView.currentItem ? sectionsListView.currentItem.x : -width
234 width: sectionsListView.currentItem ? sectionsListView.currentItem.width : 0
235 height: sectionsListView.currentItem ? sectionsListView.currentItem.height : 0
236
237 Rectangle {
238 anchors {
239 bottom: parent.bottom
240 left: parent.left
241 right: parent.right
242 }
243 height: sectionsStyle.underlineHeight
244 color: sectionsStyle.selectedSectionColor
245 }
246
247 Behavior on x { UbuntuNumberAnimation {} }
248 }
249
250 delegate: AbstractButton {
73 id: sectionButton251 id: sectionButton
74 anchors {
75 top: parent ? parent.top : undefined
76 bottom: parent ? parent.bottom : undefined
77 }
78 objectName: "section_button_" + index252 objectName: "section_button_" + index
79 width: label.width + 2 * sectionsStyle.horizontalLabelSpacing253 width: label.width + 2 * sectionsStyle.horizontalLabelSpacing
80 height: sectionsRow.height254 height: sectionsStyle.height
81 property bool selected: index === styledItem.selectedIndex255 property bool selected: index === styledItem.selectedIndex
82 onClicked: styledItem.selectedIndex = index256 onClicked: { styledItem.selectedIndex = index; sectionsListView.forceActiveFocus() }
83257
84 // Background pressed highlight258 // Background pressed highlight
85 Rectangle {259 Rectangle {
@@ -95,10 +269,14 @@
95 // modelData may be either a string, or an Action269 // modelData may be either a string, or an Action
96 text: modelData.hasOwnProperty("text") ? modelData.text : modelData270 text: modelData.hasOwnProperty("text") ? modelData.text : modelData
97 fontSize: sectionsStyle.fontSize271 fontSize: sectionsStyle.fontSize
272 font.weight: Font.Light
98 anchors.centerIn: parent273 anchors.centerIn: parent
99 color: sectionButton.selected ?274 color: sectionButton.selected ?
100 sectionsStyle.selectedSectionColor :275 sectionsStyle.selectedSectionColor :
101 sectionsStyle.sectionColor276 sectionsStyle.sectionColor
277
278 //FIXME: color animation? is that ok to design? what's the duration?
279 Behavior on color { ColorAnimation { duration: 500 } }
102 }280 }
103281
104 // Section title underline282 // Section title underline
@@ -109,11 +287,167 @@
109 right: parent.right287 right: parent.right
110 }288 }
111 height: sectionsStyle.underlineHeight289 height: sectionsStyle.underlineHeight
112 color: sectionButton.selected ?290 color: sectionsStyle.underlineColor
113 sectionsStyle.selectedSectionColor :
114 sectionsStyle.sectionColor
115 }291 }
116 }292 }
117 }293
118 }294 //Behavior on contentX { SmoothedAnimation { duration: UbuntuAnimation.FastDuration; velocity: units.gu(10)} }
295
296 }
297 }
298
299 MouseArea {
300 id: hoveringArea
301
302 function checkHovering(mouse) {
303 if (mouse.x < __listViewMargin) {
304 if (!__hoveringLeft) __hoveringLeft = true
305 } else if (mouse.x > width - __listViewMargin ) {
306 if (!__hoveringRight) __hoveringRight = true
307 } else {
308 __hoveringLeft = false
309 __hoveringRight = false
310 }
311 }
312
313 onContainsMouseChanged: console.log(containsMouse)
314 anchors.fill: parent
315 hoverEnabled: true
316
317 onPositionChanged: checkHovering(mouse)
318 onExited: {
319 __hoveringLeft = false
320 __hoveringRight = false
321 }
322 onPressed: if (!__hoveringLeft && !__hoveringRight) {
323 mouse.accepted = false
324 }
325 onClicked: {
326 //scroll the list to bring the element which is under the cursor into the view
327 //var item = sectionsListView.itemAt(mouse.x + sectionsListView.contentX - __listViewMargin, mouse.y)
328 //if (item !== null) {
329 //We could use positionViewAtIndex(...) here but it wouldn't provide animation
330
331 if (contentXAnim.running) contentXAnim.stop()
332
333 //Scroll one item at a time with animation
334 //sectionsListView.contentX = __hoveringLeft ? item.mapToItem(sectionsListView.contentItem, 0,0).x : item.mapToItem(sectionsListView.contentItem, 0,0).x - sectionsListView.width + item.width
335 var newContentX = sectionsListView.contentX + (sectionsListView.width * (__hoveringLeft ? -1 : 1))
336
337 contentXAnim.from = sectionsListView.contentX
338 //make sure we don't overshoot bounds
339 contentXAnim.to = Math.max(sectionsListView.originX, Math.min(newContentX, sectionsListView.originX + sectionsListView.contentWidth - sectionsListView.width))
340
341 contentXAnim.start()
342
343 //}
344 }
345
346 property real iconsDisabledOpacity: 0.3
347
348 Icon {
349 id: leftHoveringIcon
350 anchors.left: parent.left
351 anchors.leftMargin: units.gu(1)
352 anchors.verticalCenter: parent.verticalCenter
353 width: units.gu(1)
354 height: units.gu(1)
355 rotation: 180
356 opacity: sectionsListView.atXBeginning ? hoveringArea.iconsDisabledOpacity : 1.0
357 name: "chevron"
358 }
359
360 Icon {
361 id: rightHoveringIcon
362 anchors.right: parent.right
363 anchors.rightMargin: units.gu(1)
364 anchors.verticalCenter: parent.verticalCenter
365 width: units.gu(1)
366 height: units.gu(1)
367 opacity: sectionsListView.atXEnd ? hoveringArea.iconsDisabledOpacity : 1.0
368 name: "chevron"
369 }
370 }
371
372 Rectangle {
373 anchors.left: parent.left
374 anchors.right: parent.right
375 anchors.bottom: parent.bottom
376 height: units.dp(1)
377 color: sectionsStyle.underlineColor
378 }
379
380 LinearGradient {
381 id: gradient
382
383 visible: false
384 anchors.fill: parent
385 start: Qt.point(0,0)
386 end: Qt.point(width,0)
387
388 property real __gradientWidth: units.gu(3) / gradient.width
389 //the width is __gradientWidth, but we want the gradient to actually start/finish at __gradientSplitPosition
390 //just to leave some margin.
391 property real __gradientSplitPosition: 3/4 * __gradientWidth
392
393 gradient: Gradient {
394 //left gradient
395 GradientStop { position: 0.0 ; color: Qt.rgba(1,1,1,0) }
396 GradientStop { position: gradient.__gradientSplitPosition ; color: Qt.rgba(1,1,1,0) }
397 GradientStop { position: gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) }
398 //right gradient
399 GradientStop { position: 1.0 - gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) }
400 GradientStop { position: 1.0 - gradient.__gradientSplitPosition; color: Qt.rgba(1,1,1,0) }
401 GradientStop { position: 1.0; color: Qt.rgba(1,1,1,0) }
402 }
403
404 }
405
406 OpacityMask {
407 id: mask
408 anchors.fill: parent
409
410 source: listviewcontainer
411 maskSource: gradient
412 }
413
414
415 //Since we only show one arrow at a time, let's reuse the same item and handle the property changes with states
416 states: [
417 State {
418 name: ""
419 PropertyChanges { target: mask; visible: false }
420 PropertyChanges { target: listviewcontainer; opacity: 1.0 }
421 PropertyChanges { target: leftHoveringIcon; visible: false; }
422 PropertyChanges { target: rightHoveringIcon; visible: false; }
423 },
424 State {
425 name: "hovering"
426 when: hoveringArea.containsMouse
427 PropertyChanges { target: mask; visible: true }
428 PropertyChanges { target: listviewcontainer; opacity: 0.0 }
429 PropertyChanges { target: leftHoveringIcon; visible: true; }
430 PropertyChanges { target: rightHoveringIcon; visible: true; }
431 }
432 //one of the experiments was to only show one arrow at a time on hover
433 /*State {
434 name: "hoveringLeft"
435 when: __hoveringLeft
436 PropertyChanges { target: mask; visible: true }
437 PropertyChanges { target: gradient; mirrored: true }
438 PropertyChanges { target: listviewcontainer; opacity: 0.0 }
439 PropertyChanges { target: hoveringIcon; visible: true; rotation: 180; anchors.leftMargin: units.gu(1) }
440 AnchorChanges { target: hoveringIcon; anchors.left: hoveringArea.left; anchors.right: undefined }
441 },
442 State {
443 name: "hoveringRight"
444 when: __hoveringRight
445 PropertyChanges { target: mask; visible: true }
446 PropertyChanges { target: gradient; mirrored: false }
447 PropertyChanges { target: listviewcontainer; opacity: 0.0 }
448 PropertyChanges { target: hoveringIcon; visible: true; rotation: 0; anchors.rightMargin: units.gu(1) }
449 AnchorChanges { target: hoveringIcon; anchors.right: hoveringArea.right; anchors.left: undefined }
450 }*/
451 ]
119}452}
453
120454
=== modified file 'modules/Ubuntu/Components/qmldir'
--- modules/Ubuntu/Components/qmldir 2015-05-24 14:14:36 +0000
+++ modules/Ubuntu/Components/qmldir 2015-07-10 13:56:40 +0000
@@ -141,3 +141,4 @@
141PullToRefresh 1.3 1.3/PullToRefresh.qml141PullToRefresh 1.3 1.3/PullToRefresh.qml
142UbuntuListView 1.3 1.3/UbuntuListView11.qml142UbuntuListView 1.3 1.3/UbuntuListView11.qml
143Captions 1.3 1.3/Captions.qml143Captions 1.3 1.3/Captions.qml
144MultiColumnView 1.3 1.3/MultiColumnView.qml
144145
=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-04-14 21:02:06 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-07-10 13:56:40 +0000
@@ -205,10 +205,9 @@
205205
206 def checkPageHeader(self, pageTitle):206 def checkPageHeader(self, pageTitle):
207 orientationHelper = self.getOrientationHelper()207 orientationHelper = self.getOrientationHelper()
208 header = orientationHelper.select_single("AppHeader", title=pageTitle)208 header_label = orientationHelper.select_single(objectName="header_title_label", text=pageTitle)
209 self.assertThat(header, Not(Is(None)))209 self.assertThat(header_label, Not(Is(None)))
210 self.assertThat(header.visible, Eventually(Equals(True)))210 self.assertThat(header_label.visible, Eventually(Equals(True)))
211 return header
212211
213 def getObject(self, objectName):212 def getObject(self, objectName):
214 obj = self.app.select_single(objectName=objectName)213 obj = self.app.select_single(objectName=objectName)
215214
=== modified file 'tests/resources/navigation/MyCustomPage.qml'
--- tests/resources/navigation/MyCustomPage.qml 2015-03-03 13:20:06 +0000
+++ tests/resources/navigation/MyCustomPage.qml 2015-07-10 13:56:40 +0000
@@ -15,7 +15,7 @@
15 */15 */
1616
17import QtQuick 2.217import QtQuick 2.2
18import Ubuntu.Components 1.118import Ubuntu.Components 1.3
1919
20Page {20Page {
21 title: i18n.tr("My custom page")21 title: i18n.tr("My custom page")
2222
=== added file 'tests/resources/navigation/SplitViewStack.qml'
--- tests/resources/navigation/SplitViewStack.qml 1970-01-01 00:00:00 +0000
+++ tests/resources/navigation/SplitViewStack.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,137 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19import QtQuick.Layouts 1.1
20
21MainView {
22 id: main
23 width: units.gu(100)
24 height: units.gu(71)
25
26 MultiColumnView {
27 id: view
28 anchors.fill: parent
29 columns: {
30 if (width > units.gu(120)) return 3;
31 if (width > units.gu(80)) return 2;
32 return 1;
33 }
34 columnMetrics: [
35 ColumnMetrics {
36 column: 2
37 fillWidth: true
38 },
39 ColumnMetrics {
40 column: 3
41 fillWidth: false
42 minimumWidth: units.gu(50)
43 }
44 ]
45 primaryPage: page1
46
47 Page {
48 id: page1
49 title: "Page #1"
50
51 Rectangle {
52 anchors.fill: parent
53 color: "red"
54 }
55 Column {
56 Button {
57 text: "Add page2 next"
58 onClicked: page1.pageStack.addPageToNextColumn(page1, page2)
59 }
60 Button {
61 text: "Add page4 above"
62 onClicked: page1.pageStack.addPageToCurrentColumn(page1, page4)
63 }
64 }
65 }
66
67 Page {
68 id: page2
69 title: "Page #2"
70
71 Rectangle {
72 anchors.fill: parent
73 color: "green"
74 }
75 Column {
76 Button {
77 text: "Back..."
78 onClicked: page2.pageStack.removePages(page2)
79 }
80 Button {
81 text: "Add page3 next"
82 onClicked: page2.pageStack.addPageToNextColumn(page2, page3)
83 }
84 }
85 }
86 Page {
87 id: page3
88 title: "Page #3"
89
90 Rectangle {
91 anchors.fill: parent
92 color: "blue"
93 }
94 Button {
95 text: "Back..."
96 onClicked: page3.pageStack.removePages(page3)
97 }
98 }
99 Page {
100 id: page4
101 title: "Page #4"
102
103 Rectangle {
104 anchors.fill: parent
105 color: "teal"
106 }
107 Column {
108 Button {
109 text: "Back..."
110 onClicked: page4.pageStack.removePages(page4)
111 }
112 Button {
113 text: "Add page5 next"
114 onClicked: page4.pageStack.addPageToNextColumn(page4, page5)
115 }
116 }
117 }
118 Page {
119 id: page5
120 title: "Page #5"
121 Rectangle {
122 anchors.fill: parent
123 color: "tan"
124 }
125 Column {
126 Button {
127 text: "Back..."
128 onClicked: page5.pageStack.removePages(page5)
129 }
130 Button {
131 text: "Custom page on same column"
132 onClicked: page5.pageStack.addPageToCurrentColumn(page5, Qt.resolvedUrl("MyCustomPage.qml"))
133 }
134 }
135 }
136 }
137}
0138
=== added file 'tests/unit_x11/tst_components/ListItemWithLabel.qml'
--- tests/unit_x11/tst_components/ListItemWithLabel.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/ListItemWithLabel.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,30 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19
20ListItem {
21 property alias text: label.text
22 Label {
23 id: label
24 anchors {
25 left: parent.left
26 leftMargin: units.gu(2)
27 verticalCenter: parent.verticalCenter
28 }
29 }
30}
031
=== modified file 'tests/unit_x11/tst_components/MyExternalPage.qml'
--- tests/unit_x11/tst_components/MyExternalPage.qml 2015-03-03 13:20:06 +0000
+++ tests/unit_x11/tst_components/MyExternalPage.qml 2015-07-10 13:56:40 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2012-2014 Canonical Ltd.2 * Copyright 2012-2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -14,9 +14,13 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.217import QtQuick 2.4
18import Ubuntu.Components 1.118import Ubuntu.Components 1.3
1919
20Page {20Page {
21 title: "Page from QML file"21 title: "Page from QML file"
22 Label {
23 anchors.centerIn: parent
24 text: "This page was created from MyExternalPage.qml."
25 }
22}26}
2327
=== modified file 'tests/unit_x11/tst_components/tst_components.pro'
--- tests/unit_x11/tst_components/tst_components.pro 2015-04-25 07:10:57 +0000
+++ tests/unit_x11/tst_components/tst_components.pro 2015-07-10 13:56:40 +0000
@@ -7,5 +7,6 @@
7SOURCES += tst_components.cpp tabsmodel.cpp7SOURCES += tst_components.cpp tabsmodel.cpp
8HEADERS += tabsmodel.h8HEADERS += tabsmodel.h
99
10OTHER_FILES += $$system(ls *.qml)10OTHER_FILES += $$system(ls *.qml) \
11 tst_multicolumnview.qml
11OTHER_FILES += $$system(ls AppTheme/*)12OTHER_FILES += $$system(ls AppTheme/*)
1213
=== added file 'tests/unit_x11/tst_components/tst_multicolumnheader.qml'
--- tests/unit_x11/tst_components/tst_multicolumnheader.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/tst_multicolumnheader.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,200 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Test 1.0
19import Ubuntu.Components 1.3
20
21MainView {
22 id: root
23 width: units.gu(120)
24 height: units.gu(71)
25
26 MultiColumnView {
27 id: multiColumnView
28 width: parent.width
29 height: parent.height
30 primaryPage: rootPage
31
32 Page {
33 id: rootPage
34 title: "Root"
35
36 Column {
37 anchors {
38 top: parent.top
39 left: parent.left
40 right: parent.right
41 }
42 height: childrenRect.height
43
44 ListItemWithLabel {
45 text: "Add page left"
46 onClicked: multiColumnView.addPageToCurrentColumn(rootPage, leftPage)
47 }
48 ListItemWithLabel {
49 text: "Add page right"
50 onClicked: multiColumnView.addPageToNextColumn(rootPage, rightPage)
51 }
52 ListItemWithLabel {
53 text: "Add sections page right"
54 onClicked: multiColumnView.addPageToNextColumn(rootPage, sectionsPage)
55 }
56 ListItemWithLabel {
57 text: "Add external page right"
58 onClicked: multiColumnView.addPageToNextColumn(
59 rootPage, Qt.resolvedUrl("MyExternalPage.qml"))
60 }
61 }
62 }
63 Page {
64 id: leftPage
65 title: "First column"
66 Rectangle {
67 anchors {
68 fill: parent
69 margins: units.gu(2)
70 }
71 color: "orange"
72 }
73 }
74 Page {
75 id: rightPage
76 title: "Second column"
77 Rectangle {
78 anchors {
79 fill: parent
80 margins: units.gu(2)
81 }
82 color: "green"
83 }
84 }
85 Page {
86 id: sectionsPage
87 title: "Page with sections"
88 head.sections.model: ["uno", "dos", "tres"]
89
90 Rectangle {
91 anchors {
92 fill: parent
93 margins: units.gu(2)
94 }
95 color: "blue"
96 }
97 }
98 }
99 UbuntuTestCase {
100 when: windowShown
101
102 function get_number_of_columns() {
103 var body = findChild(multiColumnView, "body");
104 return body.children.length;
105 }
106
107 function get_header(index) {
108 return findChild(multiColumnView, "Header" + index);
109 }
110
111 function get_number_of_headers() {
112 // FIXME: With only one column, revert to using the AppHeader
113 // so multiColumnView sill not include any headers.
114 var numHeaders = 0;
115 var header = findChild(multiColumnView, "Header0");
116 verify(header !== null, "No header found!");
117 while (header !== null) {
118 numHeaders++;
119 header = get_header(numHeaders);
120 }
121 return numHeaders;
122 }
123
124 function cleanup() {
125 multiColumnView.width = root.width;
126 multiColumnView.height = root.height;
127 multiColumnView.removePages(rootPage);
128 }
129
130 function test_number_of_headers_equals_number_of_columns() {
131 multiColumnView.width = units.gu(40);
132 compare(get_number_of_columns(), 1, "Number of columns is not 1.");
133 compare(get_number_of_headers(), 1, "Number of headers is not 1.");
134 multiColumnView.width = root.width;
135 compare(get_number_of_columns(), 2, "Number of columns is not 2.");
136 compare(get_number_of_headers(), 2, "Number of headers is not 2.");
137 }
138
139 function test_header_configuration_equals_column_page_configuration() {
140 compare(get_number_of_headers(), 2, "Number of headers is not 2 initially.");
141 compare(get_header(0).config, rootPage.head,
142 "First column header is not initialized with primaryPage header config.");
143 compare(get_header(1).config, null,
144 "Second column header is not initalized with null.");
145
146 multiColumnView.addPageToCurrentColumn(rootPage, leftPage);
147 compare(get_header(0).config, leftPage.head,
148 "First column header is not updated properly.");
149 compare(get_header(1).config, null,
150 "Second column header is updated when it should not be.");
151 multiColumnView.removePages(leftPage);
152 compare(get_header(0).config, rootPage.head,
153 "First column header is not reverted properly.");
154
155 multiColumnView.addPageToNextColumn(rootPage, rightPage);
156 compare(get_header(0).config, rootPage.head,
157 "First column header is updated when it should not be.");
158 compare(get_header(1).config, rightPage.head,
159 "Second column header is not updated properly.");
160 multiColumnView.removePages(rightPage);
161 compare(get_header(1).config, null,
162 "Second column header is not reverted properly.");
163 }
164
165 function test_header_title_for_external_page() {
166 multiColumnView.addPageToNextColumn(rootPage, Qt.resolvedUrl("MyExternalPage.qml"));
167 compare(get_header(1).config.title, "Page from QML file",
168 "Adding external Page does not update the header title.");
169 }
170
171 function test_header_height() {
172 // contentHeight + divider height
173 var baseHeight = units.gu(6) + units.dp(1);
174 var withSectionsHeight = baseHeight + units.gu(4);
175 var n = get_number_of_headers();
176 var i;
177 for (i = 0; i < n; i++) {
178 compare(get_header(i).height, baseHeight,
179 "Header " + i + " height is not initialized correctly.");
180 }
181 multiColumnView.addPageToNextColumn(rootPage, rightPage);
182 for (i = 0; i < n; i++) {
183 compare(get_header(i).height, baseHeight,
184 "Header " + i + " height is incorrect after adding Page.");
185 }
186 multiColumnView.removePages(rightPage);
187 multiColumnView.addPageToNextColumn(rootPage, sectionsPage);
188 for (i = 0; i < n; i++) {
189 compare(get_header(i).height, withSectionsHeight,
190 "Header " + i + " height is incorrect after adding single Page with sections.");
191 }
192 multiColumnView.removePages(sectionsPage);
193 for (i = 0; i < n; i++) {
194 compare(get_header(i).height, baseHeight,
195 "Header " + i +
196 " height is not correctly reverted after removing Page with sections.");
197 }
198 }
199 }
200}
0201
=== added file 'tests/unit_x11/tst_components/tst_multicolumnview.qml'
--- tests/unit_x11/tst_components/tst_multicolumnview.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/tst_multicolumnview.qml 2015-07-10 13:56:40 +0000
@@ -0,0 +1,128 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtTest 1.0
19import Ubuntu.Test 1.0
20import Ubuntu.Components 1.3
21
22Item {
23 id: test
24 width: units.gu(120)
25 height: units.gu(71)
26
27 MultiColumnView {
28 id: testView
29 width: parent.width
30 height: parent.height
31
32 primaryPage: page1
33
34 Page {
35 id: page1
36 title: "Page1"
37 }
38 Page {
39 id: page2
40 title: "Page2"
41 }
42 Page {
43 id: page3
44 title: "Page3"
45 }
46 Page {
47 id: page4
48 title: "Page4"
49 }
50 }
51
52 MultiColumnView {
53 id: defaults
54 }
55
56 UbuntuTestCase {
57 when: windowShown
58
59 function cleanup() {
60// testView.columns = Qt.binding(function() {
61// return test.width > units.gu(100) ? 3 : (test.width > units.gu(80) ? 2 : 1);
62// });
63 testView.width = test.width;
64 testView.height = test.height;
65 // remove allpages
66 testView.removePages(page1);
67 }
68
69 function test_0_API() {
70 compare(defaults.primaryPage, undefined, "primaryPage not undefined by default");
71 }
72
73 function test_add_to_first_column_data() {
74 return [
75 {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
76 {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
77 ]
78 }
79 function test_add_to_first_column(data) {
80 if (data.failMsg != "") {
81 ignoreWarning(data.failMsg);
82 }
83
84 testView.addPageToCurrentColumn(data.sourcePage, data.page);
85 var firstColumn = findChild(testView, "ColumnHolder0");
86 verify(firstColumn);
87 if (data.failMsg != "") {
88 expectFail(data.tag, "Fail");
89 }
90 compare(firstColumn.pageWrapper.object, data.page);
91 }
92
93 function test_add_to_next_column_data() {
94 return [
95 {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
96 {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
97 ]
98 }
99 function test_add_to_next_column(data) {
100 if (data.failMsg != "") {
101 ignoreWarning(data.failMsg);
102 }
103
104 testView.addPageToNextColumn(data.sourcePage, data.page);
105 var secondColumn = findChild(testView, "ColumnHolder1");
106 verify(secondColumn);
107 if (data.failMsg != "") {
108 expectFail(data.tag, "Fail");
109 }
110 verify(secondColumn.pageWrapper);
111 }
112
113 function test_change_primaryPage() {
114 ignoreWarning("Cannot change primaryPage after completion.");
115 testView.primaryPage = page3;
116 }
117
118 function test_add_to_same_column_when_source_page_not_in_stack() {
119 ignoreWarning("sourcePage must be added to the view to add new page.");
120 testView.addPageToCurrentColumn(page2, page3);
121 }
122
123 function test_add_to_next_column_when_source_page_not_in_stack() {
124 ignoreWarning("sourcePage must be added to the view to add new page.");
125 testView.addPageToNextColumn(page2, page3);
126 }
127 }
128}

Subscribers

People subscribed via source and target branches