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

Proposed by Zsombor Egri
Status: Merged
Merge reported by: Cris Dywan
Merged at revision: not available
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/multicolumnview
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 1172 lines (+1033/-4)
14 files modified
components.api (+15/-0)
modules/Ubuntu/Components/1.2/stack.js (+1/-0)
modules/Ubuntu/Components/1.3/ColumnMetrics.qml (+52/-0)
modules/Ubuntu/Components/1.3/MultiColumnView.qml (+569/-0)
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/PageHeadStyle.qml (+2/-2)
modules/Ubuntu/Components/qmldir (+2/-0)
tests/resources/navigation/MyCustomPage.qml (+1/-1)
tests/resources/navigation/SplitViewStack.qml (+137/-0)
tests/unit_x11/tst_components/tst_components.pro (+2/-1)
tests/unit_x11/tst_components/tst_multicolumnview.qml (+137/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/multicolumnview
Reviewer Review Type Date Requested Status
Tim Peeters Needs Fixing
PS Jenkins bot continuous-integration Approve
Review via email: mp+261703@code.launchpad.net

Commit message

MultiColumnView component

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
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

23 + default readonly property QtObject data

why?

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

41 === modified file 'modules/Ubuntu/Components/1.2/stack.js'
42 --- modules/Ubuntu/Components/1.2/stack.js 2015-04-30 08:32:44 +0000
43 +++ modules/Ubuntu/Components/1.2/stack.js 2015-06-17 14:25:30 +0000
44 @@ -41,4 +41,5 @@
45 if (this.size() < 1) return undefined;
46 return elements[elements.length-1];
47 }
48 +
49 }

undo

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

82 + /*!
83 + 1-based value identifying the column the metrics to be applied to.
84 + */
85 + property int column

Why not start at 0, as is common?

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

87 + /*!
88 + Specifies whether the width of the column should fill the available space
89 + of the MultiColumnView column or not. Defaults to \a false.
90 + */
91 + property bool fillWidth: false

if fillWidth = true, then defaultColumnWidth will be used (if that is within the borders of the min and max width for the colum)? Can you describe that explicitly here?

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

144 + MultiColumnView stores pages added in a tree. Pages are added relative to a
145 + given page, either as sibling (\l addPageToCurrentColumn) or as child
146 + (\l addPageToNextColumn). This means that removing a non-leaf page from the Page
147 + tree will remove all its children from the page tree.

I don't agree with the terminology here. They are both children, in either of the columns.

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

152 + the column next to the source page. Giving a null value to the source page will
153 + add the page to the leftmost column of the view.

No, adding a null value is forbidden.

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

160 + \note Unlike PageStack, the component does not fill its parent content.

why not?

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

I'm reading this - http://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html and they have GridLayout, RowLayout and ColumnLayout. I'm wondering whether we should name our component MultiColumnView (similar to our MainView), or we adapt naming similar to that used in Layouts, so MultiColumnLayout?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

> 23 + default readonly property QtObject data
>
> why?
That wasn't me, it is the qmlapicheck, sorry.

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

> 41 === modified file 'modules/Ubuntu/Components/1.2/stack.js'
> 42 --- modules/Ubuntu/Components/1.2/stack.js 2015-04-30 08:32:44
> +0000
> 43 +++ modules/Ubuntu/Components/1.2/stack.js 2015-06-17 14:25:30
> +0000
> 44 @@ -41,4 +41,5 @@
> 45 if (this.size() < 1) return undefined;
> 46 return elements[elements.length-1];
> 47 }
> 48 +
> 49 }
>
> undo

ehh... will undo soon.

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

> 82 + /*!
> 83 + 1-based value identifying the column the metrics to be applied
> to.
> 84 + */
> 85 + property int column
>
> Why not start at 0, as is common?

Well, doesn't matter, we can start from 0 then.

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

