on xenial help sign in
Support fallback images for dash cards and preview widgets
commit14ad122646178f3272e49b85f5011b43949d41aa authorAlbert Astals Cid <albert.astals@canonical.com> dateWed 6 May 2015, 17:05 (11 years ago) parentfac11eb
9 files changed+150−7
plugins/Dash/CardCreator.js+8 qml/Components/ZoomableImage.qml+1 qml/Dash/Previews/PreviewHeader.qml+6 qml/Dash/Previews/PreviewImageGallery.qml+14 qml/Dash/Previews/PreviewZoomableImage.qml+13 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+30 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+21−1 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+27−6 tests/qmltests/Dash/tst_Card.qml+30
plugins/Dash/CardCreator.js+8
@@ -362,6 +362,10 @@ function cardString(template, components) {
362362 }
363363
364364 code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode);
365+ var fallback = components["art"] && components["art"]["fallback"] || "";
366+ if (fallback !== "") {
367+ code += 'Connections { target: artShapeLoader.item ? artShapeLoader.item.image : null; onStatusChanged: if (artShapeLoader.item.image.status === Image.Error) artShapeLoader.item.image.source = "%1"; } \n'.arg(fallback);
368+ }
365369 } else {
366370 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
367371 }
@@ -441,6 +445,10 @@ function cardString(template, components) {
441445
442446 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
443447 mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible);
448+ var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
449+ if (fallback !== "") {
450+ code += 'Connections { target: mascotImage.image; onStatusChanged: if (mascotImage.image.status === Image.Error) mascotImage.source = "%1"; } \n'.arg(fallback);
451+ }
444452 }
445453
446454 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")';
qml/Components/ZoomableImage.qml+1
@@ -30,6 +30,7 @@ Item {
3030 property var zoomable: false
3131 property alias imageStatus: imageRenderer.status
3232 property alias asynchronous: imageRenderer.asynchronous
33+ readonly property alias status: imageRenderer.status
3334
3435 Flickable {
3536 id: flickable
qml/Dash/Previews/PreviewHeader.qml+6
@@ -22,6 +22,7 @@ import "../"
2222/*! This preview widget shows a header
2323 * The title comes in widgetData["title"]
2424 * The mascot comes in widgetData["mascot"]
25+ * The mascot fall back image comes in widgetData["fallback"]
2526 * The subtitle comes in widgetData["subtitle"]
2627 * The attributes comes in widgetData["attributes"]
2728 */
@@ -35,11 +36,15 @@ PreviewWidget {
3536 id: headerRoot
3637 objectName: "innerPreviewHeader"
3738 readonly property url mascot: root.widgetData["mascot"] || ""
39+ readonly property url fallback: root.widgetData["fallback"] || ""
3840 readonly property string title: root.widgetData["title"] || ""
3941 readonly property string subtitle: root.widgetData["subtitle"] || ""
4042 readonly property var attributes: root.widgetData["attributes"] || null
4143 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText
4244
45+ // Rewire the source since we may have unwired it on onStatusChanged
46+ onMascotChanged: if (mascotShapeLoader.item) mascotShapeLoader.item.image.source = mascot;
47+
4348 implicitHeight: row.height + row.margins * 2
4449 width: parent.width
4550
@@ -82,6 +87,7 @@ PreviewWidget {
8287 fillMode: Image.PreserveAspectCrop
8388 horizontalAlignment: Image.AlignHCenter
8489 verticalAlignment: Image.AlignVCenter
90+ onStatusChanged: if (status === Image.Error) source = headerRoot.fallback;
8591 }
8692 }
8793 }
qml/Dash/Previews/PreviewImageGallery.qml+14
@@ -20,6 +20,7 @@ import "../../Components"
2020
2121/*! This preview widget shows a horizontal list of images.
2222 * The URIs for the images should be an array in widgetData["sources"].
23+ * Images fall back to widgetData["fallback"] if loading fails
2324 */
2425
2526PreviewWidget {
@@ -72,6 +73,14 @@ PreviewWidget {
7273 overlay.show();
7374 }
7475 }
76+
77+ Connections {
78+ target: sourceImage
79+ // If modelData would change after failing to load it would not be
80+ // reloaded since the source binding is destroyed by the source = fallback
81+ // But at the moment the model never changes
82+ onStatusChanged: if (sourceImage.status === Image.Error) sourceImage.source = widgetData["fallback"];
83+ }
7584 }
7685 }
7786
@@ -122,6 +131,11 @@ PreviewWidget {
122131 source: modelData ? modelData : ""
123132 fillMode: Image.PreserveAspectFit
124133 sourceSize { width: screenshot.width; height: screenshot.height }
134+
135+ // If modelData would change after failing to load it would not be
136+ // reloaded since the source binding is destroyed by the source = fallback
137+ // But at the moment the model never changes
138+ onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
125139 }
126140
127141 MouseArea {
qml/Dash/Previews/PreviewZoomableImage.qml+13
@@ -21,6 +21,7 @@ import "../../Components"
2121/*! \brief Preview widget for image.
2222
2323 This widget shows image contained in widgetData["source"],
24+ and falls back to widgetData["fallback"] if loading fails
2425 can be zoomable accordingly with widgetData["zoomable"].
2526 */
2627
@@ -53,6 +54,14 @@ PreviewWidget {
5354 overlay.show();
5455 }
5556 }
57+
58+ Connections {
59+ target: lazyImage.sourceImage
60+ // If modelData would change after failing to load it would not be
61+ // reloaded since the source binding is destroyed by the source = fallback
62+ // But at the moment the model never changes
63+ onStatusChanged: if (lazyImage.sourceImage.status === Image.Error) lazyImage.sourceImage.source = widgetData["fallback"];
64+ }
5665 }
5766
5867 PreviewOverlay {
@@ -67,6 +76,10 @@ PreviewWidget {
6776 anchors.fill: parent
6877 source: widgetData["source"]
6978 zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
79+ // If modelData would change after failing to load it would not be
80+ // reloaded since the source binding is destroyed by the source = fallback
81+ // But at the moment the model never changes
82+ onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
7083 }
7184 }
7285}
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+30
@@ -38,6 +38,22 @@ Rectangle {
3838 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3939 }
4040
41+ property var brokenheaderjson: {
42+ "title": "THE TITLE",
43+ "subtitle": "Something catchy",
44+ "mascot": "bad_path",
45+ "fallback": "",
46+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
47+ }
48+
49+ property var fallbackheaderjson: {
50+ "title": "THE TITLE",
51+ "subtitle": "Something catchy",
52+ "mascot": "bad_path2",
53+ "fallback": "../graphics/play_button.png",
54+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
55+ }
56+
4157 PreviewHeader {
4258 id: previewHeader
4359 widgetData: headerjson
@@ -121,5 +137,19 @@ Rectangle {
121137 compare(innerPreviewHeader.subtitle, "Something catchy");
122138 compare(innerPreviewHeader.mascot.toString().slice(-24), "graphics/play_button.png");
123139 }
140+
141+ function test_fallback() {
142+ previewHeader.widgetData = brokenheaderjson;
143+ tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
144+ var mascot = findChild(previewHeader, "mascotShape");
145+ compare(mascot.visible, false);
146+
147+ previewHeader.widgetData = {};
148+ previewHeader.widgetData = fallbackheaderjson;
149+ tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
150+ var mascot = findChild(previewHeader, "mascotShape");
151+ tryCompare(mascot, "visible", true);
152+ tryCompare(mascot.image, "status", Image.Ready);
153+ }
124154 }
125155}
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+21−1
@@ -33,10 +33,21 @@ Rectangle {
3333 "sources": [
3434 "../../graphics/phone_background.jpg",
3535 "../../graphics/tablet_background.jpg",
36− "../../graphics/clock@18.png"
36+ "../../graphics/clock@18.png",
37+ "../../graphics/borked"
3738 ]
3839 }
3940
41+ property var sourcesModel1WithFallback: {
42+ "sources": [
43+ "../../graphics/phone_background.jpg",
44+ "../../graphics/tablet_background.jpg",
45+ "../../graphics/clock@18.png",
46+ "../../graphics/borked"
47+ ]
48+ , "fallback": "../../graphics/clock@18.png"
49+ }
50+
4051 PreviewImageGallery {
4152 id: imageGallery
4253 width: parent.width
@@ -103,5 +114,14 @@ Rectangle {
103114 tryCompare(overlayListView, "currentIndex", data.index);
104115 verify(image.source === overlayListView.currentItem.source);
105116 }
117+
118+ function test_fallback() {
119+ var image3 = findChild(imageGallery, "previewImage3");
120+ tryCompare(image3, "state", "error");
121+ imageGallery.widgetData = sourcesModel0;
122+ imageGallery.widgetData = sourcesModel1WithFallback;
123+ image3 = findChild(imageGallery, "previewImage3");
124+ tryCompare(image3, "state", "ready");
125+ }
106126 }
107127}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+27−6
@@ -26,7 +26,7 @@ Rectangle {
2626 color: "lightgrey"
2727
2828 property var widgetData0: {
29− "source": Qt.resolvedUrl("../artwork/checkers.png"),
29+ "source": "../../graphics/phone_background.jpg",
3030 "zoomable": false
3131 }
3232
@@ -34,21 +34,35 @@ Rectangle {
3434 "source": ""
3535 }
3636
37− PreviewZoomableImage {
38− id: zoomableImage
37+ property var widgetData2: {
38+ "source": "fadsasf",
39+ "fallback": "../../graphics/phone_background.jpg"
40+ }
41+
42+ Loader {
43+ id: loader
3944 anchors.centerIn: parent
40− widgetData: widgetData0
45+ sourceComponent: PreviewZoomableImage {
46+ widgetData: widgetData0
47+ }
4148 }
4249
50+ property alias zoomableImage: loader.item
51+
4352 UT.UnityTestCase {
4453 name: "PreviewZoomableImageTest"
4554 when: windowShown
4655
47− property Item lazyImage: findChild(zoomableImage, "lazyImage");
48− property Item overlay: findChild(zoomableImage.rootItem, "overlay");
56+ property Item lazyImage
57+ property Item overlay
4958
5059 function init() {
60+ // Use a loader so we start from scratch each time
61+ loader.active = false;
62+ loader.active = true;
63+ lazyImage = findChild(zoomableImage, "lazyImage");
5164 waitForRendering(zoomableImage);
65+ overlay = findChild(zoomableImage.rootItem, "overlay");
5266 waitForRendering(overlay);
5367 }
5468
@@ -79,5 +93,12 @@ Rectangle {
7993 mouseClick(overlayCloseButton);
8094 tryCompare(overlay, "visible", false);
8195 }
96+
97+ function test_fallback() {
98+ zoomableImage.widgetData = widgetData2;
99+ waitForRendering(zoomableImage);
100+ waitForRendering(lazyImage);
101+ tryCompare(lazyImage, "state", "ready");
102+ }
82103 }
83104}
tests/qmltests/Dash/tst_Card.qml+30
@@ -224,6 +224,7 @@ Rectangle {
224224 property Item mascotImage: findChild(card, "mascotImage");
225225
226226 function init() {
227+ cardTool.components = Qt.binding(function() { return Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components']; });
227228 loader.visible = true;
228229 }
229230
@@ -424,6 +425,7 @@ Rectangle {
424425 ];
425426 }
426427
428+
427429 function test_background(data) {
428430 selector.selectedIndex = data.index;
429431 waitForRendering(selector);
@@ -454,6 +456,34 @@ Rectangle {
454456 }
455457 }
456458
459+ function test_fallback() {
460+ selector.selectedIndex = 0;
461+ waitForRendering(card);
462+
463+ card.cardData["art"] = "somethingbroken";
464+ card.cardDataChanged();
465+ waitForRendering(card);
466+ tryCompare(art, "visible", false);
467+
468+ cardTool.components["art"]["fallback"] = Qt.resolvedUrl("artwork/emblem.png");
469+ cardTool.componentsChanged();
470+ card.cardData["art"] = "somethingbroken";
471+ card.cardDataChanged();
472+ waitForRendering(card);
473+ tryCompare(art, "visible", true);
474+
475+ card.cardData["mascot"] = "somethingbroken2";
476+ card.cardDataChanged();
477+ compare(mascotImage.image.status, Image.Error);
478+
479+ cardTool.components["mascot"] = {"fallback": Qt.resolvedUrl("artwork/emblem.png")};
480+ cardTool.componentsChanged();
481+ card.cardData["mascot"] = "somethingbroken2";
482+ card.cardDataChanged();
483+ waitForRendering(card);
484+ tryCompare(mascotImage.image, "status", Image.Ready);
485+ }
486+
457487 function test_font_weights_data() {
458488 return [
459489 { tag: "Title only", index: 8, weight: Font.Normal },