on xenial help sign in
Merge from trunk
commit39d1fe1b3be4ef54853b4172fb76929e0fc3f41a authorMichael Terry <michael.terry@canonical.com> dateTue 13 May 2014, 14:41 (12 years ago) parentdbf6315f4d8085
76 files changed+1694−750
debian/changelog+25 debian/unity8-private.install+1−1 plugins/CMakeLists.txt+1−1 plugins/DashViews/CMakeLists.txt → plugins/Dash/CMakeLists.txt+5−5 plugins/Dash/CardCreator.js+518 plugins/Dash/CardCreatorCache.qml+40 plugins/DashViews/abstractdashview.cpp → plugins/Dash/abstractdashview.cpp plugins/DashViews/abstractdashview.h → plugins/Dash/abstractdashview.h plugins/DashViews/horizontaljournal.cpp → plugins/Dash/horizontaljournal.cpp plugins/DashViews/horizontaljournal.h → plugins/Dash/horizontaljournal.h plugins/DashViews/listviewwithpageheader.cpp → plugins/Dash/listviewwithpageheader.cpp plugins/DashViews/listviewwithpageheader.h → plugins/Dash/listviewwithpageheader.h plugins/DashViews/organicgrid.cpp → plugins/Dash/organicgrid.cpp plugins/DashViews/organicgrid.h → plugins/Dash/organicgrid.h plugins/DashViews/plugin.cpp → plugins/Dash/plugin.cpp+2−2 plugins/DashViews/plugin.h → plugins/Dash/plugin.h+3−3 plugins/Dash/qmldir+4 plugins/DashViews/verticaljournal.cpp → plugins/Dash/verticaljournal.cpp plugins/DashViews/verticaljournal.h → plugins/Dash/verticaljournal.h plugins/DashViews/qmldir−3 plugins/Unity/Indicators/menucontentactivator.cpp−1 po/fr.po+24−15 po/lv.po+1−1 qml/Dash/Card.qml−236 qml/Dash/CardCarousel.qml+13−10 qml/Dash/CardFilterGrid.qml+19−16 qml/Dash/CardHeader.qml−126 qml/Dash/CardTool.qml+30−22 qml/Dash/Previews/PreviewHeader.qml+86−9 qml/Dash/Previews/PreviewTextSummary.qml+4−2 qml/Dash/ScopeListView.qml+1−1 src/ApplicationArguments.h+4−8 src/main.cpp+43−6 tests/autopilot/unity8/shell/emulators/dash.py+3−4 tests/mocks/Unity/fake_categories.cpp+15−2 tests/mocks/Unity/fake_resultsmodel.cpp+1 tests/plugins/CMakeLists.txt+1−1 tests/plugins/DashViews/CMakeLists.txt → tests/plugins/Dash/CMakeLists.txt+16−9 tests/plugins/Dash/cardcreator/1.res+77 tests/plugins/Dash/cardcreator/1.tst+3 tests/plugins/Dash/cardcreator/2.res+115 tests/plugins/Dash/cardcreator/2.tst+3 tests/plugins/Dash/cardcreator/3.res+94 tests/plugins/Dash/cardcreator/3.tst+3 tests/plugins/Dash/cardcreator/4.res+95 tests/plugins/Dash/cardcreator/4.tst+3 tests/plugins/Dash/cardcreator/5.res+137 tests/plugins/Dash/cardcreator/5.tst+3 tests/plugins/Dash/cardcreatortest.cpp+91 tests/plugins/Dash/cardcreatortest.qml+29 tests/plugins/DashViews/horizontaljournaltest.cpp → tests/plugins/Dash/horizontaljournaltest.cpp tests/plugins/DashViews/horizontaljournaltest.qml → tests/plugins/Dash/horizontaljournaltest.qml+1−1 tests/plugins/DashViews/horizontaljournaltry.cpp → tests/plugins/Dash/horizontaljournaltry.cpp tests/plugins/DashViews/horizontaljournaltry.qml → tests/plugins/Dash/horizontaljournaltry.qml+1−1 tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp → tests/plugins/Dash/listviewwithpageheadersectionexternalmodeltest.cpp tests/plugins/DashViews/listviewwithpageheadersectiontest.cpp → tests/plugins/Dash/listviewwithpageheadersectiontest.cpp tests/plugins/DashViews/listviewwithpageheadertest.cpp → tests/plugins/Dash/listviewwithpageheadertest.cpp tests/plugins/DashViews/listviewwithpageheadertest.qml → tests/plugins/Dash/listviewwithpageheadertest.qml+1−1 tests/plugins/DashViews/listviewwithpageheadertestsection.qml → tests/plugins/Dash/listviewwithpageheadertestsection.qml+1−1 tests/plugins/DashViews/listviewwithpageheadertestsectionexternalmodel.qml → tests/plugins/Dash/listviewwithpageheadertestsectionexternalmodel.qml+1−1 tests/plugins/DashViews/organicgridtest.cpp → tests/plugins/Dash/organicgridtest.cpp tests/plugins/DashViews/organicgridtest.qml → tests/plugins/Dash/organicgridtest.qml+1−1 tests/plugins/DashViews/organicgridtry.cpp → tests/plugins/Dash/organicgridtry.cpp tests/plugins/DashViews/organicgridtry.qml → tests/plugins/Dash/organicgridtry.qml+1−1 tests/plugins/DashViews/tst_ListViewWithPageHeaderQML.qml → tests/plugins/Dash/tst_ListViewWithPageHeaderQML.qml+1−1 tests/plugins/DashViews/verticaljournaltest.cpp → tests/plugins/Dash/verticaljournaltest.cpp tests/plugins/DashViews/verticaljournaltest.qml → tests/plugins/Dash/verticaljournaltest.qml+1−1 tests/plugins/DashViews/verticaljournaltry.cpp → tests/plugins/Dash/verticaljournaltry.cpp tests/plugins/DashViews/verticaljournaltry.qml → tests/plugins/Dash/verticaljournaltry.qml+1−1 tests/qmltests/Dash/Previews/tst_Preview.qml+1−1 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+66−4 tests/qmltests/Dash/Previews/tst_PreviewTextSummary.qml+5−5 tests/qmltests/Dash/tst_Card.qml+78−92 tests/qmltests/Dash/tst_CardBenchmark.qml+10−7 tests/qmltests/Dash/tst_CardHeader.qml−116 tests/qmltests/Dash/tst_CardTool.qml+11−31
debian/changelog+25
@@ -4,6 +4,31 @@ unity8 (7.87+14.10.20140505-0ubuntu1) UNRELEASED; urgency=medium
44
55 -- Michael Terry <mterry@ubuntu.com> Wed, 02 Apr 2014 14:39:45 -0400
66
7+unity8 (7.86+14.10.20140513-0ubuntu1) utopic; urgency=low
8+
9+ [ Andrea Cimitan ]
10+ * Improve padding in Text preview widget. (LP: #1316683)
11+
12+ [ CI bot ]
13+ * Resync trunk
14+
15+ [ Nick Dedekind ]
16+ * Removed binding loop from Unity.Indicators.MenuContentActivator
17+ Change handler for QMLListProperty used by MenuContent.qml:
18+ menuActivator.content[index].active If we're already asking for the
19+ index, we know it exists already. No need to send a
20+ changeNotification on an implied creation.
21+
22+ [ Josh Arenson ]
23+ * Implements usage-style documentation for unity8 executable. Fixes
24+ lp:1269282 (LP: #1269282)
25+
26+ [ Albert Astals ]
27+ * Create specialized Card code in Javascript instead of having various
28+ copied&pasted files (LP: #1297197)
29+
30+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 13 May 2014 08:34:02 +0000
31+
732unity8 (7.86+14.10.20140507.3-0ubuntu1) utopic; urgency=low
833
934 [ Michał Sawicz ]
debian/unity8-private.install+1−1
@@ -1,5 +1,5 @@
11usr/lib/*/unity8/qml/AccountsService
2−usr/lib/*/unity8/qml/DashViews
2+usr/lib/*/unity8/qml/Dash
33usr/lib/*/unity8/qml/HudClient
44usr/lib/*/unity8/qml/LightDM
55usr/lib/*/unity8/qml/Powerd
plugins/CMakeLists.txt+1−1
@@ -1,7 +1,7 @@
11add_subdirectory(AccountsService)
22add_subdirectory(HudClient)
33add_subdirectory(LightDM)
4−add_subdirectory(DashViews)
4+add_subdirectory(Dash)
55add_subdirectory(Powerd)
66add_subdirectory(SessionBroadcast)
77add_subdirectory(SessionManager)
plugins/DashViews/CMakeLists.txt → plugins/Dash/CMakeLists.txtrenamed+5−5
@@ -30,17 +30,17 @@ set(QMLPLUGIN_SRC
3030 organicgrid.cpp
3131 )
3232
33−add_library(DashViews-qml MODULE
33+add_library(Dash-qml MODULE
3434 ${QMLPLUGIN_SRC}
3535 )
3636
37−target_link_libraries(DashViews-qml
37+target_link_libraries(Dash-qml
3838 ${Qt5Gui_LIBRARIES}
3939 ${Qt5Quick_LIBRARIES}
4040 )
4141
42−qt5_use_modules(DashViews-qml Qml Quick)
42+qt5_use_modules(Dash-qml Qml Quick)
4343
4444# export the qmldir qmltypes and plugin files
45−export_qmlfiles(DashViews DashViews)
46−export_qmlplugin(DashViews 0.1 DashViews TARGETS DashViews-qml)
45+export_qmlfiles(Dash Dash)
46+export_qmlplugin(Dash 0.1 Dash TARGETS Dash-qml)
plugins/Dash/CardCreator.jsnew file+518
@@ -0,0 +1,518 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+.pragma library
18+
19+var kBackgroundLoaderCode = 'Loader {\n\
20+ id: backgroundLoader; \n\
21+ objectName: "backgroundLoader"; \n\
22+ anchors.fill: parent; \n\
23+ asynchronous: root.asynchronous; \n\
24+ visible: status == Loader.Ready; \n\
25+ sourceComponent: UbuntuShape { \n\
26+ objectName: "background"; \n\
27+ radius: "medium"; \n\
28+ color: getColor(0) || "white"; \n\
29+ gradientColor: getColor(1) || color; \n\
30+ anchors.fill: parent; \n\
31+ image: backgroundImage.source ? backgroundImage : null; \n\
32+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; \n\
33+ property Image backgroundImage: Image { \n\
34+ objectName: "backgroundImage"; \n\
35+ source: { \n\
36+ if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
37+ else if (template && typeof template["card-background"] === "string") return template["card-background"]; \n\
38+ else return ""; \n\
39+ } \n\
40+ } \n\
41+ function getColor(index) { \n\
42+ if (cardData && typeof cardData["background"] === "object" \n\
43+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
44+ return cardData["background"]["elements"][index]; \n\
45+ } else if (template && typeof template["card-background"] === "object" \n\
46+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) { \n\
47+ return template["card-background"]["elements"][index]; \n\
48+ } else return undefined; \n\
49+ } \n\
50+ } \n\
51+ }\n';
52+
53+// %1 is used as anchors of artShapeHolder
54+// %2 is used as image width
55+// %3 is used as image height
56+var kArtShapeHolderCode = 'Item { \n\
57+ id: artShapeHolder; \n\
58+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
59+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width; \n\
60+ anchors { %1 } \n\
61+ Loader { \n\
62+ id: artShapeLoader; \n\
63+ objectName: "artShapeLoader"; \n\
64+ active: cardData && cardData["art"] || false; \n\
65+ asynchronous: root.asynchronous; \n\
66+ visible: status == Loader.Ready; \n\
67+ sourceComponent: UbuntuShape { \n\
68+ id: artShape; \n\
69+ objectName: "artShape"; \n\
70+ radius: "medium"; \n\
71+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1; \n\
72+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect; \n\
73+ Component.onCompleted: updateWidthHeightBindings(); \n\
74+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings(); \n\
75+ visible: image.status == Image.Ready; \n\
76+ function updateWidthHeightBindings() { \n\
77+ if (aspectSmallerThanImageAspect) { \n\
78+ width = Qt.binding(function() { return !visible ? 0 : image.width }); \n\
79+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect }); \n\
80+ } else { \n\
81+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect }); \n\
82+ height = Qt.binding(function() { return !visible ? 0 : image.height }); \n\
83+ } \n\
84+ } \n\
85+ image: Image { \n\
86+ objectName: "artImage"; \n\
87+ source: cardData && cardData["art"] || ""; \n\
88+ cache: true; \n\
89+ asynchronous: root.asynchronous; \n\
90+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop; \n\
91+ readonly property real aspect: implicitWidth / implicitHeight; \n\
92+ width: %2; \n\
93+ height: %3; \n\
94+ } \n\
95+ } \n\
96+ } \n\
97+ }\n';
98+
99+var kOverlayLoaderCode = 'Loader { \n\
100+ id: overlayLoader; \n\
101+ anchors { \n\
102+ left: artShapeHolder.left; \n\
103+ right: artShapeHolder.right; \n\
104+ bottom: artShapeHolder.bottom; \n\
105+ } \n\
106+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
107+ asynchronous: root.asynchronous; \n\
108+ visible: showHeader && status == Loader.Ready; \n\
109+ sourceComponent: ShaderEffect { \n\
110+ id: overlay; \n\
111+ height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight; \n\
112+ opacity: 0.6; \n\
113+ property var source: ShaderEffectSource { \n\
114+ id: shaderSource; \n\
115+ sourceItem: artShapeLoader.item; \n\
116+ onVisibleChanged: if (visible) scheduleUpdate(); \n\
117+ live: false; \n\
118+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height); \n\
119+ } \n\
120+ vertexShader: " \n\
121+ uniform highp mat4 qt_Matrix; \n\
122+ attribute highp vec4 qt_Vertex; \n\
123+ attribute highp vec2 qt_MultiTexCoord0; \n\
124+ varying highp vec2 coord; \n\
125+ void main() { \n\
126+ coord = qt_MultiTexCoord0; \n\
127+ gl_Position = qt_Matrix * qt_Vertex; \n\
128+ }"; \n\
129+ fragmentShader: " \n\
130+ varying highp vec2 coord; \n\
131+ uniform sampler2D source; \n\
132+ uniform lowp float qt_Opacity; \n\
133+ void main() { \n\
134+ lowp vec4 tex = texture2D(source, coord); \n\
135+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity; \n\
136+ }"; \n\
137+ } \n\
138+ }\n';
139+
140+// %1 is used as anchors of row
141+// %2 is used as first child of the row
142+// %3 is used as second child of the row
143+var kHeaderRow2Code = 'Row { \n\
144+ id: row; \n\
145+ objectName: "outerRow"; \n\
146+ property real margins: units.gu(1); \n\
147+ spacing: margins; \n\
148+ anchors { %1 } \n\
149+ anchors.right: parent.right; \n\
150+ anchors.margins: margins;\n\
151+ data: [ %2\n\
152+ ,\n\
153+ %3 \n\
154+ ] \n\
155+ }\n';
156+
157+// %1 is used as anchors of row
158+// %2 is used as first child of the row
159+// %3 is used as second child of the row
160+// %4 is used as third child of the row
161+var kHeaderRow3Code = 'Row { \n\
162+ id: row; \n\
163+ objectName: "outerRow"; \n\
164+ property real margins: units.gu(1); \n\
165+ spacing: margins; \n\
166+ anchors { %1 } \n\
167+ anchors.right: parent.right; \n\
168+ anchors.margins: margins;\n\
169+ data: [ %2\n\
170+ ,\n\
171+ %3 \n\
172+ ,\n\
173+ %4 \n\
174+ ] \n\
175+ }\n';
176+
177+// %1 is used as first child of the column
178+// %2 is used as second child of the column
179+var kHeaderColumnCode = 'Column { \n\
180+ anchors.verticalCenter: parent.verticalCenter; \n\
181+ spacing: units.dp(2); \n\
182+ width: parent.width - x;\n\
183+ data: [ %1\n\
184+ ,\n\
185+ %2 \n\
186+ ] \n\
187+ }\n';
188+
189+// %1 is used as anchors of mascotShapeLoader
190+var kMascotShapeLoaderCode = 'Loader { \n\
191+ id: mascotShapeLoader; \n\
192+ objectName: "mascotShapeLoader"; \n\
193+ asynchronous: root.asynchronous; \n\
194+ active: mascotImage.status === Image.Ready; \n\
195+ visible: showHeader && active && status == Loader.Ready; \n\
196+ width: units.gu(6); \n\
197+ height: units.gu(5.625); \n\
198+ sourceComponent: UbuntuShape { image: mascotImage } \n\
199+ anchors { %1 } \n\
200+ }\n';
201+
202+// %1 is used as anchors of mascotImage
203+// %2 is used as visible of mascotImage
204+var kMascotImageCode = 'Image { \n\
205+ id: mascotImage; \n\
206+ objectName: "mascotImage"; \n\
207+ anchors { %1 } \n\
208+ readonly property int maxSize: Math.max(width, height) * 4; \n\
209+ source: cardData && cardData["mascot"]; \n\
210+ width: units.gu(6); \n\
211+ height: units.gu(5.625); \n\
212+ sourceSize { width: maxSize; height: maxSize } \n\
213+ fillMode: Image.PreserveAspectCrop; \n\
214+ horizontalAlignment: Image.AlignHCenter; \n\
215+ verticalAlignment: Image.AlignVCenter; \n\
216+ visible: %2; \n\
217+ }\n';
218+
219+// %1 is used as anchors of titleLabel
220+// %1 is used as color of titleLabel
221+// %3 is used as extra condition for visible of titleLabel
222+var kTitleLabelCode = 'Label { \n\
223+ id: titleLabel; \n\
224+ objectName: "titleLabel"; \n\
225+ anchors { %1 } \n\
226+ elide: Text.ElideRight; \n\
227+ fontSize: "small"; \n\
228+ wrapMode: Text.Wrap; \n\
229+ maximumLineCount: 2; \n\
230+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
231+ color: %2; \n\
232+ visible: showHeader %3; \n\
233+ text: root.title; \n\
234+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal; \n\
235+ horizontalAlignment: root.headerAlignment; \n\
236+ }\n';
237+
238+// %1 is used as anchors of subtitleLabel
239+// %2 is used as color of subtitleLabel
240+var kSubtitleLabelCode = 'Label { \n\
241+ id: subtitleLabel; \n\
242+ objectName: "subtitleLabel"; \n\
243+ anchors { %1 } \n\
244+ elide: Text.ElideRight; \n\
245+ fontSize: "small"; \n\
246+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
247+ color: %2; \n\
248+ visible: titleLabel.visible && titleLabel.text; \n\
249+ text: cardData && cardData["subtitle"] || ""; \n\
250+ font.weight: Font.Light; \n\
251+ horizontalAlignment: root.headerAlignment; \n\
252+ }\n';
253+
254+// %1 is used as top anchor of summary
255+// %2 is used as topMargin anchor of summary
256+// %3 is used as color of summary
257+var kSummaryLabelCode = 'Label { \n\
258+ id: summary; \n\
259+ objectName: "summaryLabel"; \n\
260+ anchors { \n\
261+ top: %1; \n\
262+ left: parent.left; \n\
263+ right: parent.right; \n\
264+ margins: units.gu(1); \n\
265+ topMargin: %2; \n\
266+ } \n\
267+ wrapMode: Text.Wrap; \n\
268+ maximumLineCount: 5; \n\
269+ elide: Text.ElideRight; \n\
270+ text: cardData && cardData["summary"] || ""; \n\
271+ height: text ? implicitHeight : 0; \n\
272+ fontSize: "small"; \n\
273+ color: %3; \n\
274+ }\n';
275+
276+function cardString(template, components) {
277+ var code;
278+ code = 'AbstractButton { \n\
279+ id: root; \n\
280+ property var template; \n\
281+ property var components; \n\
282+ property var cardData; \n\
283+ property real fontScale: 1.0; \n\
284+ property int headerAlignment: Text.AlignLeft; \n\
285+ property int fixedHeaderHeight: -1; \n\
286+ property size fixedArtShapeSize: Qt.size(-1, -1); \n\
287+ readonly property string title: cardData && cardData["title"] || ""; \n\
288+ property bool asynchronous: true; \n\
289+ property bool showHeader: true; \n\
290+ implicitWidth: childrenRect.width; \n';
291+
292+ var hasArt = components["art"] && components["art"]["field"] || false;
293+ var hasSummary = components["summary"] || false;
294+ var artAndSummary = hasArt && hasSummary;
295+ var isHorizontal = template["card-layout"] === "horizontal";
296+ var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);
297+ var hasTitle = components["title"] || false;
298+ var hasMascot = components["mascot"] || false;
299+ var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
300+ var hasSubtitle = components["subtitle"] || false;
301+ var hasHeaderRow = hasMascot && hasTitle;
302+
303+ if (hasBackground) {
304+ code += kBackgroundLoaderCode;
305+ }
306+
307+ if (hasArt) {
308+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);\n';
309+
310+ var widthCode, heightCode;
311+ var anchors;
312+ if (isHorizontal) {
313+ anchors = 'left: parent.left';
314+ if (hasMascot || hasTitle) {
315+ widthCode = 'height * artShape.aspect'
316+ heightCode = 'headerHeight';
317+ } else {
318+ // This side of the else is a bit silly, who wants an horizontal layout without mascot and title?
319+ // So we define a "random" height of the image height + 2 gu for the margins
320+ widthCode = 'height * artShape.aspect'
321+ heightCode = 'units.gu(7.625)';
322+ }
323+ } else {
324+ anchors = 'horizontalCenter: parent.horizontalCenter;';
325+ widthCode = 'root.width'
326+ heightCode = 'width / artShape.aspect';
327+ }
328+
329+ code += kArtShapeHolderCode.arg(anchors).arg(widthCode).arg(heightCode);
330+ } else {
331+ code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
332+ }
333+
334+ if (headerAsOverlay) {
335+ code += kOverlayLoaderCode;
336+ }
337+
338+ var headerVerticalAnchors;
339+ if (headerAsOverlay) {
340+ headerVerticalAnchors = 'bottom: artShapeHolder.bottom; \n\
341+ bottomMargin: units.gu(1);\n';
342+ } else {
343+ if (hasArt) {
344+ if (isHorizontal) {
345+ headerVerticalAnchors = 'top: artShapeHolder.top; \n\
346+ topMargin: units.gu(1);\n';
347+ } else {
348+ headerVerticalAnchors = 'top: artShapeHolder.bottom; \n\
349+ topMargin: units.gu(1);\n';
350+ }
351+ } else {
352+ headerVerticalAnchors = 'top: parent.top; \n\
353+ topMargin: units.gu(1);\n';
354+ }
355+ }
356+ var headerLeftAnchor;
357+ var headerLeftAnchorHasMagin = false;
358+ if (isHorizontal && hasArt) {
359+ headerLeftAnchor = 'left: artShapeHolder.right; \n\
360+ leftMargin: units.gu(1);\n';
361+ headerLeftAnchorHasMagin = true;
362+ } else {
363+ headerLeftAnchor = 'left: parent.left;\n';
364+ }
365+
366+ if (hasHeaderRow) {
367+ code += 'readonly property int headerHeight: row.height + row.margins * 2;\n'
368+ } else if (hasMascot) {
369+ code += 'readonly property int headerHeight: mascotImage.height + units.gu(1) * 2;\n'
370+ } else if (hasSubtitle) {
371+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
372+ } else if (hasTitle) {
373+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;\n'
374+ } else {
375+ code += 'readonly property int headerHeight: 0;\n'
376+ }
377+
378+ var mascotShapeCode = "";
379+ var mascotCode = "";
380+ if (hasMascot) {
381+ var useMascotShape = !hasBackground && !headerAsOverlay;
382+ var anchors = "";
383+ if (!hasHeaderRow) {
384+ anchors += headerLeftAnchor;
385+ anchors += headerVerticalAnchors;
386+ if (!headerLeftAnchorHasMagin) {
387+ anchors += 'leftMargin: units.gu(1);\n'
388+ }
389+ } else {
390+ anchors = "verticalCenter: parent.verticalCenter;"
391+ }
392+
393+ if (useMascotShape) {
394+ mascotShapeCode = kMascotShapeLoaderCode.arg(anchors);
395+ }
396+
397+ var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
398+ mascotCode = kMascotImageCode.arg(anchors).arg(mascotImageVisible);
399+ }
400+
401+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"';
402+
403+ var titleSubtitleCode = "";
404+ if (hasTitle) {
405+ var color;
406+ if (headerAsOverlay) {
407+ color = '"white"';
408+ } else if (hasSummary) {
409+ color = 'summary.color';
410+ } else if (hasBackground) {
411+ color = summaryColorWithBackground;
412+ } else {
413+ color = '"grey"';
414+ }
415+
416+ var titleAnchors;
417+ var subtitleAnchors;
418+ if (hasMascot && hasSubtitle) {
419+ // Using row + column
420+ titleAnchors = 'left: parent.left; right: parent.right';
421+ subtitleAnchors = titleAnchors;
422+ } else if (hasMascot) {
423+ // Using row + label
424+ titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
425+ } else {
426+ if (headerAsOverlay) {
427+ // Using anchors to the overlay
428+ titleAnchors = 'left: parent.left; \n\
429+ leftMargin: units.gu(1); \n\
430+ right: parent.right; \n\
431+ top: overlayLoader.top; \n\
432+ topMargin: units.gu(1);\n';
433+ } else {
434+ // Using anchors to the mascot/parent
435+ titleAnchors = "right: parent.right;";
436+ titleAnchors += headerLeftAnchor;
437+ titleAnchors += headerVerticalAnchors;
438+ }
439+ subtitleAnchors = 'left: titleLabel.left; \n\
440+ leftMargin: titleLabel.leftMargin; \n\
441+ right: titleLabel.right; \n\
442+ top: titleLabel.bottom; \n\
443+ topMargin: units.dp(2);\n';
444+ }
445+
446+ var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
447+ var titleCode = kTitleLabelCode.arg(titleAnchors).arg(color).arg(titleLabelVisibleExtra);
448+ var subtitleCode = "";
449+ if (hasSubtitle) {
450+ subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
451+ }
452+
453+ if (hasMascot && hasSubtitle) {
454+ // If using row + column wrap the code in the column
455+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
456+ } else {
457+ titleSubtitleCode = titleCode + subtitleCode;
458+ }
459+ }
460+
461+ if (hasHeaderRow) {
462+ if (mascotShapeCode != "") {
463+ code += kHeaderRow3Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotShapeCode).arg(mascotCode).arg(titleSubtitleCode);
464+ } else {
465+ code += kHeaderRow2Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotCode).arg(titleSubtitleCode);
466+ }
467+ } else {
468+ code += mascotShapeCode + mascotCode + titleSubtitleCode;
469+ }
470+
471+ if (hasSummary) {
472+ var summaryTopAnchor;
473+ if (isHorizontal && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
474+ else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
475+ else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
476+ else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
477+ else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
478+ else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
479+ else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
480+ else summaryTopAnchor = "parent.top";
481+
482+ var color;
483+ if (hasBackground) {
484+ color = summaryColorWithBackground;
485+ } else {
486+ color = '"grey"';
487+ }
488+
489+ var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
490+
491+ code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
492+ }
493+
494+ if (hasSummary) {
495+ code += 'implicitHeight: summary.y + summary.height + (summary.text ? units.gu(1) : 0);\n';
496+ } else if (hasHeaderRow) {
497+ code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
498+ } else if (hasMascot) {
499+ code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
500+ } else if (hasSubtitle) {
501+ code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
502+ } else if (hasTitle) {
503+ code += 'implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);\n';
504+ }
505+ // Close the AbstractButton
506+ code += '}\n';
507+
508+ return code;
509+}
510+
511+function createCardComponent(parent, template, components) {
512+ var imports = 'import QtQuick 2.2; \n\
513+ import Ubuntu.Components 0.1; \n\
514+ import Ubuntu.Thumbnailer 0.1;\n';
515+ var card = cardString(template, components);
516+ var code = imports + 'Component {\n' + card + '}\n';
517+ return Qt.createQmlObject(code, parent, "createCardComponent");
518+}
plugins/Dash/CardCreatorCache.qmlnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+pragma Singleton
18+import QtQuick 2.2
19+import "CardCreator.js" as CardCreator
20+
21+QtObject {
22+ id: root
23+
24+ property var cache: new Object();
25+
26+ function getCardComponent(template, components) {
27+ if (template === undefined || components === undefined)
28+ return undefined;
29+
30+ var tString = JSON.stringify(template);
31+ var cString = JSON.stringify(components);
32+ var allString = tString + cString;
33+ var component = cache[allString];
34+ if (component === undefined) {
35+ component = CardCreator.createCardComponent(root, template, components);
36+ cache[allString] = component;
37+ }
38+ return component;
39+ }
40+}
plugins/DashViews/abstractdashview.cpp → plugins/Dash/abstractdashview.cpprenamed

