on xenial help sign in
Merge lp:~unity-team/unity8/1536296
463dd26Albert Astals Cid
plugins/Dash/CardCreator.js+46−34
@@ -16,9 +16,9 @@
16
16
17.pragma library
17.pragma library
18
18
19−// %1 is the template["card-background"] string
19+// %1 is the template["card-background"]["elements"][0]
20−// %2 is the template["card-background"]["elements"][0]
20+// %2 is the template["card-background"]["elements"][1]
21−// %3 is the template["card-background"]["elements"][1]
21+// %3 is the template["card-background"] string
22var kBackgroundLoaderCode = 'Loader {\n\
22var kBackgroundLoaderCode = 'Loader {\n\
23 id: backgroundLoader; \n\
23 id: backgroundLoader; \n\
24 objectName: "backgroundLoader"; \n\
24 objectName: "backgroundLoader"; \n\
@@ -46,14 +46,14 @@ var kBackgroundLoaderCode = 'Loader {\n\
46 objectName: "backgroundImage"; \n\
46 objectName: "backgroundImage"; \n\
47 source: { \n\
47 source: { \n\
48 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
48 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49− else return "%1"; \n\
49+ else return %3; \n\
50 } \n\
50 } \n\
51 } \n\
51 } \n\
52 function getColor(index) { \n\
52 function getColor(index) { \n\
53 if (cardData && typeof cardData["background"] === "object" \n\
53 if (cardData && typeof cardData["background"] === "object" \n\
54 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
54 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
55 return cardData["background"]["elements"][index]; \n\
55 return cardData["background"]["elements"][index]; \n\
56− } else return index === 0 ? %2 : %3; \n\
56+ } else return index === 0 ? %1 : %2; \n\
57 } \n\
57 } \n\
58 } \n\
58 } \n\
59 }\n';
59 }\n';
@@ -71,7 +71,7 @@ var kArtShapeHolderCode = 'Item { \n\
71 Loader { \n\
71 Loader { \n\
72 id: artShapeLoader; \n\
72 id: artShapeLoader; \n\
73 objectName: "artShapeLoader"; \n\
73 objectName: "artShapeLoader"; \n\
74− readonly property string cardArt: cardData && cardData["art"] || "%5"; \n\
74+ readonly property string cardArt: cardData && cardData["art"] || %5; \n\
75 active: cardArt != ""; \n\
75 active: cardArt != ""; \n\
76 asynchronous: root.asynchronous; \n\
76 asynchronous: root.asynchronous; \n\
77 visible: status == Loader.Ready; \n\
77 visible: status == Loader.Ready; \n\
@@ -252,7 +252,7 @@ var kMascotImageCode = 'CroppedImageMinimumSourceSize { \n\
252 id: mascotImage; \n\
252 id: mascotImage; \n\
253 objectName: "mascotImage"; \n\
253 objectName: "mascotImage"; \n\
254 anchors { %1 } \n\
254 anchors { %1 } \n\
255− source: cardData && cardData["mascot"] || "%4"; \n\
255+ source: cardData && cardData["mascot"] || %4; \n\
256 width: units.gu(6); \n\
256 width: units.gu(6); \n\
257 height: units.gu(5.625); \n\
257 height: units.gu(5.625); \n\
258 horizontalAlignment: Image.AlignHCenter; \n\
258 horizontalAlignment: Image.AlignHCenter; \n\
@@ -376,27 +376,21 @@ var kAudioProgressBarCode = 'CardAudioProgress { \n\
376 color: %3; \n\
376 color: %3; \n\
377 }';
377 }';
378
378
379−function evil_param(object) {
379+function sanitizeColor(colorString) {
380− for (var x in object) {
380+ if (colorString !== undefined) {
381− if (typeof object[x] === "object" && evil_param(object[x]))
381+ if (colorString.match(/^[#a-z0-9]*$/i) === null) {
382− return true;
382+ // This is not the perfect regexp for color
383−
383+ // but what we're trying to do here is just protect
384− if (typeof object[x] === "string" && object[x].match(/"(?:[^"\\]|\\.)*"/) != null)
384+ // against injection so it's ok
385− return true;
385+ return "";
386+ }
386 }
387 }
387−
388− return false;
388+ return colorString;
389}
389}
390
390
391function cardString(template, components) {
391function cardString(template, components) {
392 var code;
392 var code;
393
393
394− if (evil_param(template))
395− return "";
396−
397− if (evil_param(components))
398− return "";
399−
400 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
394 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
401
395
402 code = 'AbstractButton { \n\
396 code = 'AbstractButton { \n\
@@ -445,18 +439,26 @@ function cardString(template, components) {
445 }
439 }
446
440
447 if (hasBackground) {
441 if (hasBackground) {
442+ var templateCardBackground;
448− var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
443+ if (template && typeof template["card-background"] === "string") {
444+ templateCardBackground = 'decodeURI("' + encodeURI(template["card-background"]) + '")';
445+ } else {
446+ templateCardBackground = '""';
447+ }
448+
449 var backgroundElements0;
449 var backgroundElements0;
450 var backgroundElements1;
450 var backgroundElements1;
451 if (template && typeof template["card-background"] === "object" && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
451 if (template && typeof template["card-background"] === "object" && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
452− if (template["card-background"]["elements"][0] !== undefined) {
452+ var element0 = sanitizeColor(template["card-background"]["elements"][0]);
453+ var element1 = sanitizeColor(template["card-background"]["elements"][1]);
454+ if (element0 !== undefined) {
453− backgroundElements0 = '"%1"'.arg(template["card-background"]["elements"][0]);
455+ backgroundElements0 = '"%1"'.arg(element0);
454 }
456 }
455− if (template["card-background"]["elements"][1] !== undefined) {
457+ if (element1 !== undefined) {
456− backgroundElements1 = '"%1"'.arg(template["card-background"]["elements"][1]);
458+ backgroundElements1 = '"%1"'.arg(element1);
457 }
459 }
458 }
460 }
459− code += kBackgroundLoaderCode.arg(templateCardBackground).arg(backgroundElements0).arg(backgroundElements1);
461+ code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(templateCardBackground);
460 }
462 }
461
463
462 if (hasArt) {
464 if (hasArt) {
@@ -482,11 +484,16 @@ function cardString(template, components) {
482 }
484 }
483
485
484 var fallback = components["art"] && components["art"]["fallback"] || "";
486 var fallback = components["art"] && components["art"]["fallback"] || "";
485− var fallbackCode = "";
487+ fallback = encodeURI(fallback);
488+ var fallbackStatusCode = "";
489+ var fallbackURICode = '""';
486 if (fallback !== "") {
490 if (fallback !== "") {
491+ // fallbackStatusCode has %5 in it because we want to substitute it for fallbackURICode
492+ // which in kArtShapeHolderCode is %5
487− fallbackCode += 'onStatusChanged: if (status === Image.Error) source = "%1";'.arg(fallback);
493+ fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %5;';
494+ fallbackURICode = 'decodeURI("%1")'.arg(fallback);
488 }
495 }
489− code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode).arg(fallbackCode).arg(fallback);
496+ code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode).arg(fallbackStatusCode).arg(fallbackURICode);
490 } else {
497 } else {
491 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
498 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
492 }
499 }
@@ -579,11 +586,16 @@ function cardString(template, components) {
579
586
580 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
587 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
581 var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
588 var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
582− var fallbackCode = "";
589+ fallback = encodeURI(fallback);
590+ var fallbackStatusCode = "";
591+ var fallbackURICode = '""';
583 if (fallback !== "") {
592 if (fallback !== "") {
593+ // fallbackStatusCode has %4 in it because we want to substitute it for fallbackURICode
594+ // which in kMascotImageCode is %4
584− fallbackCode += 'onStatusChanged: if (status === Image.Error) source = "%1";'.arg(fallback);
595+ fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %4;';
596+ fallbackURICode = 'decodeURI("%1")'.arg(fallback);
585 }
597 }
586− mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible).arg(fallbackCode).arg(fallback);
598+ mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible).arg(fallbackStatusCode).arg(fallbackURICode);
587 }
599 }
588
600
589 var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white")';
601 var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white")';
tests/plugins/Dash/cardcreator/10.resnew file+142
@@ -0,0 +1,142 @@
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property string artShapeStyle: "inset";
6+ property string backgroundShapeStyle: "inset";
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int titleAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+ enabled: true;
17+
18+Loader {
19+ id: backgroundLoader;
20+ objectName: "backgroundLoader";
21+ anchors.fill: parent;
22+ asynchronous: root.asynchronous;
23+ visible: status == Loader.Ready;
24+ sourceComponent: UbuntuShape {
25+ objectName: "background";
26+ radius: "medium";
27+ aspect: {
28+ switch (root.backgroundShapeStyle) {
29+ case "inset": return UbuntuShape.Inset;
30+ case "shadow": return UbuntuShape.DropShadow;
31+ default:
32+ case "flat": return UbuntuShape.Flat;
33+ }
34+ }
35+ backgroundColor: getColor(0) || "white";
36+ secondaryBackgroundColor: getColor(1) || backgroundColor;
37+ backgroundMode: UbuntuShape.VerticalGradient;
38+ anchors.fill: parent;
39+ source: backgroundImage.source ? backgroundImage : null;
40+ property real luminance: Style.luminance(backgroundColor);
41+ property Image backgroundImage: Image {
42+ objectName: "backgroundImage";
43+ source: {
44+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45+ else return "";
46+ }
47+ }
48+ function getColor(index) {
49+ if (cardData && typeof cardData["background"] === "object"
50+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
51+ return cardData["background"]["elements"][index];
52+ } else return index === 0 ? "" : "";
53+ }
54+ }
55+ }
56+readonly property size artShapeSize: Qt.size(-1, -1);
57+readonly property int headerHeight: row.height;
58+Row {
59+ id: row;
60+ objectName: "outerRow";
61+ property real margins: units.gu(1);
62+ spacing: margins;
63+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
64+ anchors { top: parent.top;
65+ topMargin: units.gu(1);
66+left: parent.left;
67+ }
68+ anchors.right: parent.right;
69+ anchors.margins: margins;
70+ anchors.rightMargin: 0;
71+ data: [
72+ CroppedImageMinimumSourceSize {
73+ id: mascotImage;
74+ objectName: "mascotImage";
75+ anchors { verticalCenter: parent.verticalCenter; }
76+ source: cardData && cardData["mascot"] || "";
77+ width: units.gu(6);
78+ height: units.gu(5.625);
79+ horizontalAlignment: Image.AlignHCenter;
80+ verticalAlignment: Image.AlignVCenter;
81+ visible: showHeader;
82+
83+ }
84+,Item {
85+ id: headerTitleContainer;
86+ anchors { verticalCenter: parent.verticalCenter; }
87+ width: parent.width - x;
88+ implicitHeight: titleLabel.height + subtitleLabel.height;
89+ data: [
90+ Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+rightMargin: units.gu(1);
95+left: parent.left;
96+ top: parent.top; }
97+ elide: Text.ElideRight;
98+ fontSize: "small";
99+ wrapMode: Text.Wrap;
100+ maximumLineCount: 2;
101+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
102+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
103+ visible: showHeader ;
104+ width: undefined;
105+ text: root.title;
106+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
107+ horizontalAlignment: root.titleAlignment;
108+ }
109+,Label {
110+ id: subtitleLabel;
111+ objectName: "subtitleLabel";
112+ anchors { right: parent.right;
113+ left: parent.left;
114+rightMargin: units.gu(1);
115+top: titleLabel.bottom;
116+ }
117+ anchors.topMargin: units.dp(2);
118+ elide: Text.ElideRight;
119+ maximumLineCount: 1;
120+ fontSize: "x-small";
121+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
122+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
123+ visible: titleLabel.visible && titleLabel.text;
124+ text: cardData && cardData["subtitle"] || "";
125+ font.weight: Font.Light;
126+ }
127+
128+ ]
129+ }
130+
131+ ]
132+ }
133+UbuntuShape {
134+ id: touchdown;
135+ objectName: "touchdown";
136+ anchors { fill: backgroundLoader }
137+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
138+ radius: "medium";
139+ borderSource: "radius_pressed.sci"
140+ }
141+implicitHeight: row.y + row.height + units.gu(1);
142+}
tests/plugins/Dash/cardcreator/10.tst+2−1
@@ -1,2 +1,3 @@
1−template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2, "card-background": { "type": "color", "elements": [ "\" : \"\";} Item { } function moo() { return true ? \"" ] } }
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2, "card-background": { "type": "color", "elements": [ "\\", ": 3; } Item { } function moo () { \"" ] } }
2components: {"art":{"aspect-ratio":1},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{}}
2components: {"art":{"aspect-ratio":1},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{}}
3+result: 10.res
tests/plugins/Dash/cardcreator/11.resnew file+218
@@ -0,0 +1,218 @@
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property string artShapeStyle: "inset";
6+ property string backgroundShapeStyle: "inset";
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int titleAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+ enabled: true;
17+
18+Loader {
19+ id: backgroundLoader;
20+ objectName: "backgroundLoader";
21+ anchors.fill: parent;
22+ asynchronous: root.asynchronous;
23+ visible: status == Loader.Ready;
24+ sourceComponent: UbuntuShape {
25+ objectName: "background";
26+ radius: "medium";
27+ aspect: {
28+ switch (root.backgroundShapeStyle) {
29+ case "inset": return UbuntuShape.Inset;
30+ case "shadow": return UbuntuShape.DropShadow;
31+ default:
32+ case "flat": return UbuntuShape.Flat;
33+ }
34+ }
35+ backgroundColor: getColor(0) || "white";
36+ secondaryBackgroundColor: getColor(1) || backgroundColor;
37+ backgroundMode: UbuntuShape.VerticalGradient;
38+ anchors.fill: parent;
39+ source: backgroundImage.source ? backgroundImage : null;
40+ property real luminance: Style.luminance(backgroundColor);
41+ property Image backgroundImage: Image {
42+ objectName: "backgroundImage";
43+ source: {
44+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45+ else return "";
46+ }
47+ }
48+ function getColor(index) {
49+ if (cardData && typeof cardData["background"] === "object"
50+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
51+ return cardData["background"]["elements"][index];
52+ } else return index === 0 ? "#E9E9E9" : undefined;
53+ }
54+ }
55+ }
56+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
57+Item {
58+ id: artShapeHolder;
59+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
60+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
61+ anchors { horizontalCenter: parent.horizontalCenter; }
62+ Loader {
63+ id: artShapeLoader;
64+ objectName: "artShapeLoader";
65+ readonly property string cardArt: cardData && cardData["art"] || decodeURI("%5C");
66+ active: cardArt != "";
67+ asynchronous: root.asynchronous;
68+ visible: status == Loader.Ready;
69+ sourceComponent: Item {
70+ id: artShape;
71+ objectName: "artShape";
72+ readonly property bool doShapeItem: components["art"]["conciergeMode"] !== true;
73+ visible: image.status == Image.Ready;
74+ readonly property alias image: artImage;
75+ ShaderEffectSource {
76+ id: artShapeSource;
77+ sourceItem: artImage;
78+ anchors.centerIn: parent;
79+ width: 1;
80+ height: 1;
81+ hideSource: doShapeItem;
82+ }
83+ Loader {
84+ anchors.fill: parent;
85+ visible: artShape.doShapeItem;
86+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
87+ Component {
88+ id: artShapeShapeComponent;
89+ UbuntuShape {
90+ source: artShapeSource;
91+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
92+ radius: "medium";
93+ aspect: {
94+ switch (root.artShapeStyle) {
95+ case "inset": return UbuntuShape.Inset;
96+ case "shadow": return UbuntuShape.DropShadow;
97+ default:
98+ case "flat": return UbuntuShape.Flat;
99+ }
100+ }
101+ }
102+ }
103+ Component {
104+ id: artShapeIconComponent;
105+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
106+ }
107+ }
108+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
109+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
110+ Component.onCompleted: { updateWidthHeightBindings(); }
111+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
112+ function updateWidthHeightBindings() {
113+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
114+ width = root.fixedArtShapeSize.width;
115+ height = root.fixedArtShapeSize.height;
116+ } else {
117+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
118+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
119+ }
120+ }
121+ CroppedImageMinimumSourceSize {
122+ id: artImage;
123+ objectName: "artImage";
124+ source: artShapeLoader.cardArt;
125+ asynchronous: root.asynchronous;
126+ width: root.width;
127+ height: width / artShape.aspect;
128+ onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
129+ }
130+ }
131+ }
132+ }
133+readonly property int headerHeight: row.height;
134+Row {
135+ id: row;
136+ objectName: "outerRow";
137+ property real margins: units.gu(1);
138+ spacing: margins;
139+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
140+ anchors { top: artShapeHolder.bottom;
141+ topMargin: units.gu(1);
142+left: parent.left;
143+ }
144+ anchors.right: parent.right;
145+ anchors.margins: margins;
146+ anchors.rightMargin: 0;
147+ data: [
148+ CroppedImageMinimumSourceSize {
149+ id: mascotImage;
150+ objectName: "mascotImage";
151+ anchors { verticalCenter: parent.verticalCenter; }
152+ source: cardData && cardData["mascot"] || decodeURI("%22");
153+ width: units.gu(6);
154+ height: units.gu(5.625);
155+ horizontalAlignment: Image.AlignHCenter;
156+ verticalAlignment: Image.AlignVCenter;
157+ visible: showHeader;
158+ onStatusChanged: if (status === Image.Error) source = decodeURI("%22");
159+ }
160+,Item {
161+ id: headerTitleContainer;
162+ anchors { verticalCenter: parent.verticalCenter; }
163+ width: parent.width - x;
164+ implicitHeight: titleLabel.height + subtitleLabel.height;
165+ data: [
166+ Label {
167+ id: titleLabel;
168+ objectName: "titleLabel";
169+ anchors { right: parent.right;
170+rightMargin: units.gu(1);
171+left: parent.left;
172+ top: parent.top; }
173+ elide: Text.ElideRight;
174+ fontSize: "small";
175+ wrapMode: Text.Wrap;
176+ maximumLineCount: 2;
177+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
178+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
179+ visible: showHeader ;
180+ width: undefined;
181+ text: root.title;
182+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
183+ horizontalAlignment: root.titleAlignment;
184+ }
185+,Label {
186+ id: subtitleLabel;
187+ objectName: "subtitleLabel";
188+ anchors { right: parent.right;
189+ left: parent.left;
190+rightMargin: units.gu(1);
191+top: titleLabel.bottom;
192+ }
193+ anchors.topMargin: units.dp(2);
194+ elide: Text.ElideRight;
195+ maximumLineCount: 1;
196+ fontSize: "x-small";
197+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
198+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
199+ visible: titleLabel.visible && titleLabel.text;
200+ text: cardData && cardData["subtitle"] || "";
201+ font.weight: Font.Light;
202+ }
203+
204+ ]
205+ }
206+
207+ ]
208+ }
209+UbuntuShape {
210+ id: touchdown;
211+ objectName: "touchdown";
212+ anchors { fill: backgroundLoader }
213+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
214+ radius: "medium";
215+ borderSource: "radius_pressed.sci"
216+ }
217+implicitHeight: row.y + row.height + units.gu(1);
218+}
tests/plugins/Dash/cardcreator/11.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,"field":"art","fallback":"\\"},"background":{"field":"background"},"mascot":{"field":"icon","fallback":"\""},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{}}
3+result: 11.res
tests/plugins/Dash/cardcreator/3.res+2−2
@@ -24,7 +24,7 @@ Item {
24 Loader {
24 Loader {
25 id: artShapeLoader;
25 id: artShapeLoader;
26 objectName: "artShapeLoader";
26 objectName: "artShapeLoader";
27− readonly property string cardArt: cardData && cardData["art"] || "IHAVE\"ESCAPED\"QUOTES\"";
27+ readonly property string cardArt: cardData && cardData["art"] || decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
28 active: cardArt != "";
28 active: cardArt != "";
29 asynchronous: root.asynchronous;
29 asynchronous: root.asynchronous;
30 visible: status == Loader.Ready;
30 visible: status == Loader.Ready;
@@ -87,7 +87,7 @@ Item {
87 asynchronous: root.asynchronous;
87 asynchronous: root.asynchronous;
88 width: root.width;
88 width: root.width;
89 height: width / artShape.aspect;
89 height: width / artShape.aspect;
90− onStatusChanged: if (status === Image.Error) source = "IHAVE\"ESCAPED\"QUOTES\"";
90+ onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
91 }
91 }
92 }
92 }
93 }
93 }
tests/plugins/Dash/cardcreator/6.res+1−1
@@ -42,7 +42,7 @@ Loader {
42 objectName: "backgroundImage";
42 objectName: "backgroundImage";
43 source: {
43 source: {
44 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
44 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45− else return "http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png";
45+ else return decodeURI("http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png");
46 }
46 }
47 }
47 }
48 function getColor(index) {
48 function getColor(index) {
tests/plugins/Dash/cardcreatortest.cpp+21−22
@@ -63,6 +63,7 @@ private Q_SLOTS:
63
63
64 QVERIFY(lines[0].startsWith(templateString));
64 QVERIFY(lines[0].startsWith(templateString));
65 QVERIFY(lines[1].startsWith(componentsString));
65 QVERIFY(lines[1].startsWith(componentsString));
66+ QVERIFY(lines[2].startsWith(resultString));
66
67
67 const QString templateJSON = lines[0].mid(templateString.length());
68 const QString templateJSON = lines[0].mid(templateString.length());
68 const QString componentsJSON = lines[1].mid(componentsString.length());
69 const QString componentsJSON = lines[1].mid(componentsString.length());
@@ -71,32 +72,30 @@ private Q_SLOTS:
71 QVariant cardStringResult;
72 QVariant cardStringResult;
72 QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
73 QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
73
74
74− if (!resultFileName.isEmpty()) {
75− QFile testResultFile(testDirPath + resultFileName);
75+ QFile testResultFile(testDirPath + resultFileName);
76− QVERIFY(testResultFile.open(QIODevice::ReadOnly));
76+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77− QTextStream ts2(&testResultFile);
77+ QTextStream ts2(&testResultFile);
78−
78+
79− // Record failed results to /tmp
79+ // Record failed results to /tmp
80− const QString executedResult = cardStringResult.toString();
80+ const QString executedResult = cardStringResult.toString();
81− QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
81+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82− tmpFile.open();
82+ tmpFile.open();
83− tmpFile.setAutoRemove(false);
83+ tmpFile.setAutoRemove(false);
84− tmpFile.write(executedResult.toUtf8().constData());
84+ tmpFile.write(executedResult.toUtf8().constData());
85−
85+
86− // Line by line comparison
86+ // Line by line comparison
87− const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
87+ const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
88− const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
88+ const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
89− for (int i = 0; i < expectedLines.size(); ++i) {
89+ for (int i = 0; i < expectedLines.size(); ++i) {
90− QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
90+ QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
91− }
92−
93− // Remove the result if it passed
94− tmpFile.setAutoRemove(true);
95 }
91 }
96
92
93+ // Remove the result if it passed
94+ tmpFile.setAutoRemove(true);
95+
97 QVariant createCardComponentResult;
96 QVariant createCardComponentResult;
98 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
97 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
99− QCOMPARE(createCardComponentResult.toBool(), !resultFileName.isEmpty());
98+ QVERIFY(createCardComponentResult.toBool());
100 }
99 }
101 }
100 }
102
101