> 87 + /*!
> 88 + Specifies whether the width of the column should fill the
> available space
> 89 + of the MultiColumnView column or not. Defaults to \a false.
> 90 + */
> 91 + property bool fillWidth: false
>
> if fillWidth = true, then defaultColumnWidth will be used (if that is within
> the borders of the min and max width for the colum)? Can you describe that
> explicitly here?

if fillWidth == true => every value is omitted, just like in Layout.

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

> 160 + \note Unlike PageStack, the component does not fill its parent
> content.
>
> why not?

This was something people complained a lot in PageStack. if you want different aching, you have to set anchor.fill: undefined first, then change the anchors.

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

> I'm reading this - http://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html and
> they have GridLayout, RowLayout and ColumnLayout. I'm wondering whether we
> should name our component MultiColumnView (similar to our MainView), or we
> adapt naming similar to that used in Layouts, so MultiColumnLayout?

Not a bad idea!!!!!

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

293 + The proeprty can hold either a Page instance, a component holding a Page

proeprty

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

293 + The proeprty can hold either a Page instance, a component holding a Page
294 + or a QML document defining the Page. The property cannot be changed after
295 + component completion.
296 + */
297 + property var primaryPage

Why? What is the use case for this flexibility? So we just make it property Item primaryPage or even property Page primaryPage.

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

300 + Specifies the number of columns in the view. A condition must be set to
301 + control the number of columns depending on the space available.

It is not a requirement that a condition is set.

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

311 + /*!
312 + The property specifies the default width of each column. The property is
313 + applied on each column. If the \a minimumWidth specified for the column is

Not on the last column. That will use full width.

314 + bigger than this value, the minimum width will be applied.
315 + */
316 + property real defaultColumnWidth: units.gu(40)

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

323 + is empty. Only columns requiring special handling than the default should
324 + be specified.
325 + */

+different from

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

329 + \qmlmethod Item addPageToCurrentColumn(Item sourcePage, var page[, var properties])

this doesn't seem to do anything in the generated docs.

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

330 + Adds a \c page to the column the \c sourcePage resides in. If \c sourcePage
331 + is null, the \c page will be added to the leftmost column. \c page can be a
332

Null is not allowed. Update docs.

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