The content is the same.

plugins/DashViews/abstractdashview.h → plugins/Dash/abstractdashview.hrenamed

The content is the same.

plugins/DashViews/horizontaljournal.cpp → plugins/Dash/horizontaljournal.cpprenamed

The content is the same.

plugins/DashViews/horizontaljournal.h → plugins/Dash/horizontaljournal.hrenamed

The content is the same.

plugins/DashViews/listviewwithpageheader.cpp → plugins/Dash/listviewwithpageheader.cpprenamed

The content is the same.

plugins/DashViews/listviewwithpageheader.h → plugins/Dash/listviewwithpageheader.hrenamed

The content is the same.

plugins/DashViews/organicgrid.cpp → plugins/Dash/organicgrid.cpprenamed

The content is the same.

plugins/DashViews/organicgrid.h → plugins/Dash/organicgrid.hrenamed

The content is the same.

plugins/DashViews/plugin.cpp → plugins/Dash/plugin.cpprenamed+2−2
@@ -24,9 +24,9 @@
2424
2525#include <QAbstractItemModel>
2626
27−void DashViewsPlugin::registerTypes(const char *uri)
27+void DashPlugin::registerTypes(const char *uri)
2828{
29− Q_ASSERT(uri == QLatin1String("DashViews"));
29+ Q_ASSERT(uri == QLatin1String("Dash"));
3030 qmlRegisterType<QAbstractItemModel>();
3131 qmlRegisterType<HorizontalJournal>(uri, 0, 1, "HorizontalJournal");
3232 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
plugins/DashViews/plugin.h → plugins/Dash/plugin.hrenamed+3−3
@@ -15,13 +15,13 @@
1515 *
1616 */
1717
18−#ifndef DASHVIEWS_PLUGIN_H
19−#define DASHVIEWS_PLUGIN_H
18+#ifndef DASH_PLUGIN_H
19+#define DASH_PLUGIN_H
2020
2121#include <QtQml/QQmlEngine>
2222#include <QtQml/QQmlExtensionPlugin>
2323
24−class DashViewsPlugin : public QQmlExtensionPlugin
24+class DashPlugin : public QQmlExtensionPlugin
2525{
2626 Q_OBJECT
2727 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
plugins/Dash/qmldirnew file+4
@@ -0,0 +1,4 @@
1+module Dash
2+plugin Dash-qml
3+typeinfo plugin.qmltypes
4+singleton CardCreatorCache 0.1 CardCreatorCache.qml
plugins/DashViews/verticaljournal.cpp → plugins/Dash/verticaljournal.cpprenamed

The content is the same.

plugins/DashViews/verticaljournal.h → plugins/Dash/verticaljournal.hrenamed

The content is the same.

plugins/DashViews/qmldirdeleted−3
@@ -1,3 +0,0 @@
1−module DashViews
2−plugin DashViews-qml
3−typeinfo plugin.qmltypes
plugins/Unity/Indicators/menucontentactivator.cpp−1
@@ -316,7 +316,6 @@ MenuContentState* MenuContentActivatorPrivate::content_at(QQmlListProperty<MenuC
316316 if (!d->m_content.contains(index)) {
317317 MenuContentState* content = new MenuContentState(false);
318318 d->m_content[index] = content;
319− Q_EMIT p->contentChanged();
320319 return content;
321320 }
322321
po/fr.po+24−15
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-04-10 11:13-0400\n"
11−"PO-Revision-Date: 2014-01-28 00:34+0000\n"
12−"Last-Translator: Pierre <Unknown>\n"
11+"PO-Revision-Date: 2014-05-08 17:29+0000\n"
12+"Last-Translator: Sébastien Maes <nautilussebastien@gmail.com>\n"
1313"Language-Team: French <fr@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-05-01 06:43+0000\n"
18−"X-Generator: Launchpad (build 16985)\n"
17+"X-Launchpad-Export-Date: 2014-05-10 08:01+0000\n"
18+"X-Generator: Launchpad (build 16996)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -24,7 +24,7 @@ msgstr "Conserver dans le lanceur"
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:73
2626msgid "Unlock from Launcher"
27−msgstr ""
27+msgstr "Retirer du lanceur"
2828
2929#: qml/Components/EdgeDemoOverlay.qml:151
3030msgid "Skip intro"
@@ -37,6 +37,7 @@ msgstr "Bord droit"
3737#: qml/Components/EdgeDemo.qml:114
3838msgid "Try swiping from the right edge to unlock the phone"
3939msgstr ""
40+"Faites glisser votre doigt à partir du bord droit pour déverrouiller."
4041
4142#: qml/Components/EdgeDemo.qml:145
4243msgid "Top edge"
@@ -45,6 +46,8 @@ msgstr "Bord supérieur"
4546#: qml/Components/EdgeDemo.qml:146
4647msgid "Try swiping from the top edge to access the indicators"
4748msgstr ""
49+"Faites glisser votre doigt à partir du bord supérieur pour accéder aux "
50+"indicateurs."
4851
4952#: qml/Components/EdgeDemo.qml:171
5053msgid "Close"
@@ -53,6 +56,8 @@ msgstr "Fermer"
5356#: qml/Components/EdgeDemo.qml:172
5457msgid "Swipe up again to close the settings screen"
5558msgstr ""
59+"Faites glisser à nouveau votre doigt vers le bord supérieur pour fermer "
60+"l'écran des paramètres"
5661
5762#: qml/Components/EdgeDemo.qml:199
5863msgid "Left edge"
@@ -61,16 +66,20 @@ msgstr "Bord gauche"
6166#: qml/Components/EdgeDemo.qml:200
6267msgid "Swipe from the left to reveal the launcher for quick access to apps"
6368msgstr ""
69+"Faites glisser votre doigt à partir du bord gauche pour afficher le lanceur "
70+"d'applications."
6471
6572#: qml/Components/EdgeDemo.qml:227
6673msgid "Well done"
67−msgstr ""
74+msgstr "Félicitations"
6875
6976#: qml/Components/EdgeDemo.qml:228
7077msgid ""
7178"You have now mastered the edge gestures and can start using the "
7279"phone<br><br>Tap on the screen to start"
7380msgstr ""
81+"Vous maîtrisez maintenant les différents gestes et pouvez commencer à "
82+"utiliser le téléphone<br><br>Appuyez sur l'écran pour commencer"
7483
7584#: qml/Components/PageHeader.qml:365
7685msgid "Recent searches"
@@ -78,28 +87,28 @@ msgstr "Recherches récentes"
7887
7988#: qml/Components/SeeMore.qml:35
8089msgid "See more"
81−msgstr ""
90+msgstr "Voir plus"
8291
8392#: qml/Components/SeeMore.qml:58
8493msgid "See less"
85−msgstr ""
94+msgstr "Voir moins"
8695
8796#: qml/Dash/DashApps.qml:37
8897msgid "Recent"
89−msgstr ""
98+msgstr "Récent"
9099
91100#: qml/Dash/PreviewListView.qml:52
92101#, qt-format
93102msgid "%1 Preview"
94−msgstr ""
103+msgstr "%1 Aperçu"
95104
96105#: qml/Dash/Previews/PreviewActionCombo.qml:34
97106msgid "More..."
98−msgstr ""
107+msgstr "Plus..."
99108
100109#: qml/Dash/Previews/PreviewActionCombo.qml:34
101110msgid "Less..."
102−msgstr ""
111+msgstr "Moins..."
103112
104113#: qml/Dash/Previews/PreviewRatingInput.qml:81
105114msgid "Rate this"
@@ -115,7 +124,7 @@ msgstr "Envoyer"
115124
116125#: qml/Greeter/Greeter.qml:151
117126msgid "Swipe to unlock"
118−msgstr ""
127+msgstr "Faites glisser pour déverrouiller"
119128
120129#: qml/Greeter/Lockscreen.qml:149
121130msgid "Emergency Call"
@@ -148,7 +157,7 @@ msgstr "Parlez maintenant…"
148157
149158#: qml/Hud/Hud.qml:94
150159msgid "Speaking..."
151−msgstr ""
160+msgstr "En cours d'écoute..."
152161
153162#: qml/Notifications/NotificationMenuItemFactory.qml:91
154163msgid "Show password"
@@ -156,7 +165,7 @@ msgstr "Afficher le mot de passe"
156165
157166#: qml/Notifications/NotificationMenuItemFactory.qml:103
158167msgid "Please enter SIM PIN"
159−msgstr ""
168+msgstr "Veuillez entrer le code PIN de votre carte SIM"
160169
161170#: qml/Panel/SearchIndicator.qml:27
162171msgid "Search"
po/lv.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-05-08 06:40+0000\n"
17+"X-Launchpad-Export-Date: 2014-05-10 08:01+0000\n"
1818"X-Generator: Launchpad (build 16996)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
qml/Dash/Card.qmldeleted−236
@@ -1,236 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−// For image://albumart and image://thumbnailer image providers
20−import Ubuntu.Thumbnailer 0.1
21−
22−AbstractButton {
23− id: root
24− property var template
25− property var components
26− property var cardData
27−
28− property real fontScale: 1.0
29− property int headerAlignment: Text.AlignLeft
30− readonly property int headerHeight: headerLoader.item ? headerLoader.item.height : 0
31− property int fixedHeaderHeight: -1
32− readonly property string title: cardData && cardData["title"] || ""
33−
34− property bool showHeader: true
35−
36− implicitWidth: childrenRect.width
37− implicitHeight: summary.y + summary.height + (summary.text && backgroundLoader.active ? units.gu(1) : 0)
38−
39− Loader {
40− id: backgroundLoader
41− objectName: "backgroundLoader"
42−
43− readonly property bool artAndSummary: components["art"]["field"] && components["summary"] || false
44− active: template["card-layout"] !== "horizontal" && (template["card-background"] || components["background"] || artAndSummary)
45− anchors.fill: parent
46−
47− sourceComponent: UbuntuShape {
48− objectName: "background"
49− radius: "medium"
50− color: getColor(0) || "white"
51− gradientColor: getColor(1) || color
52− anchors.fill: parent
53− image: backgroundImage.source ? backgroundImage : null
54−
55− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
56−
57− property Image backgroundImage: Image {
58− objectName: "backgroundImage"
59− source: {
60− if (cardData && typeof cardData["background"] === "string") return cardData["background"]
61− else if (template && typeof template["card-background"] === "string") return template["card-background"]
62− else return ""
63− }
64− }
65−
66− function getColor(index) {
67− if (cardData && typeof cardData["background"] === "object"
68− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
69− return cardData["background"]["elements"][index];
70− } else if (template && typeof template["card-background"] === "object"
71− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
72− return template["card-background"]["elements"][index];
73− } else return undefined;
74− }
75− }
76− }
77−
78− UbuntuShape {
79− id: artShape
80− radius: "medium"
81− objectName: "artShape"
82− anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
83− anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
84− visible: cardData && cardData["art"] || false
85−
86− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
87− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect
88−
89− Component.onCompleted: updateWidthHeightBindings();
90− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
91−
92− function updateWidthHeightBindings() {
93− if (aspectSmallerThanImageAspect) {
94− width = Qt.binding(function() { return !visible ? 0 : image.width });
95− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
96− } else {
97− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
98− height = Qt.binding(function() { return !visible ? 0 : image.height });
99− }
100− }
101−
102− image: Image {
103− objectName: "artImage"
104− source: cardData && cardData["art"] || ""
105− cache: true
106− // FIXME uncomment when having investigated / fixed the crash
107− //sourceSize.width: width > height ? width : 0
108− //sourceSize.height: height > width ? height : 0
109− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
110−
111− readonly property real aspect: implicitWidth / implicitHeight
112− readonly property bool isHorizontal: template && template["card-layout"] === "horizontal"
113−
114− Component.onCompleted: updateWidthHeightBindings();
115− onIsHorizontalChanged: updateWidthHeightBindings();
116−
117− function updateWidthHeightBindings() {
118− if (isHorizontal) {
119− width = Qt.binding(function() { return height * artShape.aspect });
120− height = Qt.binding(function() { return root.headerHeight });
121− } else {
122− width = Qt.binding(function() { return root.width });
123− height = Qt.binding(function() { return width / artShape.aspect });
124− }
125− }
126− }
127− }
128−
129− Loader {
130− id: overlayLoader
131− anchors {
132− left: artShape.left
133− right: artShape.right
134− bottom: artShape.bottom
135− }
136− active: template && template["overlay"] && artShape.visible && artShape.image.status === Image.Ready || false
137−
138− sourceComponent: ShaderEffect {
139− id: overlay
140−
141− height: headerLoader.item ? headerLoader.item.height : 0
142− opacity: headerLoader.item ? headerLoader.item.opacity * 0.6 : 0
143−
144− property var source: ShaderEffectSource {
145− id: shaderSource
146− sourceItem: artShape
147− onVisibleChanged: if (visible) scheduleUpdate()
148− live: false
149− sourceRect: Qt.rect(0, artShape.height - overlay.height, artShape.width, overlay.height)
150− }
151−
152− vertexShader: "
153− uniform highp mat4 qt_Matrix;
154− attribute highp vec4 qt_Vertex;
155− attribute highp vec2 qt_MultiTexCoord0;
156− varying highp vec2 coord;
157− void main() {
158− coord = qt_MultiTexCoord0;
159− gl_Position = qt_Matrix * qt_Vertex;
160− }"
161−
162− fragmentShader: "
163− varying highp vec2 coord;
164− uniform sampler2D source;
165− uniform lowp float qt_Opacity;
166− void main() {
167− lowp vec4 tex = texture2D(source, coord);
168− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
169− }"
170− }
171− }
172−
173− Loader {
174− id: headerLoader
175− objectName: "cardHeaderLoader"
176−
177− anchors {
178− top: {
179− if (template) {
180− if (template["overlay"]) return overlayLoader.top;
181− if (template["card-layout"] === "horizontal") return artShape.top;
182− }
183− return artShape.bottom;
184− }
185− left: {
186− if (template) {
187− if (!template["overlay"] && template["card-layout"] === "horizontal") return artShape.right;
188− }
189− return parent.left;
190− }
191− right: parent.right
192− }
193− active: cardData && cardData["title"] || cardData && cardData["mascot"] || false
194−
195− sourceComponent: CardHeader {
196− id: header
197− objectName: "cardHeader"
198−
199− mascot: cardData && cardData["mascot"] || ""
200− title: root.title
201− subtitle: cardData && cardData["subtitle"] || ""
202−
203− titleWeight: components && components["subtitle"] ? Font.DemiBold : Font.Normal
204−
205− opacity: showHeader ? 1 : 0
206− inOverlay: root.template && root.template["overlay"] === true
207− fontColor: inOverlay ? "white" : summary.color
208− useMascotShape: !backgroundLoader.active && !inOverlay
209− headerAlignment: root.headerAlignment
210− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight
211− fontScale: root.fontScale
212−
213− Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
214− }
215− }
216−
217− Label {
218− id: summary
219− objectName: "summaryLabel"
220− anchors {
221− top: headerLoader.active ? headerLoader.bottom : artShape.bottom
222− left: parent.left
223− right: parent.right
224− margins: backgroundLoader.active ? units.gu(1) : 0
225− topMargin: 0
226− }
227− wrapMode: Text.Wrap
228− maximumLineCount: 5
229− elide: Text.ElideRight
230− text: cardData && cardData["summary"] || ""
231− height: text ? implicitHeight : 0
232− fontSize: "small"
233− // TODO karni: Change "grey" to Ubuntu.Components.Palette color once updated.
234− color: backgroundLoader.active && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"
235− }
236−}
qml/Dash/CardCarousel.qml+13−10
@@ -49,21 +49,24 @@ DashRenderer {
4949 property real fontScale: 1 / selectedItemScaleFactor
5050 property real headerHeight: cardTool.headerHeight / selectedItemScaleFactor
5151
52− itemComponent: Card {
53− id: card
54− objectName: "carouselDelegate" + index
55− fixedHeaderHeight: carousel.headerHeight
56− cardData: model
57− template: cardTool.template
58− components: cardTool.components
52+ itemComponent: Loader {
53+ id: loader
5954
6055 property bool explicitlyScaled
6156 property var model
62−
6357 enabled: false
64− showHeader: explicitlyScaled
6558
66− fontScale: carousel.fontScale
59+ sourceComponent: cardTool.cardComponent
60+ onLoaded: {
61+ item.objectName = "carouselDelegate" + index;
62+ item.fixedHeaderHeight = Qt.binding(function() { return carousel.headerHeight; });
63+ item.height = Qt.binding(function() { return cardTool.cardHeight; });
64+ item.cardData = Qt.binding(function() { return model; });
65+ item.template = Qt.binding(function() { return cardTool.template; });
66+ item.components = Qt.binding(function() { return cardTool.components; });
67+ item.fontScale = Qt.binding(function() { return carousel.fontScale; });
68+ item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
69+ }
6770 }
6871 }
6972}
qml/Dash/CardFilterGrid.qml+19−16
@@ -45,25 +45,28 @@ DashRenderer {
4545 collapsedRowCount: Math.min(2, cardTool && cardTool.template && cardTool.template["collapsed-rows"] || 2)
4646 delegateCreationBegin: genericFilterGrid.delegateCreationBegin
4747 delegateCreationEnd: genericFilterGrid.delegateCreationEnd
48− delegate: Loader {
49− asynchronous: true
48+ delegate: Item {
5049 width: filterGrid.cellWidth
5150 height: filterGrid.cellHeight
52− Card {
53− id: card
54− width: cardTool.cardWidth
55− height: cardTool.cardHeight
56− fixedHeaderHeight: cardTool.headerHeight
51+ Loader {
52+ id: loader
53+ sourceComponent: cardTool.cardComponent
5754 anchors.horizontalCenter: parent.horizontalCenter
58− objectName: "delegate" + index
59− cardData: model
60− template: cardTool.template
61− components: cardTool.components
62−
63− headerAlignment: cardTool.headerAlignment
64−
65− onClicked: genericFilterGrid.clicked(index, card.y)
66− onPressAndHold: genericFilterGrid.pressAndHold(index, card.y)
55+ onLoaded: {
56+ item.objectName = "delegate" + index;
57+ item.width = Qt.binding(function() { return cardTool.cardWidth; });
58+ item.height = Qt.binding(function() { return cardTool.cardHeight; });
59+ item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
60+ item.cardData = Qt.binding(function() { return model; });
61+ item.template = Qt.binding(function() { return cardTool.template; });
62+ item.components = Qt.binding(function() { return cardTool.components; });
63+ item.headerAlignment = Qt.binding(function() { return cardTool.headerAlignment; });
64+ }
65+ Connections {
66+ target: loader.item
67+ onClicked: genericFilterGrid.clicked(index, item.y)
68+ onPressAndHold: genericFilterGrid.pressAndHold(index, item.y)
69+ }
6770 }
6871 }
6972 }
qml/Dash/CardHeader.qmldeleted−126
@@ -1,126 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−
20−Item {
21− id: root
22− property url mascot: ""
23− property alias title: titleLabel.text
24− property var subtitle
25−
26− property alias titleWeight: titleLabel.font.weight
27− property alias titleSize: titleLabel.fontSize
28−
29− // FIXME: Saviq, used to scale fonts down in Carousel
30− property real fontScale: 1.0
31−
32− property alias headerAlignment: titleLabel.horizontalAlignment
33−
34− property bool inOverlay: false
35− property bool useMascotShape: true
36− property color fontColor: Theme.palette.selected.backgroundText
37−
38− visible: mascot != "" || title
39− implicitHeight: row.height > 0 ? row.height + row.margins * 2 : 0
40−
41− Row {
42− id: row
43− objectName: "outerRow"
44−
45− property real margins: units.gu(1)
46−
47− spacing: mascotShapeLoader.active || mascotImageLoader.active || inOverlay ? margins : 0
48− anchors {
49− top: parent.top; left: parent.left; right: parent.right
50− margins: margins
51− leftMargin: spacing
52− rightMargin: spacing
53− }
54−
55− Loader {
56− id: mascotShapeLoader
57− objectName: "mascotShapeLoader"
58−
59− active: useMascotShape && mascotImageLoader.item && mascotImageLoader.item.status === Image.Ready
60− visible: active
61− anchors.verticalCenter: parent.verticalCenter
62− // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
63− width: units.gu(6)
64− height: units.gu(5.625)
65− readonly property int maxSize: Math.max(width, height) * 4
66−
67− sourceComponent: UbuntuShape {
68− image: mascotImageLoader.item
69− }
70− }
71−
72− Loader {
73− id: mascotImageLoader
74− active: root.mascot != ""
75− visible: active && !useMascotShape && item.status === Image.Ready
76− anchors.verticalCenter: parent.verticalCenter
77− sourceComponent: Image {
78− objectName: "mascotImage"
79−
80− source: root.mascot
81− width: source ? mascotShapeLoader.width : 0
82− height: mascotShapeLoader.height
83−
84− sourceSize { width: mascotShapeLoader.maxSize; height: mascotShapeLoader.maxSize }
85− fillMode: Image.PreserveAspectCrop
86− horizontalAlignment: Image.AlignHCenter
87− verticalAlignment: Image.AlignVCenter
88− }
89− }
90−
91− Column {
92− objectName: "column"
93− width: parent.width - x
94− spacing: units.dp(2)
95− anchors.verticalCenter: parent.verticalCenter
96−
97− Label {
98− id: titleLabel
99− objectName: "titleLabel"
100− anchors { left: parent.left; right: parent.right }
101− elide: Text.ElideRight
102− font.weight: Font.Normal
103− fontSize: "small"
104− wrapMode: Text.Wrap
105− maximumLineCount: 2
106− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
107− color: fontColor
108− }
109−
110− Loader {
111− active: titleLabel.text && root.subtitle
112− anchors { left: parent.left; right: parent.right }
113− sourceComponent: Label {
114− id: subtitleLabel
115− objectName: "subtitleLabel"
116− elide: Text.ElideRight
117− fontSize: "small"
118− font.weight: Font.Light
119− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
120− color: fontColor
121− text: root.subtitle
122− }
123− }
124− }
125− }
126−}
qml/Dash/CardTool.qml+30−22
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import Dash 0.1
1819
1920/*!
2021 \brief Tool for introspecting Card properties.
@@ -65,6 +66,8 @@ Item {
6566 return layout;
6667 }
6768
69+ property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components);
70+
6871 // FIXME: Saviq
6972 // Only way for the card below to actually be laid out completely.
7073 // If invisible or in "data" array, some components are not taken into account.
@@ -108,7 +111,7 @@ Item {
108111 if (template["card-size"] >= 12 && template["card-size"] <= 38) return units.gu(template["card-size"]);
109112 return units.gu(18.5);
110113 case "grid":
111− return card.implicitHeight
114+ return cardLoader.item ? cardLoader.item.implicitHeight : 0
112115 case "carousel":
113116 return cardWidth / (components ? components["art"]["aspect-ratio"] : 1)
114117 case undefined:
@@ -122,7 +125,8 @@ Item {
122125 /*!
123126 type:real \brief Height of the card's header.
124127 */
125− readonly property alias headerHeight: card.headerHeight
128+ readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
129+ readonly property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
126130
127131 /*!
128132 \brief Desired alignment of header components.
@@ -161,15 +165,8 @@ Item {
161165 }
162166 }
163167
164− Card {
165− id: card
166− objectName: "cardToolCard"
167− template: cardTool.template
168− components: cardTool.components
169−
170− width: cardTool.cardWidth || implicitWidth
171− height: cardTool.cardHeight || implicitHeight
172−
168+ Loader {
169+ id: cardLoader
173170 property var fields: ["art", "mascot", "title", "subtitle", "summary"]
174171 property var maxData: {
175172 "art": Qt.resolvedUrl("graphics/checkers.png"),
@@ -178,19 +175,30 @@ Item {
178175 "subtitle": "—",
179176 "summary": "—\n—\n—\n—\n—"
180177 }
181−
182− onComponentsChanged: {
183− var data = {};
184− for (var k in fields) {
185− var component = components[fields[k]];
186− var key = fields[k];
187− if ((typeof component === "string" && component.length > 0) ||
188− (typeof component === "object" && component !== null
189− && typeof component["field"] === "string" && component["field"].length > 0)) {
190− data[key] = maxData[key];
178+ sourceComponent: cardTool.cardComponent
179+ onLoaded: {
180+ item.objectName = "cardToolCard";
181+ item.asynchronous = false;
182+ item.template = Qt.binding(function() { return cardTool.template; });
183+ item.components = Qt.binding(function() { return cardTool.components; });
184+ item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
185+ item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
186+ }
187+ Connections {
188+ target: cardLoader.item
189+ onComponentsChanged: {
190+ var data = {};
191+ for (var k in cardLoader.fields) {
192+ var component = cardLoader.item.components[cardLoader.fields[k]];
193+ var key = cardLoader.fields[k];
194+ if ((typeof component === "string" && component.length > 0) ||
195+ (typeof component === "object" && component !== null
196+ && typeof component["field"] === "string" && component["field"].length > 0)) {
197+ data[key] = cardLoader.maxData[key];
198+ }
191199 }
200+ cardLoader.item.cardData = data;
192201 }
193− cardData = data;
194202 }
195203 }
196204}
qml/Dash/Previews/PreviewHeader.qml+86−9
@@ -15,9 +15,10 @@
1515 */
1616
1717import QtQuick 2.0
18+import Ubuntu.Components 0.1
1819import "../"
1920
20−/*! This preview widget shows a header that is the same as the card header
21+/*! This preview widget shows a header
2122 * The title comes in widgetData["title"]
2223 * The mascot comes in widgetData["mascot"]
2324 * The subtitle comes in widgetData["subtitle"]
@@ -28,15 +29,91 @@ PreviewWidget {
2829
2930 height: childrenRect.height
3031
31− CardHeader {
32− objectName: "cardHeader"
33− mascot: root.widgetData["mascot"] || ""
34− title: root.widgetData["title"] || ""
35− subtitle: root.widgetData["subtitle"] || ""
32+ Item {
33+ id: headerRoot
34+ objectName: "innerPreviewHeader"
35+ readonly property url mascot: root.widgetData["mascot"] || ""
36+ readonly property string title: root.widgetData["title"] || ""
37+ readonly property string subtitle: root.widgetData["subtitle"] || ""
38+ readonly property color fontColor: "grey"
39+
40+ implicitHeight: row.height + row.margins * 2
3641 width: parent.width
3742
38− titleSize: "large"
39− // TODO Change "grey" to Ubuntu.Components.Palette color once updated.
40− fontColor: "grey"
43+ Row {
44+ id: row
45+ objectName: "outerRow"
46+
47+ property real margins: units.gu(1)
48+
49+ spacing: mascotShapeLoader.active ? margins : 0
50+ anchors {
51+ top: parent.top; left: parent.left; right: parent.right
52+ margins: margins
53+ leftMargin: spacing
54+ rightMargin: spacing
55+ }
56+
57+ Loader {
58+ id: mascotShapeLoader
59+
60+ anchors.verticalCenter: parent.verticalCenter
61+ // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
62+ width: units.gu(6)
63+ height: units.gu(5.625)
64+ readonly property int maxSize: Math.max(width, height) * 4
65+ asynchronous: true
66+
67+ sourceComponent: UbuntuShape {
68+ objectName: "mascotShape"
69+ visible: image.status === Image.Ready
70+ image: Image {
71+ source: headerRoot.mascot
72+ width: source ? mascotShapeLoader.width : 0
73+ height: mascotShapeLoader.height
74+
75+ sourceSize { width: mascotShapeLoader.maxSize; height: mascotShapeLoader.maxSize }
76+ fillMode: Image.PreserveAspectCrop
77+ horizontalAlignment: Image.AlignHCenter
78+ verticalAlignment: Image.AlignVCenter
79+ }
80+ }
81+ }
82+
83+ Column {
84+ objectName: "column"
85+ width: parent.width - x
86+ spacing: units.dp(2)
87+ anchors.verticalCenter: parent.verticalCenter
88+
89+ Label {
90+ id: titleLabel
91+ objectName: "titleLabel"
92+ anchors { left: parent.left; right: parent.right }
93+ elide: Text.ElideRight
94+ font.weight: Font.Normal
95+ fontSize: "large"
96+ wrapMode: Text.Wrap
97+ maximumLineCount: 2
98+ color: headerRoot.fontColor
99+ text: headerRoot.title
100+ }
101+
102+ Loader {
103+ active: titleLabel.text && headerRoot.subtitle
104+ anchors { left: parent.left; right: parent.right }
105+ sourceComponent: Label {
106+ id: subtitleLabel
107+ objectName: "subtitleLabel"
108+ elide: Text.ElideRight
109+ fontSize: "small"
110+ font.weight: Font.Light
111+ color: headerRoot.fontColor
112+ text: headerRoot.subtitle
113+ }
114+ }
115+ }
116+ }
41117 }
118+
42119}
qml/Dash/Previews/PreviewTextSummary.qml+4−2
@@ -37,6 +37,7 @@ PreviewWidget {
3737 left: parent.left
3838 right: parent.right
3939 }
40+ height: visible ? implicitHeight : 0
4041 fontSize: "large"
4142 // TODO karni: Yet another fix requiring Palette update.
4243 color: "grey" //Theme.palette.selected.backgroundText
@@ -68,7 +69,7 @@ PreviewWidget {
6869 wrapMode: Text.Wrap
6970
7071 Behavior on height {
71− UbuntuNumberAnimation {}
72+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
7273 }
7374 }
7475
@@ -79,8 +80,9 @@ PreviewWidget {
7980 left: parent.left
8081 right: parent.right
8182 top: textLabel.bottom
82− topMargin: units.gu(1)
83+ topMargin: visible ? units.gu(1) : 0
8384 }
85+ height: visible ? implicitHeight : 0
8486 visible: textLabel.lineCount > textLabel.maximumCollapsedLineCount
8587 }
8688}
qml/Dash/ScopeListView.qml+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919import SessionManager 0.1
2020
2121ListViewWithPageHeader {
src/ApplicationArguments.h+4−8
@@ -28,14 +28,10 @@ class ApplicationArguments : public QObject
2828{
2929 Q_OBJECT
3030public:
31− ApplicationArguments(const QStringList& args) {
32− if (args.contains(QLatin1String("-windowgeometry")) && args.size() > args.indexOf(QLatin1String("-windowgeometry")) + 1) {
33− QStringList geometryArg = args.at(args.indexOf(QLatin1String("-windowgeometry")) + 1).split('x');
34− if (geometryArg.size() == 2) {
35− m_size.rwidth() = geometryArg.at(0).toInt();
36− m_size.rheight() = geometryArg.at(1).toInt();
37− }
38− }
31+ // Not exposed to the app as setSize isn't invokable
32+ void setSize(int width, int height) {
33+ m_size.rwidth() = width;
34+ m_size.rheight() = height;
3935 }
4036
4137 Q_INVOKABLE bool hasGeometry() const { return m_size.isValid(); }
src/main.cpp+43−6
@@ -18,6 +18,7 @@
1818 */
1919
2020// Qt
21+#include <QCommandLineParser>
2122#include <QtQuick/QQuickView>
2223#include <QtGui/QGuiApplication>
2324#include <QtGui/QScreen>
@@ -45,6 +46,32 @@ int startShell(int argc, const char** argv, void* server)
4546
4647 QGuiApplication::setApplicationName(UNITY8_GREETER ? "Unity 8 Greeter" : "Unity 8");
4748 QGuiApplication *application;
49+
50+ QCommandLineParser parser;
51+ parser.setApplicationDescription("Description: Unity 8 Shell");
52+ parser.addHelpOption();
53+
54+ QCommandLineOption fullscreenOption("fullscreen",
55+ "Run in fullscreen");
56+ parser.addOption(fullscreenOption);
57+
58+ QCommandLineOption framelessOption("frameless",
59+ "Run without window borders");
60+ parser.addOption(framelessOption);
61+
62+ QCommandLineOption mousetouchOption("mousetouch",
63+ "Allow the mouse to provide touch input");
64+ parser.addOption(mousetouchOption);
65+
66+ QCommandLineOption windowGeometryOption(QStringList() << "windowgeometry",
67+ "Specify the window geometry as [<width>x<height>]", "windowgeometry", "1");
68+ parser.addOption(windowGeometryOption);
69+
70+ QCommandLineOption testabilityOption("testability",
71+ "DISCOURAGED: Please set QT_LOAD_TESTABILITY instead. \n \
72+Load the testability driver");
73+ parser.addOption(testabilityOption);
74+
4875 if (isUbuntuMirServer) {
4976 QLibrary unityMir("unity-mir", 1);
5077 unityMir.load();
@@ -61,6 +88,11 @@ int startShell(int argc, const char** argv, void* server)
6188 application = new QGuiApplication(argc, (char**)argv);
6289 }
6390
91+ // Treat args with single dashes the same as arguments with two dashes
92+ // Ex: -fullscreen == --fullscreen
93+ parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
94+ parser.process(*application);
95+
6496 QString indicatorProfile = qgetenv("UNITY_INDICATOR_PROFILE");
6597 if (indicatorProfile.isEmpty()) {
6698 indicatorProfile = UNITY8_GREETER ? "phone_greeter" : "phone";
@@ -68,12 +100,17 @@ int startShell(int argc, const char** argv, void* server)
68100
69101 resolveIconTheme();
70102
71− QStringList args = application->arguments();
72− ApplicationArguments qmlArgs(args);
103+ ApplicationArguments qmlArgs;
104+ if (parser.isSet(windowGeometryOption) &&
105+ parser.value(windowGeometryOption).split('x').size() == 2)
106+ {
107+ QStringList geom = parser.value(windowGeometryOption).split('x');
108+ qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
109+ }
73110
74111 // The testability driver is only loaded by QApplication but not by QGuiApplication.
75112 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
76− if (args.contains(QLatin1String("-testability")) || getenv("QT_LOAD_TESTABILITY")) {
113+ if (parser.isSet(testabilityOption) || getenv("QT_LOAD_TESTABILITY")) {
77114 QLibrary testLib(QLatin1String("qttestability"));
78115 if (testLib.load()) {
79116 typedef void (*TasInitialize)(void);
@@ -96,14 +133,14 @@ int startShell(int argc, const char** argv, void* server)
96133 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
97134 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
98135 view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
99− if (args.contains(QLatin1String("-frameless"))) {
136+ if (parser.isSet(framelessOption)) {
100137 view->setFlags(Qt::FramelessWindowHint);
101138 }
102139
103140 // You will need this if you want to interact with touch-only components using a mouse
104141 // Needed only when manually testing on a desktop.
105142 MouseTouchAdaptor *mouseTouchAdaptor = 0;
106− if (args.contains(QLatin1String("-mousetouch"))) {
143+ if (parser.isSet(mousetouchOption)) {
107144 mouseTouchAdaptor = new MouseTouchAdaptor;
108145 application->installNativeEventFilter(mouseTouchAdaptor);
109146 }
@@ -140,7 +177,7 @@ int startShell(int argc, const char** argv, void* server)
140177 view->setSource(source);
141178 view->setColor(Qt::transparent);
142179
143− if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || args.contains(QLatin1String("-fullscreen"))) {
180+ if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || parser.isSet(fullscreenOption)) {
144181 // First, size window equal to screen (fake a real WM fullscreen mode).
145182 // Then, in case we are actually running inside a windowing system,
146183 // nicely set actual WM fullscreen hint for its benefit.
tests/autopilot/unity8/shell/emulators/dash.py+3−4
@@ -161,7 +161,7 @@ class GenericScopeView(emulators.UnityEmulatorBase):
161161
162162 """
163163 category_element = self._get_category_element(category)
164− icon = category_element.select_single('Card', title=app_name)
164+ icon = category_element.select_single('AbstractButton', title=app_name)
165165 # FIXME some categories need a long press in order to see the preview.
166166 # Some categories do not show previews, like recent apps.
167167 # --elopio - 2014-1-14
@@ -191,7 +191,7 @@ class DashApps(GenericScopeView):
191191
192192 """
193193 category_element = self._get_category_element(category)
194− application_cards = category_element.select_many('Card')
194+ application_cards = category_element.select_many('AbstractButton')
195195
196196 # sort by y, x
197197 application_cards = sorted(
@@ -201,8 +201,7 @@ class DashApps(GenericScopeView):
201201 result = []
202202 for card in application_cards:
203203 if card.objectName != 'cardToolCard':
204− card_header = card.select_single('CardHeader')
205− result.append(card_header.title)
204+ result.append(card.title)
206205 return result
207206
208207
tests/mocks/Unity/fake_categories.cpp+15−2
@@ -108,7 +108,12 @@ Categories::data(const QModelIndex& index, int role) const
108108 case RoleRenderer:
109109 {
110110 QVariantMap map;
111− map["category-layout"] = index.row() % 2 == 0 ? "grid" : "carousel";
111+ if (index.row() % 2 == 0) {
112+ map["category-layout"] = "grid";
113+ } else {
114+ map["category-layout"] = "carousel";
115+ map["overlay"] = true;
116+ }
112117 map["card-size"] = "small";
113118 return map;
114119 }
@@ -146,7 +151,14 @@ Categories::data(const QModelIndex& index, int role) const
146151 case RoleRenderer:
147152 {
148153 QVariantMap map;
149− map["category-layout"] = index.row() % 2 == 0 ? "grid" : "carousel";
154+ if (index.row() % 2 == 0) {
155+ map["category-layout"] = "grid";
156+ } else {
157+ map["category-layout"] = "carousel";
158+ map["card-size"] = "medium";
159+ map["collapsed-rows"] = 2;
160+ map["overlay"] = true;
161+ }
150162 map["card-size"] = "small";
151163 return map;
152164 }
@@ -157,6 +169,7 @@ Categories::data(const QModelIndex& index, int role) const
157169 artMap["field"] = "art";
158170 map["art"] = artMap;
159171 map["title"] = "HOLA";
172+ map["subtitle"] = "HOLA";
160173 return map;
161174 }
162175 case RoleProgressSource:
tests/mocks/Unity/fake_resultsmodel.cpp+1
@@ -103,6 +103,7 @@ ResultsModel::data(const QModelIndex& index, int role) const
103103 case RoleArt:
104104 return qmlDirectory() + "graphics/applicationIcons/dash.png";
105105 case RoleSubtitle:
106+ return QString("Subtitle.%1.%2").arg(m_categoryId).arg(index.row());
106107 case RoleMascot:
107108 case RoleEmblem:
108109 case RoleOldPrice:
tests/plugins/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
11add_subdirectory(AccountsService)
2−add_subdirectory(DashViews)
2+add_subdirectory(Dash)
33add_subdirectory(LightDM)
44add_subdirectory(SessionManager)
55add_subdirectory(Ubuntu)
tests/plugins/DashViews/CMakeLists.txt → tests/plugins/Dash/CMakeLists.txtrenamed+16−9
@@ -16,7 +16,7 @@ include_directories(
1616 ${Qt5Quick_INCLUDE_DIRS}
1717 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
1818 ${Qt5V8_PRIVATE_INCLUDE_DIR}
19− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews
19+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash
2020 ${CMAKE_CURRENT_BINARY_DIR}
2121 )
2222
@@ -28,11 +28,11 @@ add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
2828macro(add_lvwph_test FILENAME TESTNAME)
2929 add_executable(${TESTNAME}TestExec
3030 ${FILENAME}test.cpp
31− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/listviewwithpageheader.cpp)
31+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/listviewwithpageheader.cpp)
3232 qt5_use_modules(${TESTNAME}TestExec Test Core Qml)
3333 target_link_libraries(${TESTNAME}TestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
3434
35− add_binary_qml_test(${TESTNAME} "" "DashViews-qml")
35+ add_binary_qml_test(${TESTNAME} "" "Dash-qml")
3636endmacro()
3737
3838add_lvwph_test(listviewwithpageheader ListViewWithPageHeader)
@@ -42,24 +42,24 @@ add_lvwph_test(listviewwithpageheadersectionexternalmodel ListViewWithPageHeader
4242macro(add_dashview_try_test FILENAME TESTNAME)
4343 add_executable(${TESTNAME}TestExec
4444 ${FILENAME}test.cpp
45− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
46− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractdashview.cpp
45+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/${FILENAME}.cpp
46+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/abstractdashview.cpp
4747 )
4848 qt5_use_modules(${TESTNAME}TestExec Test Core Qml)
4949 target_link_libraries(${TESTNAME}TestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
5050
51− add_binary_qml_test(${TESTNAME} "" "DashViews-qml")
51+ add_binary_qml_test(${TESTNAME} "" "Dash-qml")
5252
5353 add_executable(${TESTNAME}tryExec
5454 ${FILENAME}try.cpp
55− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
56− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractdashview.cpp
55+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/${FILENAME}.cpp
56+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/abstractdashview.cpp
5757 )
5858 qt5_use_modules(${TESTNAME}tryExec Test Core Qml)
5959 target_link_libraries(${TESTNAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
6060 add_custom_target(try${TESTNAME}
6161 ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME}tryExec
62− DEPENDS ${TESTNAME}tryExec DashViews-qml
62+ DEPENDS ${TESTNAME}tryExec Dash-qml
6363 )
6464endmacro()
6565
@@ -67,6 +67,13 @@ add_dashview_try_test(verticaljournal VerticalJournal)
6767add_dashview_try_test(horizontaljournal HorizontalJournal)
6868add_dashview_try_test(organicgrid OrganicGrid)
6969
70+# CardCreator test
71+add_executable(CardCreatorTestExec cardcreatortest.cpp)
72+qt5_use_modules(CardCreatorTestExec Test Core Qml)
73+target_link_libraries(CardCreatorTestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
74+add_binary_qml_test(CardCreator "" "Dash-qml")
75+
76+# plain qml test
7077set(qmltest_DEFAULT_TARGETS qmluitests)
7178set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
7279add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.resnew file+77
@@ -0,0 +1,77 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;
58+Label {
59+ id: titleLabel;
60+ objectName: "titleLabel";
61+ anchors { right: parent.right;left: parent.left;
62+top: artShapeHolder.bottom;
63+ topMargin: units.gu(1);
64+ }
65+ elide: Text.ElideRight;
66+ fontSize: "small";
67+ wrapMode: Text.Wrap;
68+ maximumLineCount: 2;
69+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
70+ color: "grey";
71+ visible: showHeader ;
72+ text: root.title;
73+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
74+ horizontalAlignment: root.headerAlignment;
75+ }
76+implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
77+}
tests/plugins/Dash/cardcreator/1.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1.6,"field":"art","fill-mode":"fit"},"title":{"field":"title"}}
3+result: 1.res
tests/plugins/Dash/cardcreator/2.resnew file+115
@@ -0,0 +1,115 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: root.asynchronous;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ color: getColor(0) || "white";
24+ gradientColor: getColor(1) || color;
25+ anchors.fill: parent;
26+ image: backgroundImage.source ? backgroundImage : null;
27+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
28+ property Image backgroundImage: Image {
29+ objectName: "backgroundImage";
30+ source: {
31+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
32+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
33+ else return "";
34+ }
35+ }
36+ function getColor(index) {
37+ if (cardData && typeof cardData["background"] === "object"
38+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
39+ return cardData["background"]["elements"][index];
40+ } else if (template && typeof template["card-background"] === "object"
41+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
42+ return template["card-background"]["elements"][index];
43+ } else return undefined;
44+ }
45+ }
46+ }
47+readonly property size artShapeSize: Qt.size(-1, -1);
48+readonly property int headerHeight: row.height + row.margins * 2;
49+Row {
50+ id: row;
51+ objectName: "outerRow";
52+ property real margins: units.gu(1);
53+ spacing: margins;
54+ anchors { top: parent.top;
55+ topMargin: units.gu(1);
56+ left: parent.left;
57+}
58+ anchors.right: parent.right;
59+ anchors.margins: margins;
60+data: [ Image {
61+ id: mascotImage;
62+ objectName: "mascotImage";
63+ anchors { verticalCenter: parent.verticalCenter; }
64+ readonly property int maxSize: Math.max(width, height) * 4;
65+ source: cardData && cardData["mascot"];
66+ width: units.gu(6);
67+ height: units.gu(5.625);
68+ sourceSize { width: maxSize; height: maxSize }
69+ fillMode: Image.PreserveAspectCrop;
70+ horizontalAlignment: Image.AlignHCenter;
71+ verticalAlignment: Image.AlignVCenter;
72+ visible: showHeader;
73+ }
74+,
75+Column {
76+ anchors.verticalCenter: parent.verticalCenter;
77+ spacing: units.dp(2);
78+ width: parent.width - x;
79+ data: [
80+Label {
81+ id: titleLabel;
82+ objectName: "titleLabel";
83+ anchors { left: parent.left; right: parent.right }
84+ elide: Text.ElideRight;
85+ fontSize: "small";
86+ wrapMode: Text.Wrap;
87+ maximumLineCount: 2;
88+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
89+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey";
90+ visible: showHeader ;
91+ text: root.title;
92+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
93+ horizontalAlignment: root.headerAlignment;
94+ }
95+,
96+Label {
97+ id: subtitleLabel;
98+ objectName: "subtitleLabel";
99+ anchors { left: parent.left; right: parent.right }
100+
101+ elide: Text.ElideRight;
102+ fontSize: "small";
103+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
104+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey";
105+ visible: titleLabel.visible && titleLabel.text;
106+ text: cardData && cardData["subtitle"] || "";
107+ font.weight: Font.Light;
108+ horizontalAlignment: root.headerAlignment;
109+ }
110+]
111+}
112+]
113+}
114+implicitHeight: row.y + row.height + units.gu(1);
115+}
tests/plugins/Dash/cardcreator/2.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"}}
3+result: 2.res
tests/plugins/Dash/cardcreator/3.resnew file+94
@@ -0,0 +1,94 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
58+Label {
59+ id: titleLabel;
60+ objectName: "titleLabel";
61+ anchors { right: parent.right;left: parent.left;
62+top: artShapeHolder.bottom;
63+ topMargin: units.gu(1);
64+}
65+ elide: Text.ElideRight;
66+ fontSize: "small";
67+ wrapMode: Text.Wrap;
68+ maximumLineCount: 2;
69+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
70+ color: "grey";
71+ visible: showHeader ;
72+ text: root.title;
73+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
74+ horizontalAlignment: root.headerAlignment;
75+ }
76+Label {
77+ id: subtitleLabel;
78+ objectName: "subtitleLabel";
79+ anchors { left: titleLabel.left;
80+ leftMargin: titleLabel.leftMargin;
81+ right: titleLabel.right;
82+ top: titleLabel.bottom;
83+ topMargin: units.dp(2); }
84+ elide: Text.ElideRight;
85+ fontSize: "small";
86+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
87+ color: "grey";
88+ visible: titleLabel.visible && titleLabel.text;
89+ text: cardData && cardData["subtitle"] || "";
90+ font.weight: Font.Light;
91+ horizontalAlignment: root.headerAlignment;
92+ }
93+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
94+}
tests/plugins/Dash/cardcreator/3.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":0.75,"field":"art","fill-mode":"crop"},"subtitle":{"field":"price"},"title":{"field":"title"}}
3+result: 3.res
tests/plugins/Dash/cardcreator/4.resnew file+95
@@ -0,0 +1,95 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: row.height + row.margins * 2;
16+Row {
17+ id: row;
18+ objectName: "outerRow";
19+ property real margins: units.gu(1);
20+ spacing: margins;
21+ anchors { top: parent.top;
22+ topMargin: units.gu(1);
23+ left: parent.left;
24+}
25+ anchors.right: parent.right;
26+ anchors.margins: margins;
27+data: [ Loader {
28+ id: mascotShapeLoader;
29+ objectName: "mascotShapeLoader";
30+ asynchronous: root.asynchronous;
31+ active: mascotImage.status === Image.Ready;
32+ visible: showHeader && active && status == Loader.Ready;
33+ width: units.gu(6);
34+ height: units.gu(5.625);
35+ sourceComponent: UbuntuShape { image: mascotImage }
36+ anchors { verticalCenter: parent.verticalCenter; }
37+ }
38+
39+,
40+Image {
41+ id: mascotImage;
42+ objectName: "mascotImage";
43+ anchors { verticalCenter: parent.verticalCenter; }
44+ readonly property int maxSize: Math.max(width, height) * 4;
45+ source: cardData && cardData["mascot"];
46+ width: units.gu(6);
47+ height: units.gu(5.625);
48+ sourceSize { width: maxSize; height: maxSize }
49+ fillMode: Image.PreserveAspectCrop;
50+ horizontalAlignment: Image.AlignHCenter;
51+ verticalAlignment: Image.AlignVCenter;
52+ visible: false;
53+ }
54+
55+,
56+Column {
57+ anchors.verticalCenter: parent.verticalCenter;
58+ spacing: units.dp(2);
59+ width: parent.width - x;
60+data: [ Label {
61+ id: titleLabel;
62+ objectName: "titleLabel";
63+ anchors { left: parent.left; right: parent.right }
64+ elide: Text.ElideRight;
65+ fontSize: "small";
66+ wrapMode: Text.Wrap;
67+ maximumLineCount: 2;
68+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
69+ color: "grey";
70+ visible: showHeader ;
71+ text: root.title;
72+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
73+ horizontalAlignment: root.headerAlignment;
74+ }
75+,
76+Label {
77+ id: subtitleLabel;
78+ objectName: "subtitleLabel";
79+ anchors { left: parent.left; right: parent.right }
80+
81+ elide: Text.ElideRight;
82+ fontSize: "small";
83+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
84+ color: "grey";
85+ visible: titleLabel.visible && titleLabel.text;
86+ text: cardData && cardData["subtitle"] || "";
87+ font.weight: Font.Light;
88+ horizontalAlignment: root.headerAlignment;
89+ }
90+]
91+}
92+]
93+}
94+implicitHeight: row.y + row.height + units.gu(1);
95+}
tests/plugins/Dash/cardcreator/4.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"horizontal","card-size":"large","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"mascot":{"field":"mascot"},"subtitle":{"field":"domain"},"title":{"field":"title"}}
3+result: 4.res
tests/plugins/Dash/cardcreator/5.resnew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+Loader {
58+ id: overlayLoader;
59+ anchors {
60+ left: artShapeHolder.left;
61+ right: artShapeHolder.right;
62+ bottom: artShapeHolder.bottom;
63+ }
64+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
65+ asynchronous: root.asynchronous;
66+ visible: showHeader && status == Loader.Ready;
67+ sourceComponent: ShaderEffect {
68+ id: overlay;
69+ height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight;
70+ opacity: 0.6;
71+ property var source: ShaderEffectSource {
72+ id: shaderSource;
73+ sourceItem: artShapeLoader.item;
74+ onVisibleChanged: if (visible) scheduleUpdate();
75+ live: false;
76+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
77+ }
78+ vertexShader: "
79+ uniform highp mat4 qt_Matrix;
80+ attribute highp vec4 qt_Vertex;
81+ attribute highp vec2 qt_MultiTexCoord0;
82+ varying highp vec2 coord;
83+ void main() {
84+ coord = qt_MultiTexCoord0;
85+ gl_Position = qt_Matrix * qt_Vertex;
86+ }";
87+ fragmentShader: "
88+ varying highp vec2 coord;
89+ uniform sampler2D source;
90+ uniform lowp float qt_Opacity;
91+ void main() {
92+ lowp vec4 tex = texture2D(source, coord);
93+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
94+ }";
95+ }
96+ }
97+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
98+Label {
99+ id: titleLabel;
100+ objectName: "titleLabel";
101+ anchors { left: parent.left;
102+ leftMargin: units.gu(1);
103+ right: parent.right;
104+ top: overlayLoader.top;
105+ topMargin: units.gu(1);
106+}
107+ elide: Text.ElideRight;
108+ fontSize: "small";
109+ wrapMode: Text.Wrap;
110+ maximumLineCount: 2;
111+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
112+ color: "white";
113+ visible: showHeader && overlayLoader.active;
114+ text: root.title;
115+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
116+ horizontalAlignment: root.headerAlignment;
117+ }
118+Label {
119+ id: subtitleLabel;
120+ objectName: "subtitleLabel";
121+ anchors { left: titleLabel.left;
122+ leftMargin: titleLabel.leftMargin;
123+ right: titleLabel.right;
124+ top: titleLabel.bottom;
125+ topMargin: units.dp(2);
126+ }
127+ elide: Text.ElideRight;
128+ fontSize: "small";
129+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
130+ color: "white";
131+ visible: titleLabel.visible && titleLabel.text;
132+ text: cardData && cardData["subtitle"] || "";
133+ font.weight: Font.Light;
134+ horizontalAlignment: root.headerAlignment;
135+ }
136+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/5.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"medium","category-layout":"carousel","collapsed-rows":2,"overlay":true}
2+components: {"art":{"aspect-ratio":1,"field":"art","fill-mode":"crop"},"subtitle":{"field":"artist"},"title":{"field":"title"}}
3+result: 5.res
tests/plugins/Dash/cardcreatortest.cppnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDir>
18+#include <QQmlEngine>
19+#include <QQuickItem>
20+#include <QQuickView>
21+#include <QtTestGui>
22+
23+class CardCreatorTest : public QObject
24+{
25+ Q_OBJECT
26+
27+private Q_SLOTS:
28+
29+ void initTestCase()
30+ {
31+ }
32+
33+ void init()
34+ {
35+ view = new QQuickView();
36+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
37+ view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/cardcreatortest.qml"));
38+ view->show();
39+ QTest::qWaitForWindowExposed(view);
40+ }
41+
42+ void cleanup()
43+ {
44+ delete view;
45+ }
46+
47+ void testKnownCases()
48+ {
49+ const QString templateString("template: ");
50+ const QString componentsString("components: ");
51+ const QString resultString("result: ");
52+
53+ const QString testDirPath = DASHVIEWSTEST_FOLDER "/cardcreator/";
54+ QDir d(testDirPath);
55+ const QStringList testFiles = d.entryList(QStringList() << "*.tst");
56+ foreach(const QString &testFileName, testFiles) {
57+ qDebug() << testFileName;
58+ QFile testFile(testDirPath + testFileName);
59+ QVERIFY(testFile.open(QIODevice::ReadOnly));
60+ QTextStream ts(&testFile);
61+ const QStringList lines = ts.readAll().split("\n");
62+
63+ QVERIFY(lines[0].startsWith(templateString));
64+ QVERIFY(lines[1].startsWith(componentsString));
65+ QVERIFY(lines[2].startsWith(resultString));
66+ const QString templateJSON = lines[0].mid(templateString.length());
67+ const QString componentsJSON = lines[1].mid(componentsString.length());
68+ const QString resultFileName = lines[2].mid(resultString.length());
69+
70+ QVariant cardStringResult;
71+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
72+
73+ QFile testResultFile(testDirPath + resultFileName);
74+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75+ QTextStream ts2(&testResultFile);
76+ const QString expectedResult = ts2.readAll();
77+ QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
78+
79+ QVariant createCardComponentResult;
80+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
81+ QVERIFY(createCardComponentResult.toBool());
82+ }
83+ }
84+
85+private:
86+ QQuickView *view;
87+};
88+
89+QTEST_MAIN(CardCreatorTest)
90+
91+#include "cardcreatortest.moc"
tests/plugins/Dash/cardcreatortest.qmlnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import "../../../plugins/Dash/CardCreator.js" as CardCreator
19+
20+Item {
21+ id: root
22+ function cardString(template, components) {
23+ return CardCreator.cardString(JSON.parse(template), JSON.parse(components));
24+ }
25+
26+ function createCardComponent(template, components) {
27+ return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components)) !== null;
28+ }
29+}
tests/plugins/DashViews/horizontaljournaltest.cpp → tests/plugins/Dash/horizontaljournaltest.cpprenamed

The content is the same.

tests/plugins/DashViews/horizontaljournaltest.qml → tests/plugins/Dash/horizontaljournaltest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/horizontaljournaltry.cpp → tests/plugins/Dash/horizontaljournaltry.cpprenamed

The content is the same.

tests/plugins/DashViews/horizontaljournaltry.qml → tests/plugins/Dash/horizontaljournaltry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp → tests/plugins/Dash/listviewwithpageheadersectionexternalmodeltest.cpprenamed

The content is the same.

tests/plugins/DashViews/listviewwithpageheadersectiontest.cpp → tests/plugins/Dash/listviewwithpageheadersectiontest.cpprenamed

The content is the same.

tests/plugins/DashViews/listviewwithpageheadertest.cpp → tests/plugins/Dash/listviewwithpageheadertest.cpprenamed

The content is the same.

tests/plugins/DashViews/listviewwithpageheadertest.qml → tests/plugins/Dash/listviewwithpageheadertest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/listviewwithpageheadertestsection.qml → tests/plugins/Dash/listviewwithpageheadertestsection.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/listviewwithpageheadertestsectionexternalmodel.qml → tests/plugins/Dash/listviewwithpageheadertestsectionexternalmodel.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/organicgridtest.cpp → tests/plugins/Dash/organicgridtest.cpprenamed

The content is the same.

tests/plugins/DashViews/organicgridtest.qml → tests/plugins/Dash/organicgridtest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/organicgridtry.cpp → tests/plugins/Dash/organicgridtry.cpprenamed

The content is the same.

tests/plugins/DashViews/organicgridtry.qml → tests/plugins/Dash/organicgridtry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/plugins/DashViews/tst_ListViewWithPageHeaderQML.qml → tests/plugins/Dash/tst_ListViewWithPageHeaderQML.qmlrenamed+1−1
@@ -16,7 +16,7 @@
1616
1717import QtQuick 2.0
1818import QtTest 1.0
19−import DashViews 0.1
19+import Dash 0.1
2020
2121Rectangle {
2222 width: 300
tests/plugins/DashViews/verticaljournaltest.cpp → tests/plugins/Dash/verticaljournaltest.cpprenamed

The content is the same.

tests/plugins/DashViews/verticaljournaltest.qml → tests/plugins/Dash/verticaljournaltest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/verticaljournaltry.cpp → tests/plugins/Dash/verticaljournaltry.cpprenamed

The content is the same.

tests/plugins/DashViews/verticaljournaltry.qml → tests/plugins/Dash/verticaljournaltry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/qmltests/Dash/Previews/tst_Preview.qml+1−1
@@ -23,7 +23,7 @@ import Unity 0.2 as Unity
2323Rectangle {
2424 id: root
2525 width: units.gu(60)
26− height: units.gu(80)
26+ height: units.gu(60)
2727 color: Theme.palette.selected.background
2828
2929 Item {
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+66−4
@@ -24,6 +24,12 @@ Rectangle {
2424 width: units.gu(60)
2525 height: units.gu(80)
2626
27+ property var origHeaderjson: {
28+ "title": "THE TITLE",
29+ "subtitle": "Something catchy",
30+ "mascot": "../graphics/play_button.png"
31+ }
32+
2733 property var headerjson: {
2834 "title": "THE TITLE",
2935 "subtitle": "Something catchy",
@@ -43,14 +49,70 @@ Rectangle {
4349 }
4450
4551 UT.UnityTestCase {
52+ id: testCase
4653 name: "PreviewHeaderTest"
4754 when: windowShown
4855
56+ property Item mascot: findChild(previewHeader, "mascotShape")
57+ property Item outerRow: findChild(previewHeader, "outerRow")
58+ property Item column: findChild(previewHeader, "column")
59+
60+ function initTestCase() {
61+ verify(typeof testCase.mascot === "object", "Couldn't find mascot object.");
62+ verify(typeof testCase.outerRow === "object", "Couldn't find outerRow object.");
63+ verify(typeof testCase.column === "object", "Couldn't find column object.");
64+
65+ headerjson.mascot = "";
66+ headerjson.title = "";
67+ headerjson.subtitle = "";
68+ previewHeader.widgetData = headerjson;
69+ }
70+
71+ function test_mascot_data() {
72+ return [
73+ { tag: "Empty", source: "", visible: false },
74+ { tag: "Invalid", source: "bad_path", visible: false },
75+ { tag: "Valid", source: "../graphics/play_button.png", visible: true },
76+ ]
77+ }
78+
79+ function test_mascot(data) {
80+ headerjson.mascot = data.source;
81+ previewHeader.widgetData = headerjson;
82+
83+ tryCompare(testCase.mascot, "visible", data.visible);
84+ }
85+
86+ function test_dimensions_data() {
87+ return [
88+ { tag: "Column width with mascot", object: column, width: previewHeader.width - mascot.width - outerRow.margins * 3, mascot: "artwork/avatar.png" },
89+ { tag: "Header height", object: previewHeader, height: function() { return outerRow.height + outerRow.margins * 2 } },
90+ ]
91+ }
92+
93+ function test_dimensions(data) {
94+ if (data.hasOwnProperty("mascot")) {
95+ headerjson.mascot = data.mascot;
96+ }
97+ previewHeader.widgetData = headerjson;
98+
99+ if (data.hasOwnProperty("width")) {
100+ tryCompare(data.object, "width", data.width);
101+ }
102+
103+ if (data.hasOwnProperty("height")) {
104+ tryCompareFunction(function() { return data.object.height === data.height() }, true);
105+ }
106+ }
107+
49108 function test_json() {
50− var cardHeader = findChild(previewHeader, "cardHeader");
51− compare(cardHeader.title, "THE TITLE");
52− compare(cardHeader.subtitle, "Something catchy");
53− compare(cardHeader.mascot.toString().slice(-24), "graphics/play_button.png");
109+ headerjson = origHeaderjson;
110+ previewHeader.widgetData = headerjson;
111+
112+ var innerPreviewHeader = findChild(previewHeader, "innerPreviewHeader");
113+ compare(innerPreviewHeader.title, "THE TITLE");
114+ compare(innerPreviewHeader.subtitle, "Something catchy");
115+ compare(innerPreviewHeader.mascot.toString().slice(-24), "graphics/play_button.png");
54116 }
55117 }
56118}
tests/qmltests/Dash/Previews/tst_PreviewTextSummary.qml+5−5
@@ -42,7 +42,7 @@ Rectangle {
4242
4343 PreviewTextSummary {
4444 id: previewTextSummary
45− anchors.fill: parent
45+ anchors { left: parent.left; right: parent.right }
4646 widgetData: widgetDataComplete
4747 }
4848
@@ -51,6 +51,7 @@ Rectangle {
5151 when: windowShown
5252
5353 property var textLabel: findChild(previewTextSummary, "textLabel")
54+ property var titleLabel: findChild(previewTextSummary, "titleLabel")
5455
5556 function init() {
5657 verify(typeof textLabel === "object", "TextLabel object could not be found.")
@@ -61,16 +62,14 @@ Rectangle {
6162 }
6263
6364 function test_optional_title() {
64− var titleLabel = findChild(previewTextSummary, "titleLabel")
65−
6665 // verify titleLabel is visible and textLabel is anchored below it
6766 compare(titleLabel.visible, true)
68− compare(textLabel.y, titleLabel.height)
67+ tryCompare(textLabel, "y", titleLabel.height)
6968
7069 // verify titleLabel disappears and textLabel moves up
7170 previewTextSummary.widgetData = widgetDataNoTitle
7271 compare(titleLabel.visible, false)
73− compare(textLabel.y, 0)
72+ tryCompare(textLabel, "y", 0)
7473 }
7574
7675 function test_see_more() {
@@ -96,6 +95,7 @@ Rectangle {
9695 verify(textLabel.lineCount <= textLabel.maximumCollapsedLineCount)
9796 compare(seeMore.visible, false)
9897 tryCompare(textLabel, "height", textLabel.contentHeight)
98+ tryCompare(previewTextSummary, "height", titleLabel.height + textLabel.height)
9999 }
100100 }
101101}
tests/qmltests/Dash/tst_Card.qml+78−92
@@ -29,8 +29,8 @@ Rectangle {
2929
3030 property string cardData: '
3131 {
32− "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
33− "mascot": "../../tests/qmltests/Dash/artwork/avatar.png",
32+ "art": "../../../tests/qmltests/Dash/artwork/music-player-design.png",
33+ "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
3434 "title": "foo",
3535 "subtitle": "bar",
3636 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
@@ -98,21 +98,26 @@ Rectangle {
9898
9999 CardTool {
100100 id: cardTool
101− template: card.template
102− components: card.components
101+ template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
102+ components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
103103 viewWidth: units.gu(48)
104104 }
105105
106− Card {
107− id: card
108− anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
106+ readonly property var card: loader.item
109107
110− width: cardTool.cardWidth || implicitWidth
111− height: cardTool.cardHeight || implicitHeight
108+ Loader {
109+ id: loader
110+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
112111
113− template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
114− components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
115− cardData: Helpers.mapData(dataArea.text, components, dataError)
112+ sourceComponent: cardTool.cardComponent
113+ onLoaded: {
114+ item.template = Qt.binding(function() { return cardTool.template; });
115+ item.components = Qt.binding(function() { return cardTool.components; });
116+ item.cardData = Qt.binding(function() { return Helpers.mapData(dataArea.text, cardTool.components, dataError); });
117+ item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
118+ item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
119+ item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
120+ }
116121 }
117122
118123 Rectangle {
@@ -181,60 +186,45 @@ Rectangle {
181186
182187 when: windowShown
183188
184− property Item header: findChild(card, "cardHeader")
185− property Item headerLoader: findChild(card, "cardHeaderLoader")
186− property Item title: findChild(header, "titleLabel")
187− property Item art: findChild(card, "artShape")
189+ property Item title: findChild(card, "titleLabel")
190+ property Item subtitle: findChild(card, "subtitleLabel")
191+ property var headerRow: findChild(card, "outerRow")
192+ property var art: findChild(card, "artShape")
188193 property Item artImage: findChild(card, "artImage")
189194 property Item summary: findChild(card, "summaryLabel")
190195 property Item background: findChild(card, "background")
191196 property Item backgroundLoader: findChild(card, "backgroundLoader")
192197 property Item backgroundImage: findChild(card, "backgroundImage")
193−
194− function initTestCase() {
195− verify(typeof testCase.header === "object", "Couldn't find header object.");
196− verify(typeof testCase.art === "object", "Couldn't find art object.");
197− verify(typeof testCase.artImage === "object", "Couldn't find artImage object.");
198− verify(typeof testCase.summary === "object", "Couldn't find summary object.");
199− verify(typeof testCase.artImage === "object", "Couldn't find background object.");
200− verify(typeof testCase.summary === "object", "Couldn't find backgroundImage object.");
201− }
198+ property Item mascotImage: findChild(card, "mascotImage");
202199
203200 function cleanup() {
204201 selector.selectedIndex = -1;
205202 }
206203
207−
208− function test_header_binding_data() {
209− return [
210− { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
211− { tag: "Title", property: "title", value: "foo", index: 0 },
212− { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
213− ];
214− }
215−
216− function test_header_binding(data) {
217− selector.selectedIndex = data.index;
218− tryCompare(testCase.header, data.property, data.value);
219− }
220−
221204 function test_card_binding_data() {
222205 return [
223− { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
224− { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
225− { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
206+ { tag: "Art", object: "artImage", property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
207+ { tag: "Summary", object: "summary", property: "text", field: "summary", index: 0 },
208+ { tag: "Fit", object: "art", fill: Image.PreserveAspectFit, index: 4 },
209+ { tag: "Mascot", object: "mascotImage", property: "source", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
210+ { tag: "Title", object: "title", property: "text", value: "foo", index: 0 },
211+ { tag: "Subtitle", object: "subtitle", property: "text", value: "bar", index: 0 },
226212 ];
227213 }
228214
229215 function test_card_binding(data) {
230216 selector.selectedIndex = data.index;
217+ waitForRendering(card);
218+
219+ tryCompareFunction(function() { return testCase[data.object] !== null }, true);
220+ var object = testCase[data.object];
231221
232222 if (data.hasOwnProperty('value')) {
233− tryCompare(data.object, data.property, data.value);
223+ tryCompare(object, data.property, data.value);
234224 }
235225
236226 if (data.hasOwnProperty('field')) {
237− tryCompare(data.object, data.property, card.cardData[data.field]);
227+ tryCompare(object, data.property, card.cardData[data.field]);
238228 }
239229 }
240230
@@ -243,31 +233,21 @@ Rectangle {
243233 { tag: "Medium", width: units.gu(18.5), index: 0 },
244234 { tag: "Small", width: units.gu(12), index: 1 },
245235 { tag: "Large", width: units.gu(38), index: 2 },
246− { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
236+ { tag: "Wide", width: units.gu(18.5), index: 0 },
247237 { tag: "Horizontal", width: units.gu(38), index: 5 },
248238 // Make sure card ends with header when there's no summary
249− { tag: "NoSummary", height: function() { return headerLoader.y + headerLoader.height }, index: 6 },
250− { tag: "HorizontalNoSummary", height: function() { return headerLoader.height }, card_layout: "horizontal", index: 6 },
239+ { tag: "NoSummary", height: function() { var cardToolRow = findChild(cardTool, "outerRow");
240+ return cardToolRow.y + cardToolRow.height + units.gu(1) }, index: 6 },
241+ { tag: "HorizontalNoSummary", height: function() { return headerRow.height + units.gu(2) }, card_layout: "horizontal", index: 6 },
251242 ]
252243 }
253244
254245 function test_card_size(data) {
255− waitForRendering(card);
256246 selector.selectedIndex = data.index;
257247
258− if (data.hasOwnProperty("size")) {
259− card.template['card-size'] = data.size;
260− card.templateChanged();
261− }
262−
263248 if (data.hasOwnProperty("card_layout")) {
264− card.template['card-layout'] = data.card_layout;
265− card.templateChanged();
266− }
267−
268− if (data.hasOwnProperty("aspect")) {
269− card.components['art']['aspect-ratio'] = data.aspect;
270− card.componentsChanged();
249+ cardTool.template['card-layout'] = data.card_layout;
250+ cardTool.templateChanged();
271251 }
272252
273253 if (data.hasOwnProperty("width")) {
@@ -288,25 +268,26 @@ Rectangle {
288268 { tag: "Large", width: units.gu(38), index: 2 },
289269 { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
290270 { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
291− { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
292− { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
293− { tag: "HorizontalWidth", width: function() { return headerLoader.x }, index: 5 },
271+ { tag: "VerticalWidth", width: function() { return headerRow.width + units.gu(1) * 2 }, index: 0 },
272+ { tag: "HorizontalHeight", height: function() { return headerRow.height + units.gu(1) * 2 }, index: 5 },
273+ { tag: "HorizontalWidth", width: function() { return headerRow.x - units.gu(1) }, index: 5 },
294274 ]
295275 }
296276
297277 function test_art_size(data) {
298278 selector.selectedIndex = data.index;
299−
300279 if (data.hasOwnProperty("size")) {
301− card.template['card-size'] = data.size;
302− card.templateChanged();
280+ cardTool.template['card-size'] = data.size;
281+ cardTool.templateChanged();
303282 }
304283
305284 if (data.hasOwnProperty("aspect")) {
306− card.components['art']['aspect-ratio'] = data.aspect;
307− card.componentsChanged();
285+ cardTool.components['art']['aspect-ratio'] = data.aspect;
286+ cardTool.componentsChanged();
308287 }
309288
289+ waitForRendering(card);
290+
310291 if (data.hasOwnProperty("width")) {
311292 if (typeof data.width === "function") {
312293 tryCompareFunction(function() { return art.width === data.width() }, true);
@@ -326,38 +307,40 @@ Rectangle {
326307
327308 function test_art_layout_data() {
328309 return [
329− { tag: "Vertical", left: function() { return 0 }, index: 0},
330− { tag: "Horizontal", left: function() { return art.width }, index: 5 },
310+ { tag: "Vertical", left: function() { return units.gu(1); }, index: 0},
311+ { tag: "Horizontal", left: function() { return art.width + units.gu(1); }, index: 5 },
331312 ];
332313 }
333314
334315 function test_art_layout(data) {
335316 selector.selectedIndex = data.index;
317+ waitForRendering(card);
336318
337− tryCompare(testCase.headerLoader, "x", data.left());
319+ tryCompare(headerRow, "x", data.left());
338320 }
339321
340322 function test_header_layout_data() {
341323 return [
342− { tag: "Vertical", top: function() { return art.y + art.height },
343− left: function() { return art.x }, index: 0 },
344− { tag: "Horizontal", top: function() { return art.y },
345− left: function() { return art.x + art.width }, index: 5 },
346− { tag: "Overlay", top: function() { return art.y + art.height - header.height },
347− left: function() { return art.x }, index: 9 },
324+ { tag: "Vertical", top: function() { return art.y + art.height + units.gu(1) },
325+ left: function() { return art.x + units.gu(1) }, index: 0 },
326+ { tag: "Horizontal", top: function() { return art.y + units.gu(1) },
327+ left: function() { return art.x + art.width + units.gu(1) }, index: 5 },
328+ { tag: "Overlay", top: function() { return art.y + art.height - headerRow.height - units.gu(1) },
329+ left: function() { return art.x + units.gu(1) }, index: 9 },
348330 ]
349331 }
350332
351333 function test_header_layout(data) {
352334 selector.selectedIndex = data.index;
335+ waitForRendering(card);
353336
354− tryCompareFunction(function() { return testCase.headerLoader.y === data.top() }, true);
355− tryCompareFunction(function() { return testCase.headerLoader.x === data.left() }, true);
337+ tryCompareFunction(function() { return testCase.headerRow.y === data.top() }, true);
338+ tryCompareFunction(function() { return testCase.headerRow.x === data.left() }, true);
356339 }
357340
358341 function test_summary_layout_data() {
359342 return [
360− { tag: "With header", top: function() { return headerLoader.y + headerLoader.height }, index: 0 },
343+ { tag: "With header", top: function() { return headerRow.y + headerRow.height + units.gu(1); }, index: 0 },
361344 { tag: "Without header", top: function() { return art.y + art.height }, index: 7 },
362345 ]
363346 }
@@ -365,16 +348,16 @@ Rectangle {
365348 function test_summary_layout(data) {
366349 selector.selectedIndex = data.index;
367350
351+ waitForRendering(card);
352+
368353 tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
369354 }
370355
371356 function test_art_visibility() {
372357 selector.selectedIndex = 8;
373358
374− tryCompare(testCase.artImage, "source", "");
375− compare(testCase.art.visible, false);
376− compare(testCase.art.height, 0);
377− compare(testCase.art.width, 0);
359+ compare(testCase.artImage, null);
360+ compare(testCase.art, null);
378361 }
379362
380363 function test_background_data() {
@@ -402,7 +385,11 @@ Rectangle {
402385
403386 waitForRendering(card);
404387
405− tryCompare(backgroundLoader, "active", data.visible);
388+ if (data.visible) {
389+ tryCompare(backgroundLoader, "active", true);
390+ } else {
391+ compare(backgroundLoader, null);
392+ }
406393
407394 if (data.hasOwnProperty("color")) {
408395 tryCompare(background, "color", data.color);
@@ -449,13 +436,15 @@ Rectangle {
449436
450437 function test_fontColor(data) {
451438 selector.selectedIndex = 10;
439+ waitForRendering(card);
452440
453441 background.color = data.tag;
454442
455443 var fontColor = data.dark ? "grey" : "white";
456444
457445 tryCompareFunction(function() { return Qt.colorEqual(summary.color, fontColor); }, true);
458− tryCompareFunction(function() { return Qt.colorEqual(header.fontColor, fontColor); }, true);
446+ tryCompareFunction(function() { return Qt.colorEqual(title.color, fontColor); }, true);
447+ tryCompareFunction(function() { return Qt.colorEqual(subtitle.color, fontColor); }, true);
459448 }
460449
461450 function test_mascotShape_data() {
@@ -468,15 +457,12 @@ Rectangle {
468457
469458 function test_mascotShape(data) {
470459 selector.selectedIndex = data.index;
460+ waitForRendering(card);
471461
472462 var shape = findChild(card, "mascotShapeLoader");
473− var image = findChild(card, "mascotImage");
474−
475− verify(shape, "Could not find shape.");
476− verify(image, "Could not find image.");
477463
478− tryCompare(shape, "active", data.shape);
479− tryCompare(image, "visible", !data.shape);
464+ compare(shape !== null, data.shape);
465+ tryCompare(mascotImage, "visible", !data.shape);
480466 }
481467 }
482468}
tests/qmltests/Dash/tst_CardBenchmark.qml+10−7
@@ -54,13 +54,16 @@ Rectangle {
5454 Repeater {
5555 id: cardRepeater
5656 model: 0
57− Card {
58− width: cardTool.cardWidth || implicitWidth
59− height: cardTool.cardHeight || implicitHeight
60−
61− template: cardTool.template
62− components: cardTool.components
63− cardData: Helpers.mapData(root.cardData, components)
57+ Loader {
58+ sourceComponent: cardTool.cardComponent
59+ onLoaded: {
60+ item.objectName = "delegate" + index;
61+ item.width = Qt.binding(function() { return cardTool.cardWidth || implicitWidth; });
62+ item.height = Qt.binding(function() { return cardTool.cardHeight || implicitHeight; });
63+ item.cardData = Qt.binding(function() { return Helpers.mapData(root.cardData, cardTool.components); });
64+ item.template = Qt.binding(function() { return cardTool.template; });
65+ item.components = Qt.binding(function() { return cardTool.components; });
66+ }
6467 }
6568 }
6669
tests/qmltests/Dash/tst_CardHeader.qmldeleted−116
@@ -1,116 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Ubuntu.Components 0.1
20−import Unity.Test 0.1 as UT
21−import "../../../qml/Dash"
22−
23−
24−Rectangle {
25− width: units.gu(40)
26− height: units.gu(72)
27− color: "lightgrey"
28−
29− CardHeader {
30− id: cardHeader
31− anchors { left: parent.left; right: parent.right }
32− }
33−
34− Rectangle {
35− anchors.fill: cardHeader
36− color: "lightblue"
37− opacity: 0.5
38− }
39−
40− UT.UnityTestCase {
41− id: testCase
42− name: "CardHeader"
43−
44− when: windowShown
45−
46− property Item mascot: findChild(cardHeader, "mascotShapeLoader")
47− property Item titleLabel: findChild(cardHeader, "titleLabel")
48− property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
49− property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
50− property Item outerRow: findChild(cardHeader, "outerRow")
51− property Item column: findChild(cardHeader, "column")
52−
53− function initTestCase() {
54− verify(typeof testCase.mascot === "object", "Couldn't find mascot object.");
55− verify(typeof testCase.titleLabel === "object", "Couldn't find titleLabel object.");
56− verify(typeof testCase.subtitleLabel === "object", "Couldn't find subtitleLabel object.");
57− verify(typeof testCase.oldPriceLabel === "object", "Couldn't find oldPriceLabel object.");
58− }
59−
60− function cleanup() {
61− cardHeader.mascot = "";
62− cardHeader.title = "";
63− cardHeader.subtitle = "";
64− }
65−
66− function test_mascot_data() {
67− return [
68− { tag: "Empty", source: "", visible: false },
69− { tag: "Invalid", source: "bad_path", visible: false },
70− { tag: "Valid", source: Qt.resolvedUrl("artwork/avatar.png"), visible: true },
71− ]
72− }
73−
74− function test_mascot(data) {
75− cardHeader.mascot = data.source;
76− tryCompare(testCase.mascot, "visible", data.visible);
77− }
78−
79− function test_labels_data() {
80− return [
81− { tag: "Empty", visible: false },
82− { tag: "Title only", title: "Foo", visible: true },
83− { tag: "Subtitle only", subtitle: "Bar", visible: false },
84− { tag: "Both", title: "Foo", subtitle: "Bar", visible: true },
85− ]
86− }
87−
88− function test_labels(data) {
89− cardHeader.title = data.title !== undefined ? data.title : "";
90− cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
91− tryCompare(cardHeader, "visible", data.visible);
92− }
93−
94− function test_dimensions_data() {
95− return [
96− { tag: "Column width", object: column, width: cardHeader.width },
97− { tag: "Column width with mascot", object: column, width: cardHeader.width - mascot.width - outerRow.margins * 3, mascot: "artwork/avatar.png" },
98− { tag: "Header height", object: cardHeader, height: function() { return outerRow.height + outerRow.margins * 2 } },
99− ]
100− }
101−
102− function test_dimensions(data) {
103− if (data.hasOwnProperty("mascot")) {
104− cardHeader.mascot = data.mascot;
105− }
106−
107− if (data.hasOwnProperty("width")) {
108− tryCompare(data.object, "width", data.width);
109− }
110−
111− if (data.hasOwnProperty("height")) {
112− tryCompareFunction(function() { return data.object.height === data.height() }, true);
113− }
114− }
115− }
116−}
tests/qmltests/Dash/tst_CardTool.qml+11−31
@@ -127,26 +127,6 @@ Rectangle {
127127 }
128128 }
129129 }
130−
131− Card {
132− id: card
133− template: cardTool.template;
134− components: cardTool.components;
135− cardData: Helpers.mapData(dataArea.text, components, dataError)
136−
137− width: cardTool.cardWidth || implicitWidth
138− height: cardTool.cardHeight || implicitHeight
139−
140− clip: true
141− headerAlignment: cardTool.headerAlignment
142−
143− Rectangle {
144− anchors.fill: parent
145− color: "transparent"
146− border.width: 1
147− border.color: "green"
148− }
149− }
150130 }
151131
152132 Rectangle {
@@ -257,7 +237,7 @@ Rectangle {
257237 id: testCase
258238 name: "Card"
259239
260− property Card internalCard: findChild(cardTool, "cardToolCard")
240+ property var internalCard: findChild(cardTool, "cardToolCard")
261241
262242 when: windowShown
263243
@@ -272,16 +252,16 @@ Rectangle {
272252
273253 function test_card_size_data() {
274254 return [
275− { tag: "Medium", width: units.gu(18.5), height: function() { return card.height }, index: 0 },
276− { tag: "No art", width: units.gu(18.5), height: function() { return card.height }, index: 1 },
277− { tag: "No summary", width: units.gu(18.5), height: function() { return card.height }, index: 2 },
278− { tag: "Just header", width: units.gu(18.5), height: function() { return card.height }, index: 3 },
279− { tag: "Just title", width: units.gu(18.5), height: function() { return card.height }, index: 4 },
280− { tag: "Title and subtitle", width: units.gu(18.5), height: function() { return card.height }, index: 5 },
281− { tag: "Title and mascot", width: units.gu(18.5), height: function() { return card.height }, index: 6 },
282− { tag: "Small", width: units.gu(12), height: function() { return card.height }, index: 7 },
283− { tag: "Large", width: units.gu(38), height: function() { return card.height }, index: 8 },
284− { tag: "Horizontal", width: units.gu(38), height: function() { return card.height }, index: 9 },
255+ { tag: "Medium", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 0 },
256+ { tag: "No art", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 1 },
257+ { tag: "No summary", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 2 },
258+ { tag: "Just header", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 3 },
259+ { tag: "Just title", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 4 },
260+ { tag: "Title and subtitle", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 5 },
261+ { tag: "Title and mascot", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 6 },
262+ { tag: "Small", width: units.gu(12), height: function() { return internalCard ? internalCard.height : 0 }, index: 7 },
263+ { tag: "Large", width: units.gu(38), height: function() { return internalCard ? internalCard.height : 0 }, index: 8 },
264+ { tag: "Horizontal", width: units.gu(38), height: function() { return internalCard ? internalCard.height : 0 }, index: 9 },
285265 { tag: "OrganicGrid", width: undefined, height: undefined, index: 0, layout_index: 1},
286266 { tag: "Journal", width: undefined, height: units.gu(20), size: 20, index: 0, layout_index: 2 },
287267 { tag: "OversizedJournal", width: undefined, height: units.gu(18.5), size: 40, index: 0, layout_index: 2 },