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

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

Description of the change

Changes needed for UI Convergency prototype

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

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

review: Disapprove

Unmerged revisions

1566. By Andrea Bernabei

merge multiColumnView branch

1565. By Andrea Bernabei

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

1564. By Andrea Bernabei

merge

1563. By Andrea Bernabei

change default color of IconButtonStyle

1562. By Andrea Bernabei

change backgroundText in theme Palette

1561. By Andrea Bernabei

add white background to header

1560. By Andrea Bernabei

update backgroundText palette value

1559. By Andrea Bernabei

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

1558. By Andrea Bernabei

merge tims branch

1557. By Andrea Bernabei

Implement dynamic number of slots in PageHeadStyle

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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