336 + function addPageToCurrentColumn(sourcePage, page, properties) {
363 + function addPageToNextColumn(sourcePage, page, properties) {

These share a lot of implementation. Add an internal addPageToColumn(i, sourcePage, page, properties) function that they call.

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

409 + console.warn("There must me a minimum of one column set.");

BE

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'components.api'
--- components.api 2015-06-02 12:37:41 +0000
+++ components.api 2015-06-17 14:25:30 +0000
@@ -204,6 +204,11 @@
204 function clear()204 function clear()
205 function QQuickMimeData* newData()205 function QQuickMimeData* newData()
206Ubuntu.Components.ColorUtils 0.1 1.0206Ubuntu.Components.ColorUtils 0.1 1.0
207Ubuntu.Components.ColumnMetrics 1.3: QtObject
208 property int column
209 property bool fillWidth
210 property double maximumWidth
211 property double minimumWidth
207Ubuntu.Components.ComboButton 1.1: Button212Ubuntu.Components.ComboButton 1.1: Button
208 property double collapsedHeight213 property double collapsedHeight
209 default readonly property QtObject comboList214 default readonly property QtObject comboList
@@ -521,6 +526,15 @@
521Ubuntu.Components.Mouse.Priority: Enum526Ubuntu.Components.Mouse.Priority: Enum
522 AfterItem527 AfterItem
523 BeforeItem528 BeforeItem
529Ubuntu.Components.MultiColumnView 1.3: PageTreeNode
530 readonly property ColumnMetrics columnMetrics
531 property int columns
532 default readonly property QtObject data
533 property double defaultColumnWidth
534 function var addPageToCurrentColumn(var sourcePage, var page, var properties)
535 function var addPageToNextColumn(var sourcePage, var page, var properties)
536 function var removePages(var page)
537 property var primaryPage
524Ubuntu.Components.ListItems.MultiValue 1.0 0.1: Base538Ubuntu.Components.ListItems.MultiValue 1.0 0.1: Base
525 property var values539 property var values
526Ubuntu.Components.ListItems.MultiValue 1.3: Base540Ubuntu.Components.ListItems.MultiValue 1.3: Base
@@ -605,6 +619,7 @@
605 property bool locked619 property bool locked
606 property string preset620 property string preset
607 readonly property PageHeadSections sections621 readonly property PageHeadSections sections
622 property string title
608 property bool visible623 property bool visible
609Ubuntu.Components.PageHeadSections 1.1: QtObject624Ubuntu.Components.PageHeadSections 1.1: QtObject
610 property bool enabled625 property bool enabled
611626
=== 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-06-17 14:25:30 +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-06-17 14:25:30 +0000
@@ -0,0 +1,52 @@
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
26 */
27QtObject {
28 /*!
29 1-based value identifying the column the metrics to be applied to.
30 */
31 property int column
32
33 /*!
34 Specifies whether the width of the column should fill the available space
35 of the MultiColumnView column or not. Defaults to \a false.
36 */
37 property bool fillWidth: false
38
39 /*!
40 Specifies the minimum width of the column. If the value is greater than
41 \l MultiColumnView::defaultColumnWidth, the value will be set as width for
42 the column.
43 */
44 property real minimumWidth: 0
45
46 /*!
47 Specifies the maximum width of the column. If the value is smaller than
48 \l MultiColumnView::defaultColumnWidth, the value will be set as width for
49 the column. A maximum value of 0 will be ignored.
50 */
51 property real maximumWidth: Number.POSITIVE_INFINITY
52}
053
=== 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-06-17 14:25:30 +0000
@@ -0,0 +1,569 @@
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, the component does not fill its parent content.
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 columns: 2
62 primaryPage: page1
63 Page {
64 id: page1
65 title: "Main page"
66 Column {
67 Button {
68 text: "Add Page2 above " + title
69 onClicked: page1.pageStack.addPageToCurrentColumn(page1, page2)
70 }
71 Button {
72 text: "Add Page3 next to " + title
73 onClicked: page1.pageStack.addPageToNextColumn(page1, page3)
74 }
75 }
76 }
77 Page {
78 id: page2
79 title: "Page #2"
80 }
81 Page {
82 id: page3
83 title: "Page #3"
84 }
85 }
86 }
87 \endqml
88
89 Column widths are controlled by the \l defaultColumnWidth property and the \l columnMetrics
90 properties. The \l columnMetrics contains a list of metrics configuring a specific
91 column. If no metrics are set, the component will use \l defaultColumnWidth on
92 each column. If the component is set to have one column only, the content will
93 be stretched to the entire component area no matter of the metrics specified.
94 When multiple columns are set, the last column is set to fill the available
95 width and the rest are configured with the \l defaultColumnWidth. This behavior
96 can be changed by specifying the \l ColumnMetrics::fillWidth for the column that
97 needs to fill the available width. There can be more columns filling the available
98 width at a time.
99
100 Let's modify the example above, to have 3 columns, where columns 1 and 3
101 should have fixed widths of 40 GU and column 2 should fill to the space
102 available. The code handling this would look as follows:
103 \qml
104 MultiColumnView {
105 columns: 3
106 defaultColumnWidth: units.gu(40)
107 columnMetrics: [
108 ColumnMetrics {
109 column: 2
110 fillWidth: true
111 },
112 ColumnMetrics {
113 column: 3
114 fillWidth: false
115 }
116 ]
117 }
118 \endqml
119
120 MultiColumnView supports adaptive column handling. When columns number changes
121 runtime, the pages are automatically rearranged to the closest columns they were
122 added to. To understand it better, let's take the following example:
123 \qml
124 import QtQuick 2.4
125 import Ubuntu.Components 1.3
126
127 MainView {
128 width: units.gu(120)
129 height: units.gu(71)
130
131 MultiColumnView {
132 anchors.fill: parent
133 columns: width > units.gu(100) ? 3 :
134 (width >= units.gu(80) ? 2 : 1)
135 primaryPage: page1
136 Page {
137 id: page1
138 title: "Main page"
139 Button {
140 text: "Add Page2 next to " + title
141 onClicked: page1.pageStack.addPageToNextColumn(page1, page2)
142 }
143 }
144 Page {
145 id: page2
146 title: "Page #2"
147 Button {
148 text: "Add Page3 next to " + title
149 onClicked: page2.pageStack.addPageToNextColumn(page2, page3)
150 }
151 }
152 Page {
153 id: page3
154 title: "Page #3"
155 }
156 }
157 }
158 \endqml
159
160 When the code is run on desktop, it will launch with a space for three columns.
161 \c page1 is set to be the primary page, \c page2 will be added to column next to
162 \c page1 (to column 2) and \c page3 next to \c page2 (column 3). When the window
163 is resized to have its size below 100 GU, the component will switch to 2 column
164 mode, and \c page3 will be placed in the last column, and the header for \c page2
165 will have a back button, indicating that there is a page below it. If the window
166 is resized to contain only one column, all pages will be shown in that column, so
167 the component will act as PageStack. Resizing the window back to 2 respectively
168 3 columns will place the pages side-by-side.
169
170 \note In the above example if \c page2 is removed, that will remove all its child
171 pages, meaning \c page3 will also be removed.
172
173 \sa PageStack, ColumnMetrics
174*/
175
176PageTreeNode {
177 id: multiColumnView
178
179 /*!
180 The property holds the first Page which will be added to the view. If the
181 view has more than one column, the page will be added to the leftmost column.
182 The proeprty can hold either a Page instance, a component holding a Page
183 or a QML document defining the Page. The property cannot be changed after
184 component completion.
185 */
186 property var primaryPage
187
188 /*!
189 Specifies the number of columns in the view. A condition must be set to
190 control the number of columns depending on the space available.
191 \qml
192 MultiColumnView {
193 id: view
194 columns: view.width > units.gu(50) ? 2 : 1
195 }
196 \endqml
197 */
198 property int columns: 1
199
200 /*!
201 The property specifies the default width of each column. The property is
202 applied on each column. If the \a minimumWidth specified for the column is
203 bigger than this value, the minimum width will be applied.
204 */
205 property real defaultColumnWidth: units.gu(40)
206
207 /*!
208 The property configures the size constraints and area filling for columns.
209 If a column is not specified, the default sizing and filling will be applied.
210 By default, only the last column is filling the available width, al other
211 columns are sized to \l defaultColumnWidth as maximum. By default the list
212 is empty. Only columns requiring special handling than the default should
213 be specified.
214 */
215 property list<ColumnMetrics> columnMetrics
216
217 /*!
218 \qmlmethod Item addPageToCurrentColumn(Item sourcePage, var page[, var properties])
219 Adds a \c page to the column the \c sourcePage resides in. If \c sourcePage
220 is null, the \c page will be added to the leftmost column. \c page can be a
221 Component or a file. \c properties is a JSon object containing properties
222 to be set when page is created. \c sourcePage must be active. Returns the
223 instance of the page created.
224 */
225 function addPageToCurrentColumn(sourcePage, page, properties) {
226 if (columns <= 0) {
227 return;
228 }
229 if (!sourcePage) {
230 console.warn("No sourcePage specified. Page will not be added.");
231 return;
232 }
233 if (!d.stack.find(sourcePage)) {
234 console.warn("sourcePage must be added to the view to add new page.");
235 return;
236 }
237
238 var wrapper = d.createWrapper(page, properties);
239 wrapper.column = d.columnForPage(sourcePage);
240 wrapper.parentPage = sourcePage;
241 d.addPage(wrapper);
242 return wrapper.object;
243 }
244
245 /*!
246 \qmlmethod Item addPageToNextColumn(Item sourcePage, var page[, var properties])
247 Same as \l addPageToCurrentColumn except that the \c page is added to the column
248 next to the one the \c sourcePage resides. If \c sourcePage is null, the new
249 page will be added to the leftmost column. If \c sourcePage is located in the
250 rightmost column, the new page will be pushed to the same column as \c sourcePage.
251 */
252 function addPageToNextColumn(sourcePage, page, properties) {
253 if (columns <= 0) {
254 return;
255 }
256 if (!sourcePage) {
257 console.warn("No sourcePage specified. Page will not be added.");
258 return;
259 }
260 if (!d.stack.find(sourcePage)) {
261 console.warn("sourcePage must be added to the view to add new page.");
262 return;
263 }
264
265 var wrapper = d.createWrapper(page, properties);
266 wrapper.column = (!sourcePage) ? 0 : d.columnForPage(sourcePage) + 1;
267 wrapper.parentPage = sourcePage;
268 d.addPage(wrapper);
269 return wrapper.object;
270 }
271
272 /*!
273 \qmlmethod void removePages(Item page)
274 The function removes and deletes all pages from the view columns until \c page
275 is reached. If the \a page is the same as the \l primaryPage, only its child
276 pages will be removed.
277 */
278 function removePages(page) {
279 // remove nodes which have page as ascendant
280 var node = d.stack.top();
281 while (node && node.childOf(page)) {
282 d.popAndSetPageForColumn(node);
283 node = d.stack.top();
284 }
285 while (node && node.object == page && node != d.stack.first()) {
286 d.popAndSetPageForColumn(node);
287 node = d.stack.top();
288 }
289 }
290
291 /*
292 internals
293 */
294
295 /*! internal */
296 onColumnsChanged: {
297 if (columns <= 0) {
298 console.warn("There must me a minimum of one column set.");
299 }
300 d.relayout();
301 }
302 Component.onCompleted: {
303 d.relayout();
304 d.completed = true;
305 if (primaryPage) {
306 var wrapper = d.createWrapper(primaryPage);
307 d.addPage(wrapper);
308 } else {
309 console.warn("No primary page set. No pages can be added without a primary page.");
310 }
311 }
312 onPrimaryPageChanged: {
313 if (d.completed) {
314 console.warn("Cannot change primaryPage after completion.");
315 return;
316 }
317 }
318 onDefaultColumnWidthChanged: body.applyMetrics()
319
320 QtObject {
321 id: d
322
323 property bool completed: false
324 property var stack: new Stack.Stack()
325
326 function createWrapper(page, properties) {
327 var wrapperComponent = Qt.createComponent("PageWrapper.qml");
328 var wrapperObject = wrapperComponent.createObject(hiddenPages);
329 wrapperObject.pageStack = multiColumnView;
330 wrapperObject.properties = properties;
331 // set reference last because it will trigger creation of the object
332 // with specified properties.
333 wrapperObject.reference = page;
334 return wrapperObject;
335 }
336
337 function addPage(pageWrapper) {
338 stack.push(pageWrapper);
339 pageWrapper.parentWrapper = stack.find(pageWrapper.parentPage);
340 var targetColumn = MathUtils.clamp(pageWrapper.column, 0, columns - 1);
341 // replace page holder's child
342 var holder = body.children[targetColumn];
343 holder.detachCurrentPage();
344 holder.attachPage(pageWrapper)
345 }
346
347 function columnForPage(page) {
348 var wrapper = stack.find(page);
349 return wrapper ? wrapper.column : 0;
350 }
351
352 // node is a triplet of {page, column, parentPage}
353 function popAndSetPageForColumn(node) {
354 stack.pop();
355 var effectiveColumn = MathUtils.clamp(node.column, 0, columns - 1);
356 var columnHolder = body.children[effectiveColumn];
357 // is the page in a column?
358 if (node == columnHolder.pageWrapper) {
359 // detach page from column
360 columnHolder.detachCurrentPage();
361 }
362 node.parent = null;
363 var prevPage = stack.topForColumn(effectiveColumn, effectiveColumn < columns - 1);
364 if (prevPage) {
365 columnHolder.attachPage(prevPage);
366 }
367 if (node.canDestroy) {
368 node.destroyObject();
369 }
370 }
371
372 // relayouts when column count changes
373 function relayout() {
374 if (body.children.length == columns) return;
375 if (body.children.length > columns) {
376 // need to remove few columns, the last ones
377 while (body.children.length > columns) {
378 var holder = body.children[body.children.length - 1];
379 holder.detachCurrentPage();
380 holder.parent = null;
381 holder.destroy();
382 }
383 } else {
384 var prevColumns = body.children.length;
385
386 // add columns
387 for (var i = 0; i < columns - prevColumns; i++) {
388 pageHolderComponent.createObject(body);
389 }
390 }
391 rearrangePages();
392 }
393
394 function rearrangePages() {
395 for (var column = columns - 1; column >= 0; column--) {
396 var holder = body.children[column];
397 var pageWrapper = stack.topForColumn(column, column < (columns - 1));
398 if (!pageWrapper) {
399 continue;
400 }
401 if (!pageWrapper.parent) {
402 // this should never happen, so if it does, we have a bug!
403 console.error("Found a page which wasn't parented anywhere!", pageWrapper.object.title);
404 continue;
405 }
406 // detach current page from holder if differs
407 if (holder.pageWrapper != pageWrapper) {
408 holder.detachCurrentPage();
409 }
410 if (pageWrapper.parent == hiddenPages) {
411 // add the page to the column
412 holder.attachPage(pageWrapper);
413 } else if (pageWrapper.pageHolder != holder) {
414 // detach the pageWrapper from its holder
415 if (pageWrapper.pageHolder) {
416 pageWrapper.pageHolder.detachCurrentPage();
417 }
418 // then attach to this holder
419 holder.attachPage(pageWrapper);
420 }
421 }
422 }
423 }
424
425 // default metrics
426 Component {
427 id: defaultMetrics
428 ColumnMetrics {
429 fillWidth: column == columns
430 minimumWidth: defaultColumnWidth
431 }
432 }
433
434 // Page holder component, can have only one Page as child at a time, all stacked pages
435 // will be parented into hiddenPages
436 Component {
437 id: pageHolderComponent
438 Item {
439 id: holder
440 objectName: "ColumnHolder" + column
441 property PageWrapper pageWrapper
442 property int column
443 property alias config: header.config
444 property ColumnMetrics metrics: setDefaultMetrics()
445
446 Layout.fillWidth: metrics.fillWidth
447 Layout.fillHeight: true
448 Layout.preferredWidth: metrics.maximumWidth > 0 ?
449 MathUtils.clamp(defaultColumnWidth, metrics.minimumWidth, metrics.maximumWidth) :
450 defaultColumnWidth
451 Layout.minimumWidth: metrics.minimumWidth
452 Layout.maximumWidth: metrics.maximumWidth
453
454 // header
455 StyledItem {
456 id: header
457 anchors {
458 left: parent.left
459 top: parent.top
460 right: parent.right
461 }
462 implicitHeight: units.gu(8)
463// styleName: config ? "PageHeadStyle" : ""
464
465 property PageHeadConfiguration config: null
466 property Item contents: null
467
468 property color dividerColor: theme.palette.normal.background
469 property color panelColor
470 }
471
472 Rectangle {
473 id: divider
474 anchors {
475 top: parent.top
476 bottom: parent.bottom
477 right: parent.right
478 }
479 width: (column == (columns - 1)) || !pageWrapper ? 0 : units.dp(2)
480 color: theme.palette.normal.background
481 }
482
483 Item {
484 id: holderBody
485 objectName: parent.objectName + "Body"
486 anchors {
487 fill: parent
488// topMargin: header.height
489 rightMargin: divider.width
490 }
491 }
492
493 function attachPage(page) {
494 if (!page) return;
495 pageWrapper = page;
496 pageWrapper.parent = holderBody;
497 pageWrapper.pageHolder = holder;
498 pageWrapper.active = true;
499 if (pageWrapper.object.hasOwnProperty("head")) {
500 header.config = pageWrapper.object.head;
501 }
502 }
503 function detachCurrentPage() {
504 if (!pageWrapper) return undefined;
505 var wrapper = pageWrapper;
506 // remove header
507 wrapper.active = false;
508 header.config = null;
509 pageWrapper = null;
510 wrapper.parent = hiddenPages;
511 wrapper.pageHolder = null;
512 return wrapper;
513 }
514
515 function setDefaultMetrics() {
516 var result = defaultMetrics.createObject(holder);
517 result.column = Qt.binding(function() { return holder.column + 1; });
518 return result;
519 }
520 }
521 }
522
523 /*! \internal */
524 // Pages declared as children will be placed directly into hiddenPages
525 default property alias data: hiddenPages.data
526 Item {
527 id: hiddenPages
528 objectName: "HiddenPagePool"
529 visible: false
530 // make sure nothing is shown eventually
531 clip: true
532 }
533
534 // Holds the columns holding the pages visible. Each column has only one page
535 // as child, the invisible stacked ones are all stored in the hiddenPages
536 // component. The stack keeps the column index onto which those should be moved
537 // once they become visible.
538 RowLayout {
539 id: body
540 anchors.fill: parent
541 spacing: 0
542
543 onChildrenChanged: {
544 // all children should have Layout.fillWidth false, except the last one
545 for (var i = 0; i < children.length; i++) {
546 children[i].column = i;
547 }
548 applyMetrics();
549 }
550
551 function applyMetrics() {
552 for (var i = 0; i < children.length; i++) {
553 var holder = children[i];
554 // search for the column metrics
555 var metrics = null;
556 for (var j = 0; j < columnMetrics.length; j++) {
557 if (columnMetrics[j].column == (i + 1)) {
558 metrics = columnMetrics[j];
559 break;
560 }
561 }
562 if (!metrics) {
563 metrics = holder.setDefaultMetrics();
564 }
565 holder.metrics = metrics;
566 }
567 }
568 }
569}
0570
=== modified file 'modules/Ubuntu/Components/1.3/Page.qml'
--- modules/Ubuntu/Components/1.3/Page.qml 2015-04-30 08:32:44 +0000
+++ modules/Ubuntu/Components/1.3/Page.qml 2015-06-17 14:25:30 +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-06-17 14:25:30 +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-06-17 14:25:30 +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-06-17 14:25:30 +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/PageHeadStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-05-22 13:54:38 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-06-17 14:25:30 +0000
@@ -23,7 +23,7 @@
23 objectName: "PageHeadStyle" // used in unit tests23 objectName: "PageHeadStyle" // used in unit tests
24 contentHeight: units.gu(7)24 contentHeight: units.gu(7)
25 fontWeight: Font.Light25 fontWeight: Font.Light
26 fontSize: "x-large"26 fontSize: "large"
27 textLeftMargin: units.gu(2)27 textLeftMargin: units.gu(2)
28 maximumNumberOfActions: 328 maximumNumberOfActions: 3
2929
@@ -394,7 +394,7 @@
394 right: parent.right394 right: parent.right
395 verticalCenter: parent.verticalCenter395 verticalCenter: parent.verticalCenter
396 }396 }
397 text: styledItem.title397 text: styledItem.config.title
398 font.weight: headerStyle.fontWeight398 font.weight: headerStyle.fontWeight
399 fontSize: headerStyle.fontSize399 fontSize: headerStyle.fontSize
400 color: headerStyle.titleColor400 color: headerStyle.titleColor
401401
=== modified file 'modules/Ubuntu/Components/qmldir'
--- modules/Ubuntu/Components/qmldir 2015-05-12 13:41:39 +0000
+++ modules/Ubuntu/Components/qmldir 2015-06-17 14:25:30 +0000
@@ -140,3 +140,5 @@
140PullToRefresh 1.3 1.3/PullToRefresh.qml140PullToRefresh 1.3 1.3/PullToRefresh.qml
141UbuntuListView 1.3 1.3/UbuntuListView11.qml141UbuntuListView 1.3 1.3/UbuntuListView11.qml
142Captions 1.3 1.3/Captions.qml142Captions 1.3 1.3/Captions.qml
143MultiColumnView 1.3 1.3/MultiColumnView.qml
144ColumnMetrics 1.3 1.3/ColumnMetrics.qml
143145
=== 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-06-17 14:25:30 +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-06-17 14:25:30 +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
=== 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-06-17 14:25:30 +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_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-06-17 14:25:30 +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 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 columns: width > units.gu(100) ? 3 : (width > units.gu(80) ? 2 : 1)
33 primaryPage: page1
34
35 Page {
36 id: page1
37 title: "Page1"
38 }
39 Page {
40 id: page2
41 title: "Page2"
42 }
43 Page {
44 id: page3
45 title: "Page3"
46 }
47 Page {
48 id: page4
49 title: "Page4"
50 }
51 }
52
53 MultiColumnView {
54 id: defaults
55 }
56
57 UbuntuTestCase {
58 when: windowShown
59
60 function cleanup() {
61// testView.columns = Qt.binding(function() {
62// return test.width > units.gu(100) ? 3 : (test.width > units.gu(80) ? 2 : 1);
63// });
64 testView.width = test.width;
65 testView.height = test.height;
66 // remove allpages
67 testView.removePages(page1);
68 }
69
70 function test_0_API() {
71 compare(defaults.columns, 1, "wrong columns");
72 compare(defaults.defaultColumnWidth, units.gu(40), "wrong defaultColumnWidth");
73 compare(defaults.columnMetrics.length, 0, "wrong columnMetrics list");
74 compare(defaults.primaryPage, undefined, "wrong primaryPage");
75 }
76
77 function test_add_to_first_column_data() {
78 return [
79 {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
80 {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
81 ]
82 }
83 function test_add_to_first_column(data) {
84 if (data.failMsg != "") {
85 ignoreWarning(data.failMsg);
86 }
87
88 testView.addPageToCurrentColumn(data.sourcePage, data.page);
89 var firstColumn = findChild(testView, "ColumnHolder0");
90 verify(firstColumn);
91 if (data.failMsg != "") {
92 expectFail(data.tag, "Fail");
93 }
94 compare(firstColumn.pageWrapper.object, data.page);
95 }
96
97 function test_add_to_next_column_data() {
98 return [
99 {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
100 {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
101 ]
102 }
103 function test_add_to_next_column(data) {
104 if (data.failMsg != "") {
105 ignoreWarning(data.failMsg);
106 }
107
108 testView.addPageToNextColumn(data.sourcePage, data.page);
109 var secondColumn = findChild(testView, "ColumnHolder1");
110 verify(secondColumn);
111 if (data.failMsg != "") {
112 expectFail(data.tag, "Fail");
113 }
114 verify(secondColumn.pageWrapper);
115 }
116
117 function test_invalid_column() {
118 ignoreWarning("There must me a minimum of one column set.");
119 defaults.columns = 0;
120 }
121
122 function test_change_primaryPage() {
123 ignoreWarning("Cannot change primaryPage after completion.");
124 testView.primaryPage = page3;
125 }
126
127 function test_add_to_same_column_when_source_page_not_in_stack() {
128 ignoreWarning("sourcePage must be added to the view to add new page.");
129 testView.addPageToCurrentColumn(page2, page3);
130 }
131
132 function test_add_to_next_column_when_source_page_not_in_stack() {
133 ignoreWarning("sourcePage must be added to the view to add new page.");
134 testView.addPageToNextColumn(page2, page3);
135 }
136 }
137}

Subscribers

People subscribed via source and target branches