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

Proposed by Andrea Bernabei on 2015-07-10
Status: Rejected
Rejected by: Zsombor Egri on 2015-12-10
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 (community) 2015-07-10 Disapprove on 2015-12-10
PS Jenkins bot continuous-integration Needs Fixing on 2015-07-10
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.

Unmerged revisions

1566. By Andrea Bernabei on 2015-07-10

merge multiColumnView branch

1565. By Andrea Bernabei on 2015-07-09

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

1564. By Andrea Bernabei on 2015-07-09

merge

1563. By Andrea Bernabei on 2015-07-07

change default color of IconButtonStyle

1562. By Andrea Bernabei on 2015-07-07

change backgroundText in theme Palette

1561. By Andrea Bernabei on 2015-07-07

add white background to header

1560. By Andrea Bernabei on 2015-07-07

update backgroundText palette value

1559. By Andrea Bernabei on 2015-07-07

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

1558. By Andrea Bernabei on 2015-07-06

merge tims branch

1557. By Andrea Bernabei on 2015-07-06

Implement dynamic number of slots in PageHeadStyle

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2015-07-10 13:05:26 +0000
3+++ components.api 2015-07-10 13:56:40 +0000
4@@ -522,6 +522,12 @@
5 Ubuntu.Components.Mouse.Priority: Enum
6 AfterItem
7 BeforeItem
8+Ubuntu.Components.MultiColumnView 1.3: PageTreeNode
9+ default readonly property QtObject data
10+ function var addPageToCurrentColumn(var sourcePage, var page, var properties)
11+ function var addPageToNextColumn(var sourcePage, var page, var properties)
12+ function var removePages(var page)
13+ property var primaryPage
14 Ubuntu.Components.ListItems.MultiValue 1.0 0.1: Base
15 property var values
16 Ubuntu.Components.ListItems.MultiValue 1.3: Base
17@@ -606,6 +612,7 @@
18 property bool locked
19 property string preset
20 readonly property PageHeadSections sections
21+ property string title
22 property bool visible
23 Ubuntu.Components.PageHeadSections 1.1: QtObject
24 property bool enabled
25
26=== modified file 'examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml'
27--- examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-04-25 08:18:45 +0000
28+++ examples/ubuntu-ui-toolkit-gallery/ListItemWithLabel.qml 2015-07-10 13:56:40 +0000
29@@ -14,7 +14,7 @@
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 */
32
33-import QtQuick 2.2
34+import QtQuick 2.4
35 import Ubuntu.Components 1.3
36
37 ListItem {
38
39=== modified file 'examples/ubuntu-ui-toolkit-gallery/Popover.qml'
40--- examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-04-29 08:55:31 +0000
41+++ examples/ubuntu-ui-toolkit-gallery/Popover.qml 2015-07-10 13:56:40 +0000
42@@ -21,17 +21,14 @@
43 Template {
44 objectName: "popoversTemplate"
45
46- tools: ToolbarItems {
47- ToolbarButton {
48- id: actionsButton
49+ head.actions: [
50+ Action {
51 text: "Actions"
52 iconSource: "call_icon.png"
53- onTriggered: PopupUtils.open(actionSelectionPopover, actionsButton)
54+ onTriggered: PopupUtils.open(actionSelectionPopover)
55 visible: true
56 }
57- locked: true
58- opened: true
59- }
60+ ]
61
62 TemplateSection {
63 className: "Popover"
64
65=== modified file 'examples/ubuntu-ui-toolkit-gallery/Sections.qml'
66--- examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-06-17 12:13:40 +0000
67+++ examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-07-10 13:56:40 +0000
68@@ -19,7 +19,7 @@
69
70 Template {
71 objectName: "sectionsTemplate"
72-
73+ head.sections.model: ["first", "second", "third"]
74 TemplateSection {
75 title: "Sections"
76 className: "Sections"
77
78=== modified file 'examples/ubuntu-ui-toolkit-gallery/Template.qml'
79--- examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-04-25 08:18:45 +0000
80+++ examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-07-10 13:56:40 +0000
81@@ -1,5 +1,5 @@
82 /*
83- * Copyright 2013 Canonical Ltd.
84+ * Copyright 2015 Canonical Ltd.
85 *
86 * This program is free software; you can redistribute it and/or modify
87 * it under the terms of the GNU Lesser General Public License as published by
88@@ -14,19 +14,19 @@
89 * along with this program. If not, see <http://www.gnu.org/licenses/>.
90 */
91
92-import QtQuick 2.0
93+import QtQuick 2.4
94 import Ubuntu.Components 1.3
95
96-Item {
97+Page {
98 id: template
99
100- width: units.gu(40)
101- height: units.gu(75)
102+ head.backAction: Action {
103+ iconName: 'back'
104+ onTriggered: columns.removePages(template)
105+ }
106
107 default property alias content: layout.children
108 property alias spacing: layout.spacing
109- property Item tools: null
110- property Flickable flickable: flickable
111
112 Flickable {
113 id: flickable
114
115=== modified file 'examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml'
116--- examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-06-23 17:40:21 +0000
117+++ examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml 2015-07-10 13:56:40 +0000
118@@ -1,5 +1,5 @@
119 /*
120- * Copyright 2013 Canonical Ltd.
121+ * Copyright 2015 Canonical Ltd.
122 *
123 * This program is free software; you can redistribute it and/or modify
124 * it under the terms of the GNU Lesser General Public License as published by
125@@ -38,107 +38,51 @@
126 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
127 LayoutMirroring.childrenInherit: true
128
129- state: width >= units.gu(80) ? "wide" : "narrow"
130- states: [
131- State {
132- name: "narrow"
133- StateChangeScript {
134- script: {
135- pageStack.push(mainPage);
136- if (selectedWidget) {
137- pageStack.push(contentPage);
138- }
139- }
140- }
141- PropertyChanges {
142- target: mainPage
143- flickable: widgetList
144- }
145- PropertyChanges {
146- target: contentPage
147- flickable: contentLoader.item ? contentLoader.item.flickable : null
148- }
149- },
150- State {
151- name: "wide"
152- StateChangeScript {
153- script: {
154- pageStack.clear();
155-
156- /* When pushing Pages into a PageStack they are reparented
157- to internally created PageWrappers. This undoes it as to
158- allow us to anchor the Pages freely again.
159- */
160- mainPage.parent = gallery;
161- contentPage.parent = gallery;
162- }
163- }
164- PropertyChanges {
165- target: mainPage
166- width: units.gu(40)
167- clip: true
168- }
169- AnchorChanges {
170- target: mainPage
171- anchors.right: undefined
172- }
173- PropertyChanges {
174- target: contentPage
175- clip: true
176- }
177- AnchorChanges {
178- target: contentPage
179- anchors.left: mainPage.right
180- }
181- }
182- ]
183-
184- property var selectedWidget: null
185-
186- Page {
187- id: mainPage
188- active: selectedWidget == null
189-
190- title: "Ubuntu UI Toolkit"
191- /* Page internally sets the topMargin of its flickable to account for
192- the height of the header. Undo it when unsetting the flickable.
193- */
194- onFlickableChanged: if (!flickable) widgetList.topMargin = 0;
195-
196- head.actions: [
197- Action {
198- text: i18n.tr('Use dark theme')
199- iconName: 'torch-on'
200- visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
201- onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
202- },
203- Action {
204- text: i18n.tr('Use light theme')
205- iconName: 'torch-off'
206- visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
207- onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
208- }
209- ]
210-
211- Rectangle {
212- color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
213- anchors.fill: parent
214-
215- ListView {
216- id: widgetList
217- objectName: "widgetList"
218+ MultiColumnView {
219+ id: columns
220+ anchors.fill: parent
221+ primaryPage: mainPage
222+
223+ Page {
224+ id: mainPage
225+ title: "Ubuntu UI Toolkit"
226+
227+ head.actions: [
228+ Action {
229+ text: i18n.tr('Use dark theme')
230+ iconName: 'torch-on'
231+ visible: theme.name == 'Ubuntu.Components.Themes.Ambiance'
232+ onTriggered: theme.name = 'Ubuntu.Components.Themes.SuruDark'
233+ },
234+ Action {
235+ text: i18n.tr('Use light theme')
236+ iconName: 'torch-off'
237+ visible: theme.name == 'Ubuntu.Components.Themes.SuruDark'
238+ onTriggered: theme.name = 'Ubuntu.Components.Themes.Ambiance'
239+ }
240+ ]
241+
242+ Rectangle {
243+ color: Qt.rgba(0.0, 0.0, 0.0, 0.01)
244 anchors.fill: parent
245- model: widgetsModel
246- delegate: ListItem.Standard {
247- text: model.label
248- objectName: model.objectName
249- enabled: model.source != ""
250- progression: true
251- selected: enabled && selectedWidget == model
252- onClicked: {
253- selectedWidget = model;
254- if (gallery.state == "narrow") {
255- pageStack.push(contentPage);
256+
257+ ListView {
258+ id: widgetList
259+ objectName: "widgetList"
260+ anchors.fill: parent
261+ model: widgetsModel
262+ currentIndex: -1
263+ delegate: ListItem.Standard {
264+ text: model.label
265+ objectName: model.objectName
266+ enabled: model.source != ""
267+ progression: true
268+ selected: index === widgetList.currentIndex
269+ onClicked: {
270+ var source = Qt.resolvedUrl(model.source);
271+ var newPage = columns.addPageToNextColumn(mainPage, source);
272+ newPage.title = model.label;
273+ widgetList.currentIndex = index;
274 }
275 }
276 }
277@@ -146,30 +90,6 @@
278 }
279 }
280
281- Page {
282- id: contentPage
283- active: selectedWidget != null
284- title: selectedWidget ? selectedWidget.label : ""
285- /* Page internally sets the topMargin of its flickable to account for
286- the height of the header. Undo it when unsetting the flickable.
287- */
288- onFlickableChanged: if (!flickable && contentLoader.item) contentLoader.item.flickable.topMargin = 0;
289- onActiveChanged: if (gallery.state == "narrow" && !active) {
290- selectedWidget = null;
291- }
292-
293- Loader {
294- id: contentLoader
295- objectName: "contentLoader"
296- anchors.fill: parent
297- source: selectedWidget ? selectedWidget.source : ""
298- }
299- }
300-
301- PageStack {
302- id: pageStack
303- }
304-
305 WidgetsModel {
306 id: widgetsModel
307 }
308
309=== modified file 'modules/Ubuntu/Components/1.2/stack.js'
310--- modules/Ubuntu/Components/1.2/stack.js 2015-04-30 08:32:44 +0000
311+++ modules/Ubuntu/Components/1.2/stack.js 2015-07-10 13:56:40 +0000
312@@ -41,4 +41,5 @@
313 if (this.size() < 1) return undefined;
314 return elements[elements.length-1];
315 }
316+
317 }
318
319=== added file 'modules/Ubuntu/Components/1.3/ColumnMetrics.qml'
320--- modules/Ubuntu/Components/1.3/ColumnMetrics.qml 1970-01-01 00:00:00 +0000
321+++ modules/Ubuntu/Components/1.3/ColumnMetrics.qml 2015-07-10 13:56:40 +0000
322@@ -0,0 +1,53 @@
323+/*
324+ * Copyright 2015 Canonical Ltd.
325+ *
326+ * This program is free software; you can redistribute it and/or modify
327+ * it under the terms of the GNU Lesser General Public License as published by
328+ * the Free Software Foundation; version 3.
329+ *
330+ * This program is distributed in the hope that it will be useful,
331+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
332+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
333+ * GNU Lesser General Public License for more details.
334+ *
335+ * You should have received a copy of the GNU Lesser General Public License
336+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
337+ */
338+
339+import QtQuick 2.4
340+
341+/*!
342+ \qmltype ColumnMetrics
343+ \inqmlmodule Ubuntu.Components 1.3
344+ \since Ubuntu.Components 1.3
345+ \ingroup ubuntu
346+ \brief Component configuring the metrics of a column in MultiColumnView.
347+ \internal
348+
349+ */
350+QtObject {
351+ /*!
352+ 1-based value identifying the column the metrics to be applied to.
353+ */
354+ property int column
355+
356+ /*!
357+ Specifies whether the width of the column should fill the available space
358+ of the MultiColumnView column or not. Defaults to \a false.
359+ */
360+ property bool fillWidth: false
361+
362+ /*!
363+ Specifies the minimum width of the column. If the value is greater than
364+ \b MultiColumnView::defaultColumnWidth, the value will be set as width for
365+ the column.
366+ */
367+ property real minimumWidth: 0
368+
369+ /*!
370+ Specifies the maximum width of the column. If the value is smaller than
371+ \b MultiColumnView::defaultColumnWidth, the value will be set as width for
372+ the column. A maximum value of 0 will be ignored.
373+ */
374+ property real maximumWidth: Number.POSITIVE_INFINITY
375+}
376
377=== added file 'modules/Ubuntu/Components/1.3/MultiColumnView.qml'
378--- modules/Ubuntu/Components/1.3/MultiColumnView.qml 1970-01-01 00:00:00 +0000
379+++ modules/Ubuntu/Components/1.3/MultiColumnView.qml 2015-07-10 13:56:40 +0000
380@@ -0,0 +1,553 @@
381+/*
382+ * Copyright 2015 Canonical Ltd.
383+ *
384+ * This program is free software; you can redistribute it and/or modify
385+ * it under the terms of the GNU Lesser General Public License as published by
386+ * the Free Software Foundation; version 3.
387+ *
388+ * This program is distributed in the hope that it will be useful,
389+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
390+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
391+ * GNU Lesser General Public License for more details.
392+ *
393+ * You should have received a copy of the GNU Lesser General Public License
394+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
395+ */
396+
397+import QtQuick 2.4
398+import QtQuick.Layouts 1.1
399+import Ubuntu.Components 1.3
400+import "stack.js" as Stack
401+
402+/*!
403+ \qmltype MultiColumnView
404+ \inqmlmodule Ubuntu.Components 1.3
405+ \since Ubuntu.Components 1.3
406+ \ingroup ubuntu
407+ \brief View with multiple columns of Pages.
408+
409+ The component provides a flexible way of viewing a stack of pages in one or
410+ more columns. Unlike in PageStack, there can be more than one Page active at
411+ a time, depending on the number of the columns in the view.
412+
413+ MultiColumnView stores pages added in a tree. Pages are added relative to a
414+ given page, either as sibling (\l addPageToCurrentColumn) or as child
415+ (\l addPageToNextColumn). This means that removing a non-leaf page from the Page
416+ tree will remove all its children from the page tree.
417+
418+ The columns are populated from left to right. The column a page is added to is
419+ detected based on the source page that is given to the functions adding the page.
420+ The pages can be added either to the same column the source page resides or to
421+ the column next to the source page. Giving a null value to the source page will
422+ add the page to the leftmost column of the view.
423+
424+ The primary page, the very first page must be specified through the \l primaryPage
425+ property. The property cannot be changed after component completion and can hold
426+ a Page instance, a Component or a url to a document defining a Page. The page
427+ cannot be removed from the view.
428+
429+ \note Unlike PageStack or Page the component does not fill its parent by default.
430+
431+ \qml
432+ import QtQuick 2.4
433+ import Ubuntu.Components 1.3
434+
435+ MainView {
436+ width: units.gu(80)
437+ height: units.gu(71)
438+
439+ MultiColumnView {
440+ anchors.fill: parent
441+ primaryPage: page1
442+ Page {
443+ id: page1
444+ title: "Main page"
445+ Column {
446+ Button {
447+ text: "Add Page2 above " + title
448+ onClicked: page1.pageStack.addPageToCurrentColumn(page1, page2)
449+ }
450+ Button {
451+ text: "Add Page3 next to " + title
452+ onClicked: page1.pageStack.addPageToNextColumn(page1, page3)
453+ }
454+ }
455+ }
456+ Page {
457+ id: page2
458+ title: "Page #2"
459+ }
460+ Page {
461+ id: page3
462+ title: "Page #3"
463+ }
464+ }
465+ }
466+ \endqml
467+
468+ MultiColumnView supports adaptive column handling. When the number of columns changes at
469+ runtime the pages are automatically rearranged. To understand it better, let's take the following example:
470+ \qml
471+ import QtQuick 2.4
472+ import Ubuntu.Components 1.3
473+
474+ MainView {
475+ width: units.gu(120)
476+ height: units.gu(71)
477+
478+ MultiColumnView {
479+ anchors.fill: parent
480+ primaryPage: page1
481+ Page {
482+ id: page1
483+ title: "Main page"
484+ Button {
485+ text: "Add Page2 next to " + title
486+ onClicked: page1.pageStack.addPageToNextColumn(page1, page2)
487+ }
488+ }
489+ Page {
490+ id: page2
491+ title: "Page #2"
492+ Button {
493+ text: "Add Page3 next to " + title
494+ onClicked: page2.pageStack.addPageToNextColumn(page2, page3)
495+ }
496+ }
497+ Page {
498+ id: page3
499+ title: "Page #3"
500+ }
501+ }
502+ }
503+ \endqml
504+
505+ When the code is run on sufficiently wide screen, like a desktop or TV,
506+ it will launch with multiple columns.
507+
508+ \c page1 is set to be the primary page, \c page2 will be added to column next to
509+ \c page1 (to column 2) and \c page3 next to \c page2 (column 3). When the window
510+ is resized to have its size below 80 GU, the component will switch to 1 column
511+ mode, and \c page3 will be placed in the last column, and the header for \c page2
512+ will have a back button, indicating that there is a page below it. If the window
513+ is resized to contain only one column, all pages will be shown in that column, so
514+ the component will act as PageStack. Resizing the window back to 2 columns will place the pages side-by-side.
515+
516+ \note In the above example if \c page2 is removed, that will remove all its child
517+ pages, meaning \c page3 will also be removed.
518+
519+ \sa PageStack
520+*/
521+
522+PageTreeNode {
523+ id: multiColumnView
524+
525+ Page {
526+ // MultiColumnView has its own split headers, so
527+ // disable the application header.
528+ id: appHeaderControlPage
529+ head {
530+ locked: true
531+ visible: false
532+ }
533+ // title is set in attachPage() when the attached Page.column === 0
534+ }
535+
536+ /*!
537+ The property holds the first Page which will be added to the view. If the
538+ view has more than one column, the page will be added to the leftmost column.
539+ The property can hold either a Page instance, a component holding a Page
540+ or a QML document defining the Page. The property cannot be changed after
541+ component completion.
542+ */
543+ property var primaryPage
544+
545+ /*!
546+ \qmlmethod Item addPageToCurrentColumn(Item sourcePage, var page[, var properties])
547+ Adds a \c page to the column the \c sourcePage resides in. \c page can be a
548+ Component or a file. \c properties is a JSON object containing properties
549+ to be set when page is created. \c sourcePage must be active. Returns the
550+ instance of the page created.
551+ */
552+ function addPageToCurrentColumn(sourcePage, page, properties) {
553+ return d.addPageToColumn(d.columnForPage(sourcePage), sourcePage, page, properties);
554+ }
555+
556+ /*!
557+ \qmlmethod Item addPageToNextColumn(Item sourcePage, var page[, var properties])
558+ Same as \l addPageToCurrentColumn except that the \c page is added to the column
559+ next to the one the \c sourcePage resides. If \c sourcePage is null, the new
560+ page will be added to the leftmost column. If \c sourcePage is located in the
561+ rightmost column, the new page will be pushed to the same column as \c sourcePage.
562+ */
563+ function addPageToNextColumn(sourcePage, page, properties) {
564+ return d.addPageToColumn(d.columnForPage(sourcePage) + 1, sourcePage, page, properties);
565+ }
566+
567+ /*!
568+ \qmlmethod void removePages(Item page)
569+ The function removes and deletes all pages up to and including \c page
570+ is reached. If the \a page is the same as the \l primaryPage, only its child
571+ pages will be removed.
572+ */
573+ function removePages(page) {
574+ // remove nodes which have page as ascendant
575+ var node = d.stack.top();
576+ while (node && node.childOf(page)) {
577+ d.popAndSetPageForColumn(node);
578+ node = d.stack.top();
579+ }
580+ while (node && node.object == page && node != d.stack.first()) {
581+ d.popAndSetPageForColumn(node);
582+ node = d.stack.top();
583+ }
584+ }
585+
586+ /*
587+ internals
588+ */
589+
590+ Component.onCompleted: {
591+ d.relayout();
592+ d.completed = true;
593+ if (primaryPage) {
594+ var wrapper = d.createWrapper(primaryPage);
595+ d.addWrappedPage(wrapper);
596+ } else {
597+ console.warn("No primary page set. No pages can be added without a primary page.");
598+ }
599+ }
600+ onPrimaryPageChanged: {
601+ if (d.completed) {
602+ console.warn("Cannot change primaryPage after completion.");
603+ return;
604+ }
605+ }
606+
607+ QtObject {
608+ id: d
609+
610+ property bool completed: false
611+ property var stack: new Stack.Stack()
612+
613+ property int columns: multiColumnView.width >= units.gu(80) ? 2 : 1
614+ /*! internal */
615+ onColumnsChanged: {
616+ if (columns <= 0) {
617+ console.warn("There must be a minimum of one column set.");
618+ columns = 1;
619+ }
620+ d.relayout();
621+ }
622+ property real defaultColumnWidth: units.gu(40)
623+ onDefaultColumnWidthChanged: body.applyMetrics()
624+ property list<ColumnMetrics> columnMetrics
625+
626+ function createWrapper(page, properties) {
627+ var wrapperComponent = Qt.createComponent("PageWrapper.qml");
628+ var wrapperObject = wrapperComponent.createObject(hiddenPages);
629+ wrapperObject.pageStack = multiColumnView;
630+ wrapperObject.properties = properties;
631+ // set reference last because it will trigger creation of the object
632+ // with specified properties.
633+ wrapperObject.reference = page;
634+ return wrapperObject;
635+ }
636+
637+ function addWrappedPage(pageWrapper) {
638+ stack.push(pageWrapper);
639+ pageWrapper.parentWrapper = stack.find(pageWrapper.parentPage);
640+ var targetColumn = MathUtils.clamp(pageWrapper.column, 0, d.columns - 1);
641+ // replace page holder's child
642+ var holder = body.children[targetColumn];
643+ holder.detachCurrentPage();
644+ holder.attachPage(pageWrapper)
645+ }
646+
647+ function columnForPage(page) {
648+ var wrapper = stack.find(page);
649+ return wrapper ? wrapper.column : 0;
650+ }
651+
652+ function addPageToColumn(column, sourcePage, page, properties) {
653+ if (column < 0) {
654+ console.warn("Column must be >= 0.");
655+ return;
656+ }
657+ if (!sourcePage) {
658+ console.warn("No sourcePage specified. Page will not be added.");
659+ return;
660+ }
661+ if (!d.stack.find(sourcePage)) {
662+ console.warn("sourcePage must be added to the view to add new page.");
663+ return;
664+ }
665+
666+ var wrapper = d.createWrapper(page, properties);
667+ wrapper.parentPage = sourcePage;
668+ wrapper.column = column;
669+ d.addWrappedPage(wrapper);
670+ return wrapper.object;
671+ }
672+
673+ // node is a triplet of {page, column, parentPage}
674+ function popAndSetPageForColumn(node) {
675+ stack.pop();
676+ var effectiveColumn = MathUtils.clamp(node.column, 0, d.columns - 1);
677+ var columnHolder = body.children[effectiveColumn];
678+ // is the page in a column?
679+ if (node == columnHolder.pageWrapper) {
680+ // detach page from column
681+ columnHolder.detachCurrentPage();
682+ }
683+ node.parent = null;
684+ var prevPage = stack.topForColumn(effectiveColumn, effectiveColumn < d.columns - 1);
685+ if (prevPage) {
686+ columnHolder.attachPage(prevPage);
687+ }
688+ if (node.canDestroy) {
689+ node.destroyObject();
690+ }
691+ }
692+
693+ // relayouts when column count changes
694+ function relayout() {
695+ if (body.children.length == d.columns) return;
696+ if (body.children.length > d.columns) {
697+ // need to remove few columns, the last ones
698+ while (body.children.length > d.columns) {
699+ var holder = body.children[body.children.length - 1];
700+ holder.detachCurrentPage();
701+ holder.parent = null;
702+ holder.destroy();
703+ }
704+ } else {
705+ var prevColumns = body.children.length;
706+
707+ // add columns
708+ for (var i = 0; i < d.columns - prevColumns; i++) {
709+ pageHolderComponent.createObject(body);
710+ }
711+ }
712+ rearrangePages();
713+ }
714+
715+ function rearrangePages() {
716+ for (var column = d.columns - 1; column >= 0; column--) {
717+ var holder = body.children[column];
718+ var pageWrapper = stack.topForColumn(column, column < (d.columns - 1));
719+ if (!pageWrapper) {
720+ continue;
721+ }
722+ if (!pageWrapper.parent) {
723+ // this should never happen, so if it does, we have a bug!
724+ console.error("Found a page which wasn't parented anywhere!", pageWrapper.object.title);
725+ continue;
726+ }
727+ // detach current page from holder if differs
728+ if (holder.pageWrapper != pageWrapper) {
729+ holder.detachCurrentPage();
730+ }
731+ if (pageWrapper.parent == hiddenPages) {
732+ // add the page to the column
733+ holder.attachPage(pageWrapper);
734+ } else if (pageWrapper.pageHolder != holder) {
735+ // detach the pageWrapper from its holder
736+ if (pageWrapper.pageHolder) {
737+ pageWrapper.pageHolder.detachCurrentPage();
738+ }
739+ // then attach to this holder
740+ holder.attachPage(pageWrapper);
741+ }
742+ }
743+ }
744+ }
745+
746+ // default metrics
747+ Component {
748+ id: defaultMetrics
749+ ColumnMetrics {
750+ fillWidth: column == d.columns
751+ minimumWidth: d.defaultColumnWidth
752+ }
753+ }
754+
755+ // Page holder component, can have only one Page as child at a time, all stacked pages
756+ // will be parented into hiddenPages
757+ Component {
758+ id: pageHolderComponent
759+ // Page uses the height of the parentNode for its height, so make
760+ // the holder a PageTreeNode that determines the Page height.
761+ PageTreeNode {
762+ id: holder
763+ active: false
764+ objectName: "ColumnHolder" + column
765+ property PageWrapper pageWrapper
766+ property int column
767+ property alias config: subHeader.config
768+ property ColumnMetrics metrics: setDefaultMetrics()
769+
770+ Layout.fillWidth: metrics.fillWidth
771+ Layout.fillHeight: true
772+ Layout.preferredWidth: metrics.maximumWidth > 0 ?
773+ MathUtils.clamp(d.defaultColumnWidth, metrics.minimumWidth, metrics.maximumWidth) :
774+ d.defaultColumnWidth
775+ Layout.minimumWidth: metrics.minimumWidth
776+ Layout.maximumWidth: metrics.maximumWidth
777+
778+ // prevent the pages from taking the app header height into account.
779+ __propagated: null
780+ Item {
781+ id: holderBody
782+ objectName: parent.objectName + "Body"
783+ anchors {
784+ top: subHeader.bottom
785+ bottom: parent.bottom
786+ left: parent.left
787+ right: parent.right
788+ rightMargin: verticalDivider.width
789+ }
790+ // we need to clip because the header does not have a background
791+ clip: true
792+ }
793+
794+ property alias head: subHeader
795+ StyledItem {
796+ id: subHeader
797+ anchors {
798+ left: parent.left
799+ top: parent.top
800+ right: parent.right
801+ }
802+ height: body.headerHeight
803+
804+ styleName: config ? "PageHeadStyle" : ""
805+ theme.version: Ubuntu.toolkitVersion
806+ objectName: "Header" + column
807+
808+ property real preferredHeight: subHeader.__styleInstance ?
809+ subHeader.__styleInstance.implicitHeight :
810+ 0
811+ onPreferredHeightChanged: {
812+ body.updateHeaderHeight(preferredHeight);
813+ }
814+
815+ property PageHeadConfiguration config: null
816+ property Item contents: null
817+
818+ property color dividerColor: multiColumnView.__propagated.header.dividerColor
819+ property color panelColor: multiColumnView.__propagated.header.panelColor
820+
821+ visible: holder.pageWrapper && holder.pageWrapper.active
822+ }
823+
824+ Rectangle {
825+ id: verticalDivider
826+ anchors {
827+ top: parent.top
828+ bottom: parent.bottom
829+ right: parent.right
830+ }
831+ width: (column == (d.columns - 1)) || !pageWrapper ? 0 : units.dp(1)
832+ color: subHeader.dividerColor
833+ }
834+
835+ function attachPage(page) {
836+ pageWrapper = page;
837+ pageWrapper.parent = holderBody;
838+ pageWrapper.pageHolder = holder;
839+ pageWrapper.active = true;
840+
841+ if (pageWrapper.object.hasOwnProperty("head")) {
842+ subHeader.config = pageWrapper.object.head;
843+ }
844+ if (pageWrapper.column === 0 && pageWrapper.object.hasOwnProperty("title")) {
845+ // set the application title
846+ appHeaderControlPage.title = pageWrapper.object.title;
847+ }
848+ }
849+ function detachCurrentPage() {
850+ if (!pageWrapper) return undefined;
851+ var wrapper = pageWrapper;
852+ // remove header
853+ wrapper.active = false;
854+ subHeader.config = null;
855+ pageWrapper = null;
856+ wrapper.parent = hiddenPages;
857+ wrapper.pageHolder = null;
858+ return wrapper;
859+ }
860+
861+ function setDefaultMetrics() {
862+ var result = defaultMetrics.createObject(holder);
863+ result.column = Qt.binding(function() { return holder.column + 1; });
864+ return result;
865+ }
866+ }
867+ }
868+
869+ /*! \internal */
870+ // Pages declared as children will be placed directly into hiddenPages
871+ default property alias data: hiddenPages.data
872+ Item {
873+ id: hiddenPages
874+ objectName: "HiddenPagePool"
875+ visible: false
876+ // make sure nothing is shown eventually
877+ clip: true
878+ }
879+
880+ // Holds the columns holding the pages visible. Each column has only one page
881+ // as child, the invisible stacked ones are all stored in the hiddenPages
882+ // component. The stack keeps the column index onto which those should be moved
883+ // once they become visible.
884+ RowLayout {
885+ id: body
886+ objectName: "body"
887+ anchors.fill: parent
888+ spacing: 0
889+
890+ property real headerHeight: 0
891+
892+ function updateHeaderHeight(newHeight) {
893+ if (newHeight > body.headerHeight) {
894+ body.headerHeight = newHeight;
895+ } else {
896+ var h = 0;
897+ var subHeight = 0;
898+ for (var i = 0; i < children.length; i++) {
899+ subHeight = children[i].head.preferredHeight;
900+ if (subHeight > h) h = subHeight;
901+ }
902+ body.headerHeight = h;
903+ }
904+ }
905+
906+ onChildrenChanged: {
907+ // all children should have Layout.fillWidth false, except the last one
908+ for (var i = 0; i < children.length; i++) {
909+ children[i].column = i;
910+ }
911+ applyMetrics();
912+ }
913+
914+ function applyMetrics() {
915+ for (var i = 0; i < children.length; i++) {
916+ var holder = children[i];
917+ // search for the column metrics
918+ var metrics = null;
919+ for (var j = 0; j < d.columnMetrics.length; j++) {
920+ if (d.columnMetrics[j].column == (i + 1)) {
921+ metrics = d.columnMetrics[j];
922+ break;
923+ }
924+ }
925+ if (!metrics) {
926+ metrics = holder.setDefaultMetrics();
927+ }
928+ holder.metrics = metrics;
929+ updateHeaderHeight(0);
930+ }
931+ }
932+ }
933+}
934
935=== modified file 'modules/Ubuntu/Components/1.3/OrientationHelper.qml'
936--- modules/Ubuntu/Components/1.3/OrientationHelper.qml 2015-07-02 17:39:03 +0000
937+++ modules/Ubuntu/Components/1.3/OrientationHelper.qml 2015-07-10 13:56:40 +0000
938@@ -117,7 +117,7 @@
939 return 0;
940
941 var availableHeight = orientationHelper.parent.height;
942- if (d.stateAngle === 0 && UbuntuApplication.inputMethod.visible && anchorToKeyboard)
943+ if (d.stateAngle === 0 && UbuntuApplication.inputMethod && UbuntuApplication.inputMethod.visible && anchorToKeyboard)
944 availableHeight -= UbuntuApplication.inputMethod.keyboardRectangle.height;
945 return availableHeight;
946 }
947
948=== modified file 'modules/Ubuntu/Components/1.3/Page.qml'
949--- modules/Ubuntu/Components/1.3/Page.qml 2015-07-02 20:40:01 +0000
950+++ modules/Ubuntu/Components/1.3/Page.qml 2015-07-10 13:56:40 +0000
951@@ -43,6 +43,7 @@
952 readonly property alias head: headerConfig
953 Toolkit13.PageHeadConfiguration {
954 id: headerConfig
955+ title: page.title
956 }
957
958 Toolkit13.Object {
959
960=== modified file 'modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml'
961--- modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-04-30 08:32:44 +0000
962+++ modules/Ubuntu/Components/1.3/PageHeadConfiguration.qml 2015-07-10 13:56:40 +0000
963@@ -56,4 +56,6 @@
964
965 // auto-updated by AppHeader, but may be set by the developer
966 property bool visible
967+
968+ property string title
969 }
970
971=== modified file 'modules/Ubuntu/Components/1.3/PageWrapper.qml'
972--- modules/Ubuntu/Components/1.3/PageWrapper.qml 2015-05-05 16:23:29 +0000
973+++ modules/Ubuntu/Components/1.3/PageWrapper.qml 2015-07-10 13:56:40 +0000
974@@ -46,6 +46,43 @@
975 property bool canDestroy: false
976
977 /*!
978+ Column number in MultiColumnView.
979+ */
980+ property int column: 0
981+
982+ /*!
983+ Parent page.
984+ */
985+ property Item parentPage
986+
987+ /*!
988+ Parent PageWrapper or the parentPage.
989+ */
990+ property Item parentWrapper
991+
992+ /*!
993+ Page holder in MultiColumnView
994+ */
995+ property Item pageHolder
996+
997+ /*!
998+ Returns true if the current PageWrapper is a child of the given page
999+ */
1000+ function childOf(page) {
1001+ if (parentPage == page) return true;
1002+ if (page && parentWrapper) {
1003+ var wrapper = parentWrapper;
1004+ while (wrapper) {
1005+ if (wrapper.object == page) {
1006+ return true;
1007+ }
1008+ wrapper = wrapper.parentWrapper;
1009+ }
1010+ }
1011+ return false;
1012+ }
1013+
1014+ /*!
1015 This value is updated when a PageWrapper is pushed to/popped from a PageStack.
1016 */
1017 active: false
1018
1019=== added file 'modules/Ubuntu/Components/1.3/stack.js'
1020--- modules/Ubuntu/Components/1.3/stack.js 1970-01-01 00:00:00 +0000
1021+++ modules/Ubuntu/Components/1.3/stack.js 2015-07-10 13:56:40 +0000
1022@@ -0,0 +1,75 @@
1023+/*
1024+ * Copyright 2015 Canonical Ltd.
1025+ *
1026+ * This program is free software; you can redistribute it and/or modify
1027+ * it under the terms of the GNU Lesser General Public License as published by
1028+ * the Free Software Foundation; version 3.
1029+ *
1030+ * This program is distributed in the hope that it will be useful,
1031+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1032+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1033+ * GNU Lesser General Public License for more details.
1034+ *
1035+ * You should have received a copy of the GNU Lesser General Public License
1036+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1037+ */
1038+
1039+.pragma library
1040+// By defining Stack as a function, we can make its variables private,
1041+// and force calls to Stack to make use of the functions we define.
1042+function Stack() {
1043+ var elements;
1044+ this.clear = function() {
1045+ elements = [];
1046+ }
1047+
1048+ this.clear();
1049+
1050+ this.push = function(element) {
1051+ elements.push(element);
1052+ };
1053+
1054+ this.pop = function() {
1055+ elements.pop();
1056+ };
1057+
1058+ this.size = function() {
1059+ return elements.length;
1060+ }
1061+
1062+ this.first = function() {
1063+ return this.size() > 0 ? elements[0] : null;
1064+ }
1065+
1066+ this.top = function() {
1067+ if (this.size() < 1) return undefined;
1068+ return elements[elements.length-1];
1069+ }
1070+
1071+ // returns the topmost element for the column, using exact column match
1072+ // in case the column to be extracted is less than the columns available
1073+ this.topForColumn = function(column, exactMatch) {
1074+ if (exactMatch == undefined) {
1075+ exactMatch = false;
1076+ }
1077+
1078+ for (var i = elements.length - 1; i >= 0; i--) {
1079+ var node = elements[i];
1080+ if ((exactMatch && elements[i].column == column) || (!exactMatch && elements[i].column >= column)) {
1081+ return elements[i];
1082+ }
1083+ }
1084+ return undefined;
1085+ }
1086+
1087+ // returns the node the Page is stored; undefined if not found
1088+ this.find = function(page) {
1089+ if (!page) return null;
1090+ for (var i = elements.length - 1; i >= 0; i--) {
1091+ if (elements[i].object == page) {
1092+ return elements[i];
1093+ }
1094+ }
1095+ return null;
1096+ }
1097+}
1098
1099=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml'
1100--- modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml 2015-05-26 10:54:14 +0000
1101+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/IconButtonStyle.qml 2015-07-10 13:56:40 +0000
1102@@ -26,7 +26,7 @@
1103 /*!
1104 The color of the icons.
1105 */
1106- property color foregroundColor: "#808080"
1107+ property color foregroundColor: "#333333"
1108
1109 /*!
1110 The background color of the button.
1111
1112=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml'
1113--- modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-06-23 13:22:10 +0000
1114+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-07-10 13:56:40 +0000
1115@@ -27,15 +27,22 @@
1116 textLeftMargin: units.gu(2)
1117 maximumNumberOfActions: 3
1118
1119+ PageHeadConfiguration {
1120+ id: defaultConfig
1121+ }
1122+
1123+ property PageHeadConfiguration config: styledItem.config ?
1124+ styledItem.config :
1125+ defaultConfig
1126 /*!
1127 The color of the buttons in the header.
1128 */
1129- property color buttonColor: styledItem.config.foregroundColor
1130+ property color buttonColor: headerStyle.config.foregroundColor
1131
1132 /*!
1133 The color of the title text.
1134 */
1135- property color titleColor: styledItem.config.foregroundColor
1136+ property color titleColor: headerStyle.config.foregroundColor
1137
1138 // FIXME: When the three panel color properties below are removed,
1139 // update unity8/Dash/PageHeader to use the new theming (currently
1140@@ -84,6 +91,12 @@
1141 // have a separator.
1142 property alias __separator_visible: divider.visible
1143
1144+ //Default background for the header is white according to latest visuals
1145+ Rectangle {
1146+ anchors.fill: parent
1147+ color: "white"
1148+ }
1149+
1150 Rectangle {
1151 id: divider
1152 anchors {
1153@@ -100,16 +113,23 @@
1154 objectName: "headerSectionsItem"
1155 anchors {
1156 left: parent.left
1157- leftMargin: units.gu(2)
1158+ right: parent.right
1159+ //leftMargin: units.gu(2)
1160 bottom: divider.top
1161 }
1162+ visible: model && model.length > 0
1163 enabled: sections.enabled
1164- height: model && model.length > 0 ? implicitHeight : 0
1165-
1166- property PageHeadSections sections: styledItem.config.sections
1167- model: sections.model
1168-
1169- onSelectedIndexChanged: sections.selectedIndex = sectionsItem.selectedIndex
1170+ height: visible ? implicitHeight : 0
1171+
1172+ property PageHeadSections sections: headerStyle.config.sections
1173+ model: sections ? sections.model : null
1174+
1175+ onSelectedIndexChanged: {
1176+ if (sections) {
1177+ sections.selectedIndex = sectionsItem.selectedIndex;
1178+ }
1179+ }
1180+
1181 Connections {
1182 target: sectionsItem.sections
1183 onSelectedIndexChanged: sectionsItem.selectedIndex = sectionsItem.sections.selectedIndex
1184@@ -244,10 +264,10 @@
1185 PageHeadButton {
1186 id: customBackButton
1187 objectName: "customBackButton"
1188- action: styledItem.config.backAction
1189- visible: null !== styledItem.config.backAction &&
1190- styledItem.config.backAction.visible
1191- color: styledItem.config.foregroundColor
1192+ action: headerStyle.config.backAction
1193+ visible: null !== headerStyle.config.backAction &&
1194+ headerStyle.config.backAction.visible
1195+ color: headerStyle.config.foregroundColor
1196 }
1197
1198 PageHeadButton {
1199@@ -258,10 +278,10 @@
1200 visible: styledItem.pageStack !== null &&
1201 styledItem.pageStack !== undefined &&
1202 styledItem.pageStack.depth > 1 &&
1203- !styledItem.config.backAction
1204+ !headerStyle.config.backAction
1205
1206 text: "back"
1207- color: styledItem.config.foregroundColor
1208+ color: headerStyle.config.foregroundColor
1209
1210 onTriggered: {
1211 styledItem.pageStack.pop();
1212@@ -327,13 +347,13 @@
1213 Label {
1214 objectName: "header_title_label"
1215 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
1216- visible: !contentsContainer.visible && styledItem.config.preset === ""
1217+ visible: !contentsContainer.visible && headerStyle.config.preset === ""
1218 anchors {
1219 left: parent.left
1220 right: parent.right
1221 verticalCenter: parent.verticalCenter
1222 }
1223- text: styledItem.title
1224+ text: headerStyle.config.title
1225 font.weight: headerStyle.fontWeight
1226 fontSize: headerStyle.fontSize
1227 color: headerStyle.titleColor
1228@@ -346,7 +366,7 @@
1229 // when the bindings below is no longer active
1230 id: contentsContainer
1231 anchors.fill: parent
1232- visible: styledItem.contents || styledItem.config.contents
1233+ visible: styledItem.contents || headerStyle.config.contents
1234 }
1235 Binding {
1236 target: styledItem.contents
1237@@ -361,16 +381,23 @@
1238 when: styledItem.contents
1239 }
1240 Binding {
1241- target: styledItem.config.contents
1242+ target: headerStyle.config.contents
1243 property: "parent"
1244 value: contentsContainer
1245- when: styledItem.config.contents && !styledItem.contents
1246+ when: headerStyle.config.contents && !styledItem.contents
1247 }
1248 }
1249
1250 ActionBar {
1251 id: actionsContainer
1252 objectName: "headerActionBar"
1253+
1254+ //currently hardcoded maximum number of actions we want to show in the header
1255+ property int maxActions: 6
1256+
1257+ //UX: we reserver max 30% of the header width for actions
1258+ property real headerPortionDedicatedToActions: 0.3
1259+
1260 anchors {
1261 top: parent.top
1262 right: rightAnchor.left
1263@@ -378,8 +405,10 @@
1264 }
1265 height: headerStyle.contentHeight
1266
1267- actions: styledItem.config.actions
1268- numberOfSlots: 3
1269+ actions: headerStyle.config.actions
1270+
1271+ //FIXME: currently hardcoded icon width!
1272+ numberOfSlots: Math.min(Math.min(Math.floor(headerStyle.width * headerPortionDedicatedToActions / units.gu(4)), actions.length), maxActions)
1273 }
1274 }
1275 }
1276
1277=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml'
1278--- modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml 2015-04-27 07:19:22 +0000
1279+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/Palette.qml 2015-07-10 13:56:40 +0000
1280@@ -33,7 +33,7 @@
1281 }
1282 selected {
1283 background: Qt.rgba(0, 0, 0, 0.05)
1284- backgroundText: UbuntuColors.darkGrey
1285+ backgroundText: "#333333"//"#5D5D5D"//UbuntuColors.darkGrey
1286 selection: selected.foreground
1287 foreground: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.2)
1288 foregroundText: UbuntuColors.darkGrey
1289
1290=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml'
1291--- modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-06-16 23:18:45 +0000
1292+++ modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-07-10 13:56:40 +0000
1293@@ -1,3 +1,20 @@
1294+/* !!!!!!!!!! THIS IS A WIP SECTION HEADER !!!!!!!!!!!!!!!
1295+
1296+ * Copyright 2015 Canonical Ltd.
1297+ *
1298+ * This program is free software; you can redistribute it and/or modify
1299+ * it under the terms of the GNU Lesser General Public License as published by
1300+ * the Free Software Foundation; version 3.
1301+ *
1302+ * This program is distributed in the hope that it will be useful,
1303+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1304+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1305+ * GNU Lesser General Public License for more details.
1306+ *
1307+ * You should have received a copy of the GNU Lesser General Public License
1308+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1309+ */
1310+
1311 /*
1312 * Copyright 2015 Canonical Ltd.
1313 *
1314@@ -15,20 +32,33 @@
1315 */
1316 import QtQuick 2.4
1317 import Ubuntu.Components 1.3
1318+import QtGraphicalEffects 1.0
1319
1320 Item {
1321 id: sectionsStyle
1322
1323- implicitWidth: sectionsRow.width
1324+ //VISUAL CHANGES:
1325+ //- sectionColor
1326+ //- added underlineColor
1327+ //- fontsize to medium
1328+ //- font weight to Light
1329+
1330+ implicitWidth: units.gu(50) //sectionsRow.width
1331 implicitHeight: units.gu(4)
1332
1333 enabled: styledItem.enabled
1334 opacity: enabled ? 1.0 : 0.5
1335-
1336 /*!
1337 The foreground color of unselected sections.
1338 */
1339- property color sectionColor: theme.palette.selected.backgroundText
1340+ //FIXME: hardcoded color
1341+ property color sectionColor: "#888888"//theme.palette.selected.backgroundText
1342+
1343+ /*!
1344+ The foreground color of underline rectangle of unselected sections.
1345+ */
1346+ //FIXME: hardcoded color
1347+ property color underlineColor: Qt.rgba(0,0,0,0.2)//theme.palette.selected.backgroundText
1348
1349 /*!
1350 The foreground color of the selected section.
1351@@ -43,7 +73,7 @@
1352 /*!
1353 The font size for the text in the buttons.
1354 */
1355- property string fontSize: "small"
1356+ property string fontSize: "medium"
1357
1358 /*!
1359 The spacing on the left and right sides of the label
1360@@ -56,30 +86,174 @@
1361 */
1362 property real underlineHeight: units.dp(2)
1363
1364- Row {
1365- id: sectionsRow
1366- anchors {
1367- top: parent.top
1368- bottom: parent.bottom
1369- horizontalCenter: parent.horizontalCenter
1370- }
1371- width: childrenRect.width
1372-
1373- Repeater {
1374- id: sectionsRepeater
1375+ property real __listViewMargin: units.gu(3)
1376+ property bool __hoveringLeft: false
1377+ property bool __hoveringRight: false
1378+
1379+ onStateChanged: console.log(state)
1380+
1381+ clip: true
1382+
1383+ //This item is needed for the OpacityMask feature. It is needed to make sure that when we
1384+ //bring a list element into view, that element won't be covered by the opacity mask. So we
1385+ //disable clipping on the list but we give it margins. This way when an item is repositioned
1386+ //to be within the listview, that item will not be positioned under the opacity mask. (which is
1387+ //what would have happened if the listview were filling the parent)
1388+ Item {
1389+ id: listviewcontainer
1390+ anchors.fill: parent
1391+
1392+ //We need this because OpacityMask will draw this listview for us, but at the
1393+ //same time we still want to get the input events!
1394+ opacity: 0
1395+
1396+ ListView {
1397+ id: sectionsListView
1398+ objectName: "section_listview"
1399+
1400+ property bool animateContentX: false
1401+
1402+ //this is just to disable keyboard navigation to avoid messing with contentX/contentWidth while
1403+ //the view is moving
1404+ focus: !moving
1405+
1406+ SmoothedAnimation {
1407+ id: contentXAnim
1408+ target: sectionsListView
1409+ property: "contentX"
1410+ duration: UbuntuAnimation.FastDuration
1411+ velocity: units.gu(10)
1412+ //alwaysRunToEnd: true
1413+ }
1414+
1415+ function ensureItemIsInTheMiddle(currentItem) {
1416+ if (currentItem !== null) {
1417+ //stop the flick before doing computations
1418+ if (moving) {
1419+ return
1420+ }
1421+ if (dragging || flicking) {
1422+ cancelFlick()
1423+ }
1424+
1425+ console.log("CONTENTX", contentX)
1426+ if (contentXAnim.running) { contentXAnim.stop() }
1427+ console.log("CONTENTXAFTER", contentX)
1428+
1429+ var pos = currentItem.mapToItem(sectionsListView.contentItem, 0, 0)
1430+
1431+ //In the case where we're moving to an item which is outside of the screen (this
1432+ //happens when using Keyboard navigation after scrolling the view), we
1433+ //try working around the fact that contentWidth/X keeps changing
1434+ //by using listview's functions (WHICH DON'T HAVE ANIMATIONS though) just to position the
1435+ //view more or less at the right place. After that, we'll fake an animation from one side to the middle of the
1436+ //screen.
1437+ /*if (pos.x + pos.width <= sectionsListView.x) {
1438+ positionViewAtIndex(currentIndex, ListView.Beginning)
1439+ //refresh the mapping as we've moved the view
1440+ pos = currentItem.mapToItem(sectionsListView, 0, 0)
1441+
1442+ } else if (pos.x >= sectionsListView.x + sectionsListView.width) {
1443+ positionViewAtIndex(currentIndex, ListView.End)
1444+ //refresh the mapping as we've moved the view
1445+ pos = currentItem.mapToItem(sectionsListView, 0, 0)
1446+ }*/
1447+
1448+ var newContentX = pos.x - sectionsListView.width/2 + currentItem.width/2
1449+ contentXAnim.from = contentX
1450+ //make sure we don't overshoot bounds
1451+ contentXAnim.to = Math.max(originX, Math.min(newContentX, originX + contentWidth - width))
1452+ contentXAnim.start()
1453+
1454+ console.log("OUR VALUES", contentX, originX, contentWidth, contentXAnim.to)
1455+ //FIXME: due to listview internal implementation and the fact that delegates don't have a fixed size,
1456+ //this breaks in some cases, due to originX changing *after* we start the
1457+ //animation. In those cases the current item doesn't align to the horizontal center because we compute
1458+ //contentXAnim.to based on an originX which is then changed by the internals after we have already
1459+ //started the animation. We could fixup the position at the end of the animation,
1460+ //but that would just be a workaround.
1461+ }
1462+ }
1463+
1464+ onContentXChanged: console.log(contentX, originX, contentWidth)
1465+
1466+ anchors.fill: parent
1467+ anchors.leftMargin: __listViewMargin
1468+ anchors.rightMargin: __listViewMargin
1469+
1470+ //FIXME: this means when we resize the window it will refocus on the current item even if it's outside of the view!
1471+ //NOTE: removing this also breaks the alignment when the sections are initialized, because of contentX/contentWidth changing
1472+ onWidthChanged: ensureItemIsInTheMiddle(currentItem)
1473+
1474+ orientation: ListView.Horizontal
1475+
1476+ //We want to block dragging but at the same time we want the keyboard to work!!!
1477+ //interactive: contentWidth > width
1478+ boundsBehavior: Flickable.StopAtBounds
1479+
1480 model: styledItem.model
1481- objectName: "sections_repeater"
1482- AbstractButton {
1483+
1484+ //We need this to make sure that we have delegates for the whole width, since we have
1485+ //clip disabled. Read more
1486+ displayMarginBeginning: __listViewMargin
1487+ displayMarginEnd: __listViewMargin
1488+
1489+ //make sure that the currentItem is in the middle when everything is initialized
1490+ Component.onCompleted: ensureItemIsInTheMiddle(currentItem)
1491+
1492+ //FIXME: keyboard navigation offered by ListView will break this, won't it?
1493+ currentIndex: styledItem.selectedIndex
1494+ onCurrentIndexChanged: {
1495+ styledItem.selectedIndex = currentIndex
1496+ }
1497+ onCurrentItemChanged: {
1498+ //adjust contentX so that the item is kept in the middle
1499+ //don't use ListView.ApplyRange because that does an awkward animation when you select an item
1500+ //*while* the current item is outside of screen
1501+ ensureItemIsInTheMiddle(currentItem)
1502+ }
1503+
1504+ //highlightMoveDuration: UbuntuAnimation.BriskDuration
1505+ //highlightResizeDuration: UbuntuAnimation.BriskDuration
1506+
1507+ highlightFollowsCurrentItem: false
1508+ //DON'T use ApplyRange, because it will cause a weird animation when you select an item which is on screen
1509+ //with the previously selected item being out of screen
1510+ //highlightRangeMode: ListView.ApplyRange
1511+ //preferredHighlightEnd: width/2 + currentItem.width/2
1512+ //preferredHighlightBegin: width/2 - currentItem.width/2
1513+
1514+ //highlightFollowsCurrentItem will resize the highlight element to fill the delegate,
1515+ //so we need an Item in the middle to set a height for the highlight rectangle different
1516+ //from the delegate size
1517+ highlight: Item {
1518+ //show the highlight on top of the delegate
1519+ z: 2
1520+
1521+ x: sectionsListView.currentItem ? sectionsListView.currentItem.x : -width
1522+ width: sectionsListView.currentItem ? sectionsListView.currentItem.width : 0
1523+ height: sectionsListView.currentItem ? sectionsListView.currentItem.height : 0
1524+
1525+ Rectangle {
1526+ anchors {
1527+ bottom: parent.bottom
1528+ left: parent.left
1529+ right: parent.right
1530+ }
1531+ height: sectionsStyle.underlineHeight
1532+ color: sectionsStyle.selectedSectionColor
1533+ }
1534+
1535+ Behavior on x { UbuntuNumberAnimation {} }
1536+ }
1537+
1538+ delegate: AbstractButton {
1539 id: sectionButton
1540- anchors {
1541- top: parent ? parent.top : undefined
1542- bottom: parent ? parent.bottom : undefined
1543- }
1544 objectName: "section_button_" + index
1545 width: label.width + 2 * sectionsStyle.horizontalLabelSpacing
1546- height: sectionsRow.height
1547+ height: sectionsStyle.height
1548 property bool selected: index === styledItem.selectedIndex
1549- onClicked: styledItem.selectedIndex = index
1550+ onClicked: { styledItem.selectedIndex = index; sectionsListView.forceActiveFocus() }
1551
1552 // Background pressed highlight
1553 Rectangle {
1554@@ -95,10 +269,14 @@
1555 // modelData may be either a string, or an Action
1556 text: modelData.hasOwnProperty("text") ? modelData.text : modelData
1557 fontSize: sectionsStyle.fontSize
1558+ font.weight: Font.Light
1559 anchors.centerIn: parent
1560 color: sectionButton.selected ?
1561 sectionsStyle.selectedSectionColor :
1562 sectionsStyle.sectionColor
1563+
1564+ //FIXME: color animation? is that ok to design? what's the duration?
1565+ Behavior on color { ColorAnimation { duration: 500 } }
1566 }
1567
1568 // Section title underline
1569@@ -109,11 +287,167 @@
1570 right: parent.right
1571 }
1572 height: sectionsStyle.underlineHeight
1573- color: sectionButton.selected ?
1574- sectionsStyle.selectedSectionColor :
1575- sectionsStyle.sectionColor
1576+ color: sectionsStyle.underlineColor
1577 }
1578 }
1579- }
1580- }
1581+
1582+ //Behavior on contentX { SmoothedAnimation { duration: UbuntuAnimation.FastDuration; velocity: units.gu(10)} }
1583+
1584+ }
1585+ }
1586+
1587+ MouseArea {
1588+ id: hoveringArea
1589+
1590+ function checkHovering(mouse) {
1591+ if (mouse.x < __listViewMargin) {
1592+ if (!__hoveringLeft) __hoveringLeft = true
1593+ } else if (mouse.x > width - __listViewMargin ) {
1594+ if (!__hoveringRight) __hoveringRight = true
1595+ } else {
1596+ __hoveringLeft = false
1597+ __hoveringRight = false
1598+ }
1599+ }
1600+
1601+ onContainsMouseChanged: console.log(containsMouse)
1602+ anchors.fill: parent
1603+ hoverEnabled: true
1604+
1605+ onPositionChanged: checkHovering(mouse)
1606+ onExited: {
1607+ __hoveringLeft = false
1608+ __hoveringRight = false
1609+ }
1610+ onPressed: if (!__hoveringLeft && !__hoveringRight) {
1611+ mouse.accepted = false
1612+ }
1613+ onClicked: {
1614+ //scroll the list to bring the element which is under the cursor into the view
1615+ //var item = sectionsListView.itemAt(mouse.x + sectionsListView.contentX - __listViewMargin, mouse.y)
1616+ //if (item !== null) {
1617+ //We could use positionViewAtIndex(...) here but it wouldn't provide animation
1618+
1619+ if (contentXAnim.running) contentXAnim.stop()
1620+
1621+ //Scroll one item at a time with animation
1622+ //sectionsListView.contentX = __hoveringLeft ? item.mapToItem(sectionsListView.contentItem, 0,0).x : item.mapToItem(sectionsListView.contentItem, 0,0).x - sectionsListView.width + item.width
1623+ var newContentX = sectionsListView.contentX + (sectionsListView.width * (__hoveringLeft ? -1 : 1))
1624+
1625+ contentXAnim.from = sectionsListView.contentX
1626+ //make sure we don't overshoot bounds
1627+ contentXAnim.to = Math.max(sectionsListView.originX, Math.min(newContentX, sectionsListView.originX + sectionsListView.contentWidth - sectionsListView.width))
1628+
1629+ contentXAnim.start()
1630+
1631+ //}
1632+ }
1633+
1634+ property real iconsDisabledOpacity: 0.3
1635+
1636+ Icon {
1637+ id: leftHoveringIcon
1638+ anchors.left: parent.left
1639+ anchors.leftMargin: units.gu(1)
1640+ anchors.verticalCenter: parent.verticalCenter
1641+ width: units.gu(1)
1642+ height: units.gu(1)
1643+ rotation: 180
1644+ opacity: sectionsListView.atXBeginning ? hoveringArea.iconsDisabledOpacity : 1.0
1645+ name: "chevron"
1646+ }
1647+
1648+ Icon {
1649+ id: rightHoveringIcon
1650+ anchors.right: parent.right
1651+ anchors.rightMargin: units.gu(1)
1652+ anchors.verticalCenter: parent.verticalCenter
1653+ width: units.gu(1)
1654+ height: units.gu(1)
1655+ opacity: sectionsListView.atXEnd ? hoveringArea.iconsDisabledOpacity : 1.0
1656+ name: "chevron"
1657+ }
1658+ }
1659+
1660+ Rectangle {
1661+ anchors.left: parent.left
1662+ anchors.right: parent.right
1663+ anchors.bottom: parent.bottom
1664+ height: units.dp(1)
1665+ color: sectionsStyle.underlineColor
1666+ }
1667+
1668+ LinearGradient {
1669+ id: gradient
1670+
1671+ visible: false
1672+ anchors.fill: parent
1673+ start: Qt.point(0,0)
1674+ end: Qt.point(width,0)
1675+
1676+ property real __gradientWidth: units.gu(3) / gradient.width
1677+ //the width is __gradientWidth, but we want the gradient to actually start/finish at __gradientSplitPosition
1678+ //just to leave some margin.
1679+ property real __gradientSplitPosition: 3/4 * __gradientWidth
1680+
1681+ gradient: Gradient {
1682+ //left gradient
1683+ GradientStop { position: 0.0 ; color: Qt.rgba(1,1,1,0) }
1684+ GradientStop { position: gradient.__gradientSplitPosition ; color: Qt.rgba(1,1,1,0) }
1685+ GradientStop { position: gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) }
1686+ //right gradient
1687+ GradientStop { position: 1.0 - gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) }
1688+ GradientStop { position: 1.0 - gradient.__gradientSplitPosition; color: Qt.rgba(1,1,1,0) }
1689+ GradientStop { position: 1.0; color: Qt.rgba(1,1,1,0) }
1690+ }
1691+
1692+ }
1693+
1694+ OpacityMask {
1695+ id: mask
1696+ anchors.fill: parent
1697+
1698+ source: listviewcontainer
1699+ maskSource: gradient
1700+ }
1701+
1702+
1703+ //Since we only show one arrow at a time, let's reuse the same item and handle the property changes with states
1704+ states: [
1705+ State {
1706+ name: ""
1707+ PropertyChanges { target: mask; visible: false }
1708+ PropertyChanges { target: listviewcontainer; opacity: 1.0 }
1709+ PropertyChanges { target: leftHoveringIcon; visible: false; }
1710+ PropertyChanges { target: rightHoveringIcon; visible: false; }
1711+ },
1712+ State {
1713+ name: "hovering"
1714+ when: hoveringArea.containsMouse
1715+ PropertyChanges { target: mask; visible: true }
1716+ PropertyChanges { target: listviewcontainer; opacity: 0.0 }
1717+ PropertyChanges { target: leftHoveringIcon; visible: true; }
1718+ PropertyChanges { target: rightHoveringIcon; visible: true; }
1719+ }
1720+ //one of the experiments was to only show one arrow at a time on hover
1721+ /*State {
1722+ name: "hoveringLeft"
1723+ when: __hoveringLeft
1724+ PropertyChanges { target: mask; visible: true }
1725+ PropertyChanges { target: gradient; mirrored: true }
1726+ PropertyChanges { target: listviewcontainer; opacity: 0.0 }
1727+ PropertyChanges { target: hoveringIcon; visible: true; rotation: 180; anchors.leftMargin: units.gu(1) }
1728+ AnchorChanges { target: hoveringIcon; anchors.left: hoveringArea.left; anchors.right: undefined }
1729+ },
1730+ State {
1731+ name: "hoveringRight"
1732+ when: __hoveringRight
1733+ PropertyChanges { target: mask; visible: true }
1734+ PropertyChanges { target: gradient; mirrored: false }
1735+ PropertyChanges { target: listviewcontainer; opacity: 0.0 }
1736+ PropertyChanges { target: hoveringIcon; visible: true; rotation: 0; anchors.rightMargin: units.gu(1) }
1737+ AnchorChanges { target: hoveringIcon; anchors.right: hoveringArea.right; anchors.left: undefined }
1738+ }*/
1739+ ]
1740 }
1741+
1742
1743=== modified file 'modules/Ubuntu/Components/qmldir'
1744--- modules/Ubuntu/Components/qmldir 2015-05-24 14:14:36 +0000
1745+++ modules/Ubuntu/Components/qmldir 2015-07-10 13:56:40 +0000
1746@@ -141,3 +141,4 @@
1747 PullToRefresh 1.3 1.3/PullToRefresh.qml
1748 UbuntuListView 1.3 1.3/UbuntuListView11.qml
1749 Captions 1.3 1.3/Captions.qml
1750+MultiColumnView 1.3 1.3/MultiColumnView.qml
1751
1752=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/__init__.py'
1753--- tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-04-14 21:02:06 +0000
1754+++ tests/autopilot/ubuntuuitoolkit/tests/__init__.py 2015-07-10 13:56:40 +0000
1755@@ -205,10 +205,9 @@
1756
1757 def checkPageHeader(self, pageTitle):
1758 orientationHelper = self.getOrientationHelper()
1759- header = orientationHelper.select_single("AppHeader", title=pageTitle)
1760- self.assertThat(header, Not(Is(None)))
1761- self.assertThat(header.visible, Eventually(Equals(True)))
1762- return header
1763+ header_label = orientationHelper.select_single(objectName="header_title_label", text=pageTitle)
1764+ self.assertThat(header_label, Not(Is(None)))
1765+ self.assertThat(header_label.visible, Eventually(Equals(True)))
1766
1767 def getObject(self, objectName):
1768 obj = self.app.select_single(objectName=objectName)
1769
1770=== modified file 'tests/resources/navigation/MyCustomPage.qml'
1771--- tests/resources/navigation/MyCustomPage.qml 2015-03-03 13:20:06 +0000
1772+++ tests/resources/navigation/MyCustomPage.qml 2015-07-10 13:56:40 +0000
1773@@ -15,7 +15,7 @@
1774 */
1775
1776 import QtQuick 2.2
1777-import Ubuntu.Components 1.1
1778+import Ubuntu.Components 1.3
1779
1780 Page {
1781 title: i18n.tr("My custom page")
1782
1783=== added file 'tests/resources/navigation/SplitViewStack.qml'
1784--- tests/resources/navigation/SplitViewStack.qml 1970-01-01 00:00:00 +0000
1785+++ tests/resources/navigation/SplitViewStack.qml 2015-07-10 13:56:40 +0000
1786@@ -0,0 +1,137 @@
1787+/*
1788+ * Copyright 2015 Canonical Ltd.
1789+ *
1790+ * This program is free software; you can redistribute it and/or modify
1791+ * it under the terms of the GNU Lesser General Public License as published by
1792+ * the Free Software Foundation; version 3.
1793+ *
1794+ * This program is distributed in the hope that it will be useful,
1795+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1796+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1797+ * GNU Lesser General Public License for more details.
1798+ *
1799+ * You should have received a copy of the GNU Lesser General Public License
1800+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1801+ */
1802+
1803+import QtQuick 2.4
1804+import Ubuntu.Components 1.3
1805+import QtQuick.Layouts 1.1
1806+
1807+MainView {
1808+ id: main
1809+ width: units.gu(100)
1810+ height: units.gu(71)
1811+
1812+ MultiColumnView {
1813+ id: view
1814+ anchors.fill: parent
1815+ columns: {
1816+ if (width > units.gu(120)) return 3;
1817+ if (width > units.gu(80)) return 2;
1818+ return 1;
1819+ }
1820+ columnMetrics: [
1821+ ColumnMetrics {
1822+ column: 2
1823+ fillWidth: true
1824+ },
1825+ ColumnMetrics {
1826+ column: 3
1827+ fillWidth: false
1828+ minimumWidth: units.gu(50)
1829+ }
1830+ ]
1831+ primaryPage: page1
1832+
1833+ Page {
1834+ id: page1
1835+ title: "Page #1"
1836+
1837+ Rectangle {
1838+ anchors.fill: parent
1839+ color: "red"
1840+ }
1841+ Column {
1842+ Button {
1843+ text: "Add page2 next"
1844+ onClicked: page1.pageStack.addPageToNextColumn(page1, page2)
1845+ }
1846+ Button {
1847+ text: "Add page4 above"
1848+ onClicked: page1.pageStack.addPageToCurrentColumn(page1, page4)
1849+ }
1850+ }
1851+ }
1852+
1853+ Page {
1854+ id: page2
1855+ title: "Page #2"
1856+
1857+ Rectangle {
1858+ anchors.fill: parent
1859+ color: "green"
1860+ }
1861+ Column {
1862+ Button {
1863+ text: "Back..."
1864+ onClicked: page2.pageStack.removePages(page2)
1865+ }
1866+ Button {
1867+ text: "Add page3 next"
1868+ onClicked: page2.pageStack.addPageToNextColumn(page2, page3)
1869+ }
1870+ }
1871+ }
1872+ Page {
1873+ id: page3
1874+ title: "Page #3"
1875+
1876+ Rectangle {
1877+ anchors.fill: parent
1878+ color: "blue"
1879+ }
1880+ Button {
1881+ text: "Back..."
1882+ onClicked: page3.pageStack.removePages(page3)
1883+ }
1884+ }
1885+ Page {
1886+ id: page4
1887+ title: "Page #4"
1888+
1889+ Rectangle {
1890+ anchors.fill: parent
1891+ color: "teal"
1892+ }
1893+ Column {
1894+ Button {
1895+ text: "Back..."
1896+ onClicked: page4.pageStack.removePages(page4)
1897+ }
1898+ Button {
1899+ text: "Add page5 next"
1900+ onClicked: page4.pageStack.addPageToNextColumn(page4, page5)
1901+ }
1902+ }
1903+ }
1904+ Page {
1905+ id: page5
1906+ title: "Page #5"
1907+ Rectangle {
1908+ anchors.fill: parent
1909+ color: "tan"
1910+ }
1911+ Column {
1912+ Button {
1913+ text: "Back..."
1914+ onClicked: page5.pageStack.removePages(page5)
1915+ }
1916+ Button {
1917+ text: "Custom page on same column"
1918+ onClicked: page5.pageStack.addPageToCurrentColumn(page5, Qt.resolvedUrl("MyCustomPage.qml"))
1919+ }
1920+ }
1921+ }
1922+ }
1923+}
1924
1925=== added file 'tests/unit_x11/tst_components/ListItemWithLabel.qml'
1926--- tests/unit_x11/tst_components/ListItemWithLabel.qml 1970-01-01 00:00:00 +0000
1927+++ tests/unit_x11/tst_components/ListItemWithLabel.qml 2015-07-10 13:56:40 +0000
1928@@ -0,0 +1,30 @@
1929+/*
1930+ * Copyright 2015 Canonical Ltd.
1931+ *
1932+ * This program is free software; you can redistribute it and/or modify
1933+ * it under the terms of the GNU Lesser General Public License as published by
1934+ * the Free Software Foundation; version 3.
1935+ *
1936+ * This program is distributed in the hope that it will be useful,
1937+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1938+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1939+ * GNU Lesser General Public License for more details.
1940+ *
1941+ * You should have received a copy of the GNU Lesser General Public License
1942+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1943+ */
1944+
1945+import QtQuick 2.4
1946+import Ubuntu.Components 1.3
1947+
1948+ListItem {
1949+ property alias text: label.text
1950+ Label {
1951+ id: label
1952+ anchors {
1953+ left: parent.left
1954+ leftMargin: units.gu(2)
1955+ verticalCenter: parent.verticalCenter
1956+ }
1957+ }
1958+}
1959
1960=== modified file 'tests/unit_x11/tst_components/MyExternalPage.qml'
1961--- tests/unit_x11/tst_components/MyExternalPage.qml 2015-03-03 13:20:06 +0000
1962+++ tests/unit_x11/tst_components/MyExternalPage.qml 2015-07-10 13:56:40 +0000
1963@@ -1,5 +1,5 @@
1964 /*
1965- * Copyright 2012-2014 Canonical Ltd.
1966+ * Copyright 2012-2015 Canonical Ltd.
1967 *
1968 * This program is free software; you can redistribute it and/or modify
1969 * it under the terms of the GNU Lesser General Public License as published by
1970@@ -14,9 +14,13 @@
1971 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1972 */
1973
1974-import QtQuick 2.2
1975-import Ubuntu.Components 1.1
1976+import QtQuick 2.4
1977+import Ubuntu.Components 1.3
1978
1979 Page {
1980 title: "Page from QML file"
1981+ Label {
1982+ anchors.centerIn: parent
1983+ text: "This page was created from MyExternalPage.qml."
1984+ }
1985 }
1986
1987=== modified file 'tests/unit_x11/tst_components/tst_components.pro'
1988--- tests/unit_x11/tst_components/tst_components.pro 2015-04-25 07:10:57 +0000
1989+++ tests/unit_x11/tst_components/tst_components.pro 2015-07-10 13:56:40 +0000
1990@@ -7,5 +7,6 @@
1991 SOURCES += tst_components.cpp tabsmodel.cpp
1992 HEADERS += tabsmodel.h
1993
1994-OTHER_FILES += $$system(ls *.qml)
1995+OTHER_FILES += $$system(ls *.qml) \
1996+ tst_multicolumnview.qml
1997 OTHER_FILES += $$system(ls AppTheme/*)
1998
1999=== added file 'tests/unit_x11/tst_components/tst_multicolumnheader.qml'
2000--- tests/unit_x11/tst_components/tst_multicolumnheader.qml 1970-01-01 00:00:00 +0000
2001+++ tests/unit_x11/tst_components/tst_multicolumnheader.qml 2015-07-10 13:56:40 +0000
2002@@ -0,0 +1,200 @@
2003+/*
2004+ * Copyright 2015 Canonical Ltd.
2005+ *
2006+ * This program is free software; you can redistribute it and/or modify
2007+ * it under the terms of the GNU Lesser General Public License as published by
2008+ * the Free Software Foundation; version 3.
2009+ *
2010+ * This program is distributed in the hope that it will be useful,
2011+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2012+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2013+ * GNU Lesser General Public License for more details.
2014+ *
2015+ * You should have received a copy of the GNU Lesser General Public License
2016+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2017+ */
2018+
2019+import QtQuick 2.4
2020+import Ubuntu.Test 1.0
2021+import Ubuntu.Components 1.3
2022+
2023+MainView {
2024+ id: root
2025+ width: units.gu(120)
2026+ height: units.gu(71)
2027+
2028+ MultiColumnView {
2029+ id: multiColumnView
2030+ width: parent.width
2031+ height: parent.height
2032+ primaryPage: rootPage
2033+
2034+ Page {
2035+ id: rootPage
2036+ title: "Root"
2037+
2038+ Column {
2039+ anchors {
2040+ top: parent.top
2041+ left: parent.left
2042+ right: parent.right
2043+ }
2044+ height: childrenRect.height
2045+
2046+ ListItemWithLabel {
2047+ text: "Add page left"
2048+ onClicked: multiColumnView.addPageToCurrentColumn(rootPage, leftPage)
2049+ }
2050+ ListItemWithLabel {
2051+ text: "Add page right"
2052+ onClicked: multiColumnView.addPageToNextColumn(rootPage, rightPage)
2053+ }
2054+ ListItemWithLabel {
2055+ text: "Add sections page right"
2056+ onClicked: multiColumnView.addPageToNextColumn(rootPage, sectionsPage)
2057+ }
2058+ ListItemWithLabel {
2059+ text: "Add external page right"
2060+ onClicked: multiColumnView.addPageToNextColumn(
2061+ rootPage, Qt.resolvedUrl("MyExternalPage.qml"))
2062+ }
2063+ }
2064+ }
2065+ Page {
2066+ id: leftPage
2067+ title: "First column"
2068+ Rectangle {
2069+ anchors {
2070+ fill: parent
2071+ margins: units.gu(2)
2072+ }
2073+ color: "orange"
2074+ }
2075+ }
2076+ Page {
2077+ id: rightPage
2078+ title: "Second column"
2079+ Rectangle {
2080+ anchors {
2081+ fill: parent
2082+ margins: units.gu(2)
2083+ }
2084+ color: "green"
2085+ }
2086+ }
2087+ Page {
2088+ id: sectionsPage
2089+ title: "Page with sections"
2090+ head.sections.model: ["uno", "dos", "tres"]
2091+
2092+ Rectangle {
2093+ anchors {
2094+ fill: parent
2095+ margins: units.gu(2)
2096+ }
2097+ color: "blue"
2098+ }
2099+ }
2100+ }
2101+ UbuntuTestCase {
2102+ when: windowShown
2103+
2104+ function get_number_of_columns() {
2105+ var body = findChild(multiColumnView, "body");
2106+ return body.children.length;
2107+ }
2108+
2109+ function get_header(index) {
2110+ return findChild(multiColumnView, "Header" + index);
2111+ }
2112+
2113+ function get_number_of_headers() {
2114+ // FIXME: With only one column, revert to using the AppHeader
2115+ // so multiColumnView sill not include any headers.
2116+ var numHeaders = 0;
2117+ var header = findChild(multiColumnView, "Header0");
2118+ verify(header !== null, "No header found!");
2119+ while (header !== null) {
2120+ numHeaders++;
2121+ header = get_header(numHeaders);
2122+ }
2123+ return numHeaders;
2124+ }
2125+
2126+ function cleanup() {
2127+ multiColumnView.width = root.width;
2128+ multiColumnView.height = root.height;
2129+ multiColumnView.removePages(rootPage);
2130+ }
2131+
2132+ function test_number_of_headers_equals_number_of_columns() {
2133+ multiColumnView.width = units.gu(40);
2134+ compare(get_number_of_columns(), 1, "Number of columns is not 1.");
2135+ compare(get_number_of_headers(), 1, "Number of headers is not 1.");
2136+ multiColumnView.width = root.width;
2137+ compare(get_number_of_columns(), 2, "Number of columns is not 2.");
2138+ compare(get_number_of_headers(), 2, "Number of headers is not 2.");
2139+ }
2140+
2141+ function test_header_configuration_equals_column_page_configuration() {
2142+ compare(get_number_of_headers(), 2, "Number of headers is not 2 initially.");
2143+ compare(get_header(0).config, rootPage.head,
2144+ "First column header is not initialized with primaryPage header config.");
2145+ compare(get_header(1).config, null,
2146+ "Second column header is not initalized with null.");
2147+
2148+ multiColumnView.addPageToCurrentColumn(rootPage, leftPage);
2149+ compare(get_header(0).config, leftPage.head,
2150+ "First column header is not updated properly.");
2151+ compare(get_header(1).config, null,
2152+ "Second column header is updated when it should not be.");
2153+ multiColumnView.removePages(leftPage);
2154+ compare(get_header(0).config, rootPage.head,
2155+ "First column header is not reverted properly.");
2156+
2157+ multiColumnView.addPageToNextColumn(rootPage, rightPage);
2158+ compare(get_header(0).config, rootPage.head,
2159+ "First column header is updated when it should not be.");
2160+ compare(get_header(1).config, rightPage.head,
2161+ "Second column header is not updated properly.");
2162+ multiColumnView.removePages(rightPage);
2163+ compare(get_header(1).config, null,
2164+ "Second column header is not reverted properly.");
2165+ }
2166+
2167+ function test_header_title_for_external_page() {
2168+ multiColumnView.addPageToNextColumn(rootPage, Qt.resolvedUrl("MyExternalPage.qml"));
2169+ compare(get_header(1).config.title, "Page from QML file",
2170+ "Adding external Page does not update the header title.");
2171+ }
2172+
2173+ function test_header_height() {
2174+ // contentHeight + divider height
2175+ var baseHeight = units.gu(6) + units.dp(1);
2176+ var withSectionsHeight = baseHeight + units.gu(4);
2177+ var n = get_number_of_headers();
2178+ var i;
2179+ for (i = 0; i < n; i++) {
2180+ compare(get_header(i).height, baseHeight,
2181+ "Header " + i + " height is not initialized correctly.");
2182+ }
2183+ multiColumnView.addPageToNextColumn(rootPage, rightPage);
2184+ for (i = 0; i < n; i++) {
2185+ compare(get_header(i).height, baseHeight,
2186+ "Header " + i + " height is incorrect after adding Page.");
2187+ }
2188+ multiColumnView.removePages(rightPage);
2189+ multiColumnView.addPageToNextColumn(rootPage, sectionsPage);
2190+ for (i = 0; i < n; i++) {
2191+ compare(get_header(i).height, withSectionsHeight,
2192+ "Header " + i + " height is incorrect after adding single Page with sections.");
2193+ }
2194+ multiColumnView.removePages(sectionsPage);
2195+ for (i = 0; i < n; i++) {
2196+ compare(get_header(i).height, baseHeight,
2197+ "Header " + i +
2198+ " height is not correctly reverted after removing Page with sections.");
2199+ }
2200+ }
2201+ }
2202+}
2203
2204=== added file 'tests/unit_x11/tst_components/tst_multicolumnview.qml'
2205--- tests/unit_x11/tst_components/tst_multicolumnview.qml 1970-01-01 00:00:00 +0000
2206+++ tests/unit_x11/tst_components/tst_multicolumnview.qml 2015-07-10 13:56:40 +0000
2207@@ -0,0 +1,128 @@
2208+/*
2209+ * Copyright 2015 Canonical Ltd.
2210+ *
2211+ * This program is free software; you can redistribute it and/or modify
2212+ * it under the terms of the GNU Lesser General Public License as published by
2213+ * the Free Software Foundation; version 3.
2214+ *
2215+ * This program is distributed in the hope that it will be useful,
2216+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2217+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2218+ * GNU Lesser General Public License for more details.
2219+ *
2220+ * You should have received a copy of the GNU Lesser General Public License
2221+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2222+ */
2223+
2224+import QtQuick 2.4
2225+import QtTest 1.0
2226+import Ubuntu.Test 1.0
2227+import Ubuntu.Components 1.3
2228+
2229+Item {
2230+ id: test
2231+ width: units.gu(120)
2232+ height: units.gu(71)
2233+
2234+ MultiColumnView {
2235+ id: testView
2236+ width: parent.width
2237+ height: parent.height
2238+
2239+ primaryPage: page1
2240+
2241+ Page {
2242+ id: page1
2243+ title: "Page1"
2244+ }
2245+ Page {
2246+ id: page2
2247+ title: "Page2"
2248+ }
2249+ Page {
2250+ id: page3
2251+ title: "Page3"
2252+ }
2253+ Page {
2254+ id: page4
2255+ title: "Page4"
2256+ }
2257+ }
2258+
2259+ MultiColumnView {
2260+ id: defaults
2261+ }
2262+
2263+ UbuntuTestCase {
2264+ when: windowShown
2265+
2266+ function cleanup() {
2267+// testView.columns = Qt.binding(function() {
2268+// return test.width > units.gu(100) ? 3 : (test.width > units.gu(80) ? 2 : 1);
2269+// });
2270+ testView.width = test.width;
2271+ testView.height = test.height;
2272+ // remove allpages
2273+ testView.removePages(page1);
2274+ }
2275+
2276+ function test_0_API() {
2277+ compare(defaults.primaryPage, undefined, "primaryPage not undefined by default");
2278+ }
2279+
2280+ function test_add_to_first_column_data() {
2281+ return [
2282+ {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
2283+ {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
2284+ ]
2285+ }
2286+ function test_add_to_first_column(data) {
2287+ if (data.failMsg != "") {
2288+ ignoreWarning(data.failMsg);
2289+ }
2290+
2291+ testView.addPageToCurrentColumn(data.sourcePage, data.page);
2292+ var firstColumn = findChild(testView, "ColumnHolder0");
2293+ verify(firstColumn);
2294+ if (data.failMsg != "") {
2295+ expectFail(data.tag, "Fail");
2296+ }
2297+ compare(firstColumn.pageWrapper.object, data.page);
2298+ }
2299+
2300+ function test_add_to_next_column_data() {
2301+ return [
2302+ {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."},
2303+ {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""},
2304+ ]
2305+ }
2306+ function test_add_to_next_column(data) {
2307+ if (data.failMsg != "") {
2308+ ignoreWarning(data.failMsg);
2309+ }
2310+
2311+ testView.addPageToNextColumn(data.sourcePage, data.page);
2312+ var secondColumn = findChild(testView, "ColumnHolder1");
2313+ verify(secondColumn);
2314+ if (data.failMsg != "") {
2315+ expectFail(data.tag, "Fail");
2316+ }
2317+ verify(secondColumn.pageWrapper);
2318+ }
2319+
2320+ function test_change_primaryPage() {
2321+ ignoreWarning("Cannot change primaryPage after completion.");
2322+ testView.primaryPage = page3;
2323+ }
2324+
2325+ function test_add_to_same_column_when_source_page_not_in_stack() {
2326+ ignoreWarning("sourcePage must be added to the view to add new page.");
2327+ testView.addPageToCurrentColumn(page2, page3);
2328+ }
2329+
2330+ function test_add_to_next_column_when_source_page_not_in_stack() {
2331+ ignoreWarning("sourcePage must be added to the view to add new page.");
2332+ testView.addPageToNextColumn(page2, page3);
2333+ }
2334+ }
2335+}

Subscribers

People subscribed via source and target branches