on xenial help sign in
Merge trunk
95053d4Josh Arenson
debian/changelog+21
@@ -1,3 +1,24 @@
1+unity8 (8.12+16.04.20160504.2-0ubuntu1) xenial; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Expandable Filter Widget
5+ * Pass the category correctly
6+
7+ [ Albert Astals Cid, CI Train Bot ]
8+ * Make the previewActions react to the actions model changing (LP:
9+ #1485887)
10+ * Reload Preview Zoomable Image source if it gets updated
11+ * Update the icon also to the original one if that's what the scope
12+ wants
13+
14+ [ Andrea Cimitan ]
15+ * Add social actions to cards
16+
17+ [ CI Train Bot ]
18+ * Resync trunk.
19+
20+ -- Pawel Stolowski <ci-train-bot@canonical.com> Wed, 04 May 2016 18:09:33 +0000
21+
1unity8 (8.12+16.04.20160429.2-0ubuntu1) xenial; urgency=medium
22unity8 (8.12+16.04.20160429.2-0ubuntu1) xenial; urgency=medium
2
23
3 [ CI Train Bot ]
24 [ CI Train Bot ]
debian/control+2−2
@@ -30,7 +30,7 @@ Build-Depends: cmake,
30 libqt5xmlpatterns5-dev,
30 libqt5xmlpatterns5-dev,
31 libsystemsettings-dev,
31 libsystemsettings-dev,
32 libudev-dev,
32 libudev-dev,
33− libunity-api-dev (>= 7.110),
33+ libunity-api-dev (>= 7.111),
34 libusermetricsoutput1-dev,
34 libusermetricsoutput1-dev,
35# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
35# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
36 libx11-dev[!armhf],
36 libx11-dev[!armhf],
@@ -136,7 +136,7 @@ Depends: qml-module-qtquick-layouts,
136 unity-application-impl-15,
136 unity-application-impl-15,
137 unity-notifications-impl-3,
137 unity-notifications-impl-3,
138 unity-plugin-scopes | unity-scopes-impl,
138 unity-plugin-scopes | unity-scopes-impl,
139− unity-scopes-impl-11,
139+ unity-scopes-impl-12,
140 unity8-fake-env | unity-application-impl,
140 unity8-fake-env | unity-application-impl,
141 ${misc:Depends},
141 ${misc:Depends},
142Breaks: unity8 (<< 7.86),
142Breaks: unity8 (<< 7.86),
plugins/Dash/CardCreator.js+44−1
@@ -351,6 +351,17 @@ var kAttributesRowCode = 'CardAttributes { \n\
351 model: cardData && cardData["attributes"]; \n\
351 model: cardData && cardData["attributes"]; \n\
352 }\n';
352 }\n';
353
353
354+// %1 is used as anchors of socialActionsRow
355+// %2 is used as color of socialActionsRow
356+var kSocialActionsRowCode = 'CardSocialActions { \n\
357+ id: socialActionsRow; \n\
358+ objectName: "socialActionsRow"; \n\
359+ anchors { %1 } \n\
360+ color: %2; \n\
361+ model: cardData && cardData["socialActions"]; \n\
362+ onClicked: root.action(actionId); \n\
363+ }\n';
364+
354// %1 is used as top anchor of summary
365// %1 is used as top anchor of summary
355// %2 is used as topMargin anchor of summary
366// %2 is used as topMargin anchor of summary
356// %3 is used as color of summary
367// %3 is used as color of summary
@@ -437,9 +448,11 @@ function cardString(template, components, isCardTool) {
437 var hasSubtitle = hasTitle && components["subtitle"] || false;
448 var hasSubtitle = hasTitle && components["subtitle"] || false;
438 var hasHeaderRow = hasMascot && hasTitle;
449 var hasHeaderRow = hasMascot && hasTitle;
439 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
450 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
451+ var hasSocialActions = hasTitle && components["social-actions"] || false;
440 var isAudio = template["quick-preview-type"] === "audio";
452 var isAudio = template["quick-preview-type"] === "audio";
441 var asynchronous = isCardTool ? "false" : "true";
453 var asynchronous = isCardTool ? "false" : "true";
442
454
455+ code += 'signal action(var actionId);\n';
443 if (isAudio) {
456 if (isAudio) {
444 // For now we only support audio cards with [optional] art, title, subtitle
457 // For now we only support audio cards with [optional] art, title, subtitle
445 // in horizontal mode
458 // in horizontal mode
@@ -841,6 +854,34 @@ function cardString(template, components, isCardTool) {
841 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(summaryColor);
854 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(summaryColor);
842 }
855 }
843
856
857+ if (hasSocialActions) {
858+ var socialAnchors;
859+ var socialTopAnchor;
860+
861+ if (hasSummary) socialTopAnchor = 'summary.bottom;';
862+ else if (isHorizontal && hasArt) socialTopAnchor = 'artShapeHolder.bottom;';
863+ else if (headerAsOverlay && hasArt) socialTopAnchor = 'artShapeHolder.bottom;';
864+ else if (hasHeaderRow) socialTopAnchor = 'row.bottom;';
865+ else if (hasTitleContainer) socialTopAnchor = 'headerTitleContainer.bottom;';
866+ else if (hasMascot) socialTopAnchor = 'mascotImage.bottom;';
867+ else if (hasAttributes) socialTopAnchor = 'attributesRow.bottom;';
868+ else if (hasSubtitle) socialTopAnchor = 'subtitleLabel.bottom;';
869+ else if (hasTitle) socialTopAnchor = 'titleLabel.bottom;';
870+ else if (hasArt) socialTopAnchor = 'artShapeHolder.bottom;';
871+ else socialTopAnchor = 'parent.top';
872+
873+ socialAnchors = 'top: ' + socialTopAnchor + ' left: parent.left; right: parent.right; topMargin: units.gu(1);'
874+
875+ var socialColor;
876+ if (hasBackground) {
877+ socialColor = summaryColorWithBackground;
878+ } else {
879+ socialColor = 'root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText';
880+ }
881+
882+ code += kSocialActionsRowCode.arg(socialAnchors).arg(socialColor);
883+ }
884+
844 var touchdownAnchors;
885 var touchdownAnchors;
845 if (hasBackground) {
886 if (hasBackground) {
846 touchdownAnchors = 'fill: backgroundLoader';
887 touchdownAnchors = 'fill: backgroundLoader';
@@ -852,7 +893,9 @@ function cardString(template, components, isCardTool) {
852 code += kTouchdownCode.arg(touchdownAnchors);
893 code += kTouchdownCode.arg(touchdownAnchors);
853
894
854 var implicitHeight = 'implicitHeight: ';
895 var implicitHeight = 'implicitHeight: ';
896+ if (hasSocialActions) {
897+ implicitHeight += 'socialActionsRow.y + socialActionsRow.height + units.gu(1);\n';
855− if (hasSummary) {
898+ } else if (hasSummary) {
856 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
899 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
857 } else if (isAudio) {
900 } else if (isAudio) {
858 implicitHeight += 'audioButton.height;\n';
901 implicitHeight += 'audioButton.height;\n';
plugins/Dash/CardSocialActions.qmlnew file+94
@@ -0,0 +1,94 @@
1+/*
2+ * Copyright 2016 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.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+
21+Column {
22+ id: socialActions
23+ spacing: units.gu(0.5)
24+
25+ property alias model: repeater.model
26+ property color color: theme.palette.normal.baseText
27+
28+ signal clicked(var actionId)
29+
30+ Rectangle {
31+ id: divider
32+ visible: repeater.count > 0
33+ anchors {
34+ left: parent.left;
35+ right: parent.right;
36+ leftMargin:units.dp(1);
37+ rightMargin: units.dp(1);
38+ }
39+ color: Qt.darker(theme.palette.normal.background, 1.12)
40+ height: visible ? units.dp(1) : 0
41+ }
42+
43+ Row {
44+ id: row
45+ anchors {
46+ left: parent.left
47+ right: parent.right
48+ leftMargin: units.gu(1)
49+ rightMargin: units.gu(1)
50+ }
51+ spacing: units.gu(2)
52+ readonly property int visibleItems: {
53+ if (width <= units.gu(12)) // small card
54+ return 2;
55+ else if (width <= units.gu(21)) // medium card
56+ return 3;
57+ else // large or horizontal card
58+ return 4;
59+ }
60+
61+ Repeater {
62+ id: repeater
63+ delegate: Loader {
64+ height: units.gu(2)
65+ active: index < row.visibleItems
66+ sourceComponent: AbstractButton {
67+ objectName: "delegate" + index
68+ height: units.gu(2)
69+ width: icon.width
70+ Icon {
71+ id: icon
72+ objectName: "icon"
73+
74+ readonly property url urlIcon: modelData && modelData["icon"] || ""
75+ readonly property url urlTemporaryIcon: "temporaryIcon" in modelData && modelData["temporaryIcon"] || ""
76+
77+ height: units.gu(2)
78+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
79+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
80+ source: urlIcon
81+ color: socialActions.color
82+
83+ onUrlIconChanged: {
84+ if (urlIcon) source = urlIcon
85+ }
86+ }
87+
88+ onClicked: socialActions.clicked(modelData["id"]);
89+ onPressedChanged: if (pressed && icon.urlTemporaryIcon != "") icon.source = icon.urlTemporaryIcon
90+ }
91+ }
92+ }
93+ }
94+}
plugins/Dash/qmldir+1
@@ -5,6 +5,7 @@ singleton CardCreatorCache 0.1 CardCreatorCache.qml
5singleton DashAudioPlayer 0.1 DashAudioPlayer.qml
5singleton DashAudioPlayer 0.1 DashAudioPlayer.qml
6ScopeStyle 0.1 ScopeStyle.qml
6ScopeStyle 0.1 ScopeStyle.qml
7CardAttributes 0.1 CardAttributes.qml
7CardAttributes 0.1 CardAttributes.qml
8+CardSocialActions 0.1 CardSocialActions.qml
8CroppedImageMinimumSourceSize 0.1 CroppedImageMinimumSourceSize.qml
9CroppedImageMinimumSourceSize 0.1 CroppedImageMinimumSourceSize.qml
9AudioProgressBar 0.1 AudioProgressBar.qml
10AudioProgressBar 0.1 AudioProgressBar.qml
10CardAudioProgress 0.1 CardAudioProgress.qml
11CardAudioProgress 0.1 CardAudioProgress.qml
po/unity8.pot+4−4
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-04-29 20:07+0000\n"
11+"POT-Creation-Date: 2016-05-04 18:09+0000\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -186,15 +186,15 @@ msgstr ""
186msgid "Reset"
186msgid "Reset"
187msgstr ""
187msgstr ""
188
188
189−#: qml/Dash/GenericScopeView.qml:575 qml/Dash/GenericScopeView.qml:760
189+#: qml/Dash/GenericScopeView.qml:579 qml/Dash/GenericScopeView.qml:764
190msgid "Show less"
190msgid "Show less"
191msgstr ""
191msgstr ""
192
192
193−#: qml/Dash/GenericScopeView.qml:575
193+#: qml/Dash/GenericScopeView.qml:579
194msgid "Show all"
194msgid "Show all"
195msgstr ""
195msgstr ""
196
196
197−#: qml/Dash/GenericScopeView.qml:632
197+#: qml/Dash/GenericScopeView.qml:636
198msgctxt "Label: Hint for dash search line edit"
198msgctxt "Label: Hint for dash search line edit"
199msgid "Search"
199msgid "Search"
200msgstr ""
200msgstr ""
qml/Dash/CardGrid.qml+1
@@ -78,6 +78,7 @@ DashRenderer {
78 target: loader.item
78 target: loader.item
79 onClicked: root.clicked(index, result, loader.item, model)
79 onClicked: root.clicked(index, result, loader.item, model)
80 onPressAndHold: root.pressAndHold(index, result, model)
80 onPressAndHold: root.pressAndHold(index, result, model)
81+ onAction: root.action(index, result, actionId)
81 }
82 }
82 }
83 }
83 }
84 }
qml/Dash/CardHorizontalList.qml+1
@@ -59,6 +59,7 @@ DashRenderer {
59 target: loader.item
59 target: loader.item
60 onClicked: root.clicked(index, result, loader.item, model)
60 onClicked: root.clicked(index, result, loader.item, model)
61 onPressAndHold: root.pressAndHold(index, result, model)
61 onPressAndHold: root.pressAndHold(index, result, model)
62+ onAction: root.action(index, result, actionId)
62 }
63 }
63 }
64 }
64 }
65 }
qml/Dash/CardTool.qml+23−7
@@ -162,7 +162,7 @@ Item {
162 id: attributesModel
162 id: attributesModel
163 property int numOfAttributes: 0
163 property int numOfAttributes: 0
164 property var model: []
164 property var model: []
165− property bool hasAttributes: {
165+ readonly property bool hasAttributes: {
166 var attributes = components["attributes"];
166 var attributes = components["attributes"];
167 var hasAttributesFlag = (attributes != undefined) && (attributes["field"] != undefined);
167 var hasAttributesFlag = (attributes != undefined) && (attributes["field"] != undefined);
168
168
@@ -182,16 +182,31 @@ Item {
182 }
182 }
183 }
183 }
184
184
185+ Item {
186+ id: socialActionsModel
187+ property var model: []
188+ readonly property bool hasActions: components["social-actions"] != undefined
189+
190+ onHasActionsChanged: {
191+ model = []
192+ if (hasActions) {
193+ model.push( {"id":"text", "icon":"image://theme/ok" } );
194+ }
195+ }
196+ }
197+
185 Loader {
198 Loader {
186 id: cardLoader
199 id: cardLoader
187− readonly property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
200+ readonly property var cfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "social-actions"]
201+ readonly property var dfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "socialActions"]
188 readonly property var maxData: {
202 readonly property var maxData: {
189 "art": Qt.resolvedUrl("graphics/pixel.png"),
203 "art": Qt.resolvedUrl("graphics/pixel.png"),
190 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
204 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
191 "title": "—\n—",
205 "title": "—\n—",
192 "subtitle": "—",
206 "subtitle": "—",
193 "summary": "—\n—\n—\n—\n—",
207 "summary": "—\n—\n—\n—\n—",
194− "attributes": attributesModel.model
208+ "attributes": attributesModel.model,
209+ "socialActions": socialActionsModel.model
195 }
210 }
196 sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true);
211 sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true);
197 onLoaded: {
212 onLoaded: {
@@ -206,13 +221,14 @@ Item {
206 }
221 }
207 function updateCardData() {
222 function updateCardData() {
208 var data = {};
223 var data = {};
209− for (var k in fields) {
224+ for (var k in cfields) {
225+ var ckey = cfields[k];
210− var component = cardTool.components[fields[k]];
226+ var component = cardTool.components[ckey];
211− var key = fields[k];
212 if ((typeof component === "string" && component.length > 0) ||
227 if ((typeof component === "string" && component.length > 0) ||
213 (typeof component === "object" && component !== null
228 (typeof component === "object" && component !== null
214 && typeof component["field"] === "string" && component["field"].length > 0)) {
229 && typeof component["field"] === "string" && component["field"].length > 0)) {
230+ var dkey = dfields[k];
215− data[key] = maxData[key];
231+ data[dkey] = maxData[dkey];
216 }
232 }
217 }
233 }
218 item.cardData = data;
234 item.cardData = data;
qml/Dash/CardVerticalJournal.qml+1
@@ -78,6 +78,7 @@ DashRenderer {
78 target: loader.item
78 target: loader.item
79 onClicked: root.clicked(index, result, loader.item, model)
79 onClicked: root.clicked(index, result, loader.item, model)
80 onPressAndHold: root.pressAndHold(index, result, model)
80 onPressAndHold: root.pressAndHold(index, result, model)
81+ onAction: root.action(index, result, actionId)
81 }
82 }
82 }
83 }
83 }
84 }
qml/Dash/DashRenderer.qml+6
@@ -57,4 +57,10 @@ Item {
57 /// @param result result model of the clicked item, used for activation
57 /// @param result result model of the clicked item, used for activation
58 /// @param itemModel model of the item
58 /// @param itemModel model of the item
59 signal pressAndHold(int index, var result, var itemModel)
59 signal pressAndHold(int index, var result, var itemModel)
60+
61+ /// Emitted when the user clicked on an item action
62+ /// @param index is the index of the clicked item
63+ /// @param result result model of the clicked item, used for activation
64+ /// @param actionId id of the clicked action
65+ signal action(int index, var result, var actionId)
60}
66}
qml/Dash/Filters/FilterExpandableWidget.qmlnew file+96
@@ -0,0 +1,96 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItems
20+
21+/*! Expandable Filter Widget. */
22+
23+FilterWidget {
24+ id: root
25+
26+ showsTitleOnItsOwn: true
27+
28+ implicitHeight: expandingItem.height
29+
30+ ListItems.Expandable {
31+ id: expandingItem
32+ objectName: "expandingItem"
33+
34+ expandedHeight: collapsedHeight + column.height
35+ anchors.left: parent.left
36+ anchors.right: parent.right
37+ showDivider: false
38+
39+ onClicked: {
40+ expanded = !expanded;
41+ forceActiveFocus();
42+ }
43+ __contentsMargins: 0
44+
45+ Item {
46+ id: titleHolder
47+ anchors.top: parent.top
48+ height: expandingItem.collapsedHeight
49+ anchors.left: parent.left
50+ anchors.right: parent.right
51+
52+ Label {
53+ anchors.left: parent.left
54+ anchors.leftMargin: units.gu(2)
55+ anchors.right: dropDown.left
56+ anchors.verticalCenter: parent.verticalCenter
57+ text: widgetData.title || ""
58+ }
59+
60+ Image {
61+ id: dropDown
62+ height: units.gu(3)
63+ fillMode: Image.PreserveAspectFit
64+ anchors.right: parent.right
65+ anchors.rightMargin: units.gu(2)
66+ anchors.verticalCenter: parent.verticalCenter
67+ source: expandingItem.expanded ? "image://theme/up" : "image://theme/down"
68+ }
69+ }
70+
71+ Column {
72+ id: column
73+ anchors.left: parent.left
74+ anchors.right: parent.right
75+ anchors.top: titleHolder.bottom
76+
77+ Repeater {
78+ id: repeater
79+ model: widgetData.filters
80+
81+ delegate: FilterWidgetFactory {
82+ width: parent.width
83+
84+ widgetId: id
85+ widgetType: type
86+ widgetData: filter
87+
88+ ListItems.ThinDivider {
89+ anchors.bottom: parent.bottom
90+ visible: index !== repeater.count - 1
91+ }
92+ }
93+ }
94+ }
95+ }
96+}
qml/Dash/Filters/FilterWidget.qml+3
@@ -25,5 +25,8 @@ Item {
25 //! Variable used to contain widget's data
25 //! Variable used to contain widget's data
26 property var widgetData: null
26 property var widgetData: null
27
27
28+ //! Does the widget show the title inside?
29+ property bool showsTitleOnItsOwn: false
30+
28 objectName: widgetId
31 objectName: widgetId
29}
32}
qml/Dash/Filters/FilterWidgetFactory.qml+2−1
@@ -35,7 +35,7 @@ Item {
35
35
36 Label {
36 Label {
37 id: title
37 id: title
38− text: widgetData ? widgetData.title : ""
38+ text: widgetData && !(loader.item && loader.item.showsTitleOnItsOwn) ? widgetData.title : ""
39 height: text != "" ? implicitHeight : 0
39 height: text != "" ? implicitHeight : 0
40
40
41 anchors {
41 anchors {
@@ -68,6 +68,7 @@ Item {
68 case Filters.OptionSelectorFilter: return "FilterOptionSelector.qml";
68 case Filters.OptionSelectorFilter: return "FilterOptionSelector.qml";
69 case Filters.RangeInputFilter: return "FilterRangeInput.qml";
69 case Filters.RangeInputFilter: return "FilterRangeInput.qml";
70 case Filters.ValueSliderFilter: return "FilterValueSlider.qml";
70 case Filters.ValueSliderFilter: return "FilterValueSlider.qml";
71+ case Filters.ExpandableFilterWidget: return "FilterExpandableWidget.qml";
71 default: return "";
72 default: return "";
72 }
73 }
73 }
74 }
qml/Dash/GenericScopeView.qml+4
@@ -395,6 +395,10 @@ FocusScope {
395 scopeView.itemPressedAndHeld(result, baseItem.category);
395 scopeView.itemPressedAndHeld(result, baseItem.category);
396 }
396 }
397
397
398+ onAction: { // (int index, var result, var actionId)
399+ scope.activateAction(result, baseItem.category, actionId);
400+ }
401+
398 function categoryItemCount() {
402 function categoryItemCount() {
399 var categoryItemCount = -1;
403 var categoryItemCount = -1;
400 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
404 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
qml/Dash/Previews/PreviewActionCombo.qml+1−1
@@ -59,7 +59,7 @@ Item {
59
59
60 delegate: PreviewActionButton {
60 delegate: PreviewActionButton {
61 data: modelData
61 data: modelData
62− width: implicitWidth < parent.width ? parent.width : implicitWidth
62+ width: implicitWidth < root.width ? root.width : implicitWidth
63 onClicked: root.triggeredAction(modelData)
63 onClicked: root.triggeredAction(modelData)
64 strokeColor: moreButton.strokeColor
64 strokeColor: moreButton.strokeColor
65 }
65 }
qml/Dash/Previews/PreviewActions.qml+8−7
@@ -26,25 +26,26 @@ PreviewWidget {
26 id: root
26 id: root
27
27
28 implicitHeight: row.height + units.gu(1)
28 implicitHeight: row.height + units.gu(1)
29+ readonly property var actions: root.widgetData ? root.widgetData["actions"] : null
29
30
30 Row {
31 Row {
31 id: row
32 id: row
32− readonly property var actions: root.widgetData ? root.widgetData["actions"] : null
33 anchors.right: parent.right
33 anchors.right: parent.right
34
34
35 spacing: units.gu(1)
35 spacing: units.gu(1)
36
36
37 Loader {
37 Loader {
38 id: loader
38 id: loader
39+ objectName: "loader"
39− readonly property bool button: row.actions && row.actions.length == 2
40+ readonly property bool button: root.actions && root.actions.length == 2
40− readonly property bool combo: row.actions && row.actions.length > 2
41+ readonly property bool combo: root.actions && root.actions.length > 2
41 source: button ? "PreviewActionButton.qml" : (combo ? "PreviewActionCombo.qml" : "")
42 source: button ? "PreviewActionButton.qml" : (combo ? "PreviewActionCombo.qml" : "")
42 width: (root.width - units.gu(1)) / 2
43 width: (root.width - units.gu(1)) / 2
43 onLoaded: {
44 onLoaded: {
44 if (button) {
45 if (button) {
45− item.data = row.actions[1];
46+ item.data = Qt.binding(function() { return root.actions[1]; });
46 } else if (combo) {
47 } else if (combo) {
47− item.model = row.actions.slice(1);
48+ item.model = Qt.binding(function() { return root.actions.slice(1); });
48 }
49 }
49 }
50 }
50 Binding {
51 Binding {
@@ -61,8 +62,8 @@ PreviewWidget {
61 }
62 }
62
63
63 PreviewActionButton {
64 PreviewActionButton {
64− data: visible ? row.actions[0] : null
65+ data: visible ? root.actions[0] : null
65− visible: row.actions && row.actions.length > 0
66+ visible: root.actions && root.actions.length > 0
66 onTriggeredAction: root.triggered(root.widgetId, actionData.id, actionData)
67 onTriggeredAction: root.triggered(root.widgetId, actionData.id, actionData)
67 width: (root.width - units.gu(1)) / 2
68 width: (root.width - units.gu(1)) / 2
68 color: root.scopeStyle ? root.scopeStyle.previewButtonColor : UbuntuColors.orange
69 color: root.scopeStyle ? root.scopeStyle.previewButtonColor : UbuntuColors.orange
qml/Dash/Previews/PreviewIconActions.qml+15
@@ -32,10 +32,20 @@ PreviewWidget {
32 id: row
32 id: row
33 readonly property var actions: root.widgetData ? root.widgetData["actions"] : null
33 readonly property var actions: root.widgetData ? root.widgetData["actions"] : null
34 width: parent.width
34 width: parent.width
35+ onActionsChanged: {
36+ // Rewire the source since it may have been unwired on onClicked
37+ // but the icon didn't change i.e. the scope updated the results
38+ // and the icon needs to go back to the one it was originally
39+ for (var i = 0; i < repeater.count; ++i) {
40+ var button = repeater.itemAt(i);
41+ button.updateIcon();
42+ }
43+ }
35
44
36 spacing: units.gu(2)
45 spacing: units.gu(2)
37
46
38 Repeater {
47 Repeater {
48+ id: repeater
39 model: row.actions
49 model: row.actions
40
50
41 AbstractButton {
51 AbstractButton {
@@ -43,6 +53,11 @@ PreviewWidget {
43 height: label.height
53 height: label.height
44 width: childrenRect.width
54 width: childrenRect.width
45
55
56+ readonly property var iconUrl: modelData.icon;
57+ function updateIcon() {
58+ icon.source = iconUrl;
59+ }
60+
46 Image {
61 Image {
47 id: icon
62 id: icon
48 height: parent.height
63 height: parent.height
qml/Dash/Previews/PreviewZoomableImage.qml+13−8
@@ -34,6 +34,8 @@ PreviewWidget {
34
34
35 property Item rootItem: QuickUtils.rootItem(root)
35 property Item rootItem: QuickUtils.rootItem(root)
36
36
37+ readonly property var imageUrl: widgetData["source"] || widgetData["fallback"] || ""
38+
37 LazyImage {
39 LazyImage {
38 id: lazyImage
40 id: lazyImage
39 objectName: "lazyImage"
41 objectName: "lazyImage"
@@ -42,7 +44,7 @@ PreviewWidget {
42 right: parent.right
44 right: parent.right
43 }
45 }
44 scaleTo: "width"
46 scaleTo: "width"
45− source: widgetData["source"] || widgetData["fallback"] || ""
47+ source: root.imageUrl
46 asynchronous: true
48 asynchronous: true
47 useUbuntuShape: false
49 useUbuntuShape: false
48 pressed: mouseArea.pressed
50 pressed: mouseArea.pressed
@@ -59,11 +61,12 @@ PreviewWidget {
59
61
60 Connections {
62 Connections {
61 target: lazyImage.sourceImage
63 target: lazyImage.sourceImage
62− // If modelData would change after failing to load it would not be
63− // reloaded since the source binding is destroyed by the source = fallback
64− // But at the moment the model never changes
65 onStatusChanged: if (lazyImage.sourceImage.status === Image.Error) lazyImage.sourceImage.source = widgetData["fallback"];
64 onStatusChanged: if (lazyImage.sourceImage.status === Image.Error) lazyImage.sourceImage.source = widgetData["fallback"];
66 }
65 }
66+ Connections {
67+ target: root
68+ onImageUrlChanged: lazyImage.sourceImage.source = root.imageUrl;
69+ }
67
70
68 PreviewMediaToolbar {
71 PreviewMediaToolbar {
69 id: toolbar
72 id: toolbar
@@ -86,12 +89,14 @@ PreviewWidget {
86
89
87 delegate: ZoomableImage {
90 delegate: ZoomableImage {
88 anchors.fill: parent
91 anchors.fill: parent
89− source: widgetData["source"] || widgetData["fallback"] || ""
92+ source: root.imageUrl
90 zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
93 zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
91− // If modelData would change after failing to load it would not be
92− // reloaded since the source binding is destroyed by the source = fallback
93− // But at the moment the model never changes
94 onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
94 onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
95+
96+ Connections {
97+ target: root
98+ onImageUrlChanged: source = root.imageUrl;
99+ }
95 }
100 }
96 }
101 }
97}
102}
tests/mocks/Unity/CMakeLists.txt+3−1
@@ -8,7 +8,7 @@ add_subdirectory(DashCommunicator)
8add_subdirectory(Screens)
8add_subdirectory(Screens)
9
9
10pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
10pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
11−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=11)
11+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=12)
12
12
13include_directories(
13include_directories(
14 ${CMAKE_CURRENT_BINARY_DIR}
14 ${CMAKE_CURRENT_BINARY_DIR}
@@ -34,6 +34,7 @@ set(UnityQML_SOURCES
34 fake_optionselectoroptions.cpp
34 fake_optionselectoroptions.cpp
35 fake_rangeinputfilter.cpp
35 fake_rangeinputfilter.cpp
36 fake_valuesliderfilter.cpp
36 fake_valuesliderfilter.cpp
37+ fake_expandablefilterwidget.cpp
37 fake_valueslidervalues.cpp
38 fake_valueslidervalues.cpp
38 fake_unity_plugin.cpp
39 fake_unity_plugin.cpp
39 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
40 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
@@ -51,6 +52,7 @@ set(UnityQML_SOURCES
51 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/SettingsModelInterface.h
52 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/SettingsModelInterface.h
52 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderFilterInterface.h
53 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderFilterInterface.h
53 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderValuesInterface.h
54 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderValuesInterface.h
55+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ExpandableFilterWidgetInterface.h
54)
56)
55
57
56# Workaround for gcc failure LP: #1417664
58# Workaround for gcc failure LP: #1417664
tests/mocks/Unity/fake_categories.cpp+4−1
@@ -88,7 +88,7 @@ Categories::data(const QModelIndex& index, int role) const
88 }
88 }
89 case RoleComponents:
89 case RoleComponents:
90 {
90 {
91− QVariantMap map, artMap, attributeMap;
91+ QVariantMap map, artMap, attributeMap, socialActionMap;
92 if (index.row() % 2 != 0) {
92 if (index.row() % 2 != 0) {
93 artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
93 artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
94 } else {
94 } else {
@@ -99,6 +99,9 @@ Categories::data(const QModelIndex& index, int role) const
99 map["title"] = "HOLA";
99 map["title"] = "HOLA";
100 map["subtitle"] = "HOLA";
100 map["subtitle"] = "HOLA";
101 attributeMap["field"] = "attribute";
101 attributeMap["field"] = "attribute";
102+ socialActionMap["icon"] = "icon";
103+ socialActionMap["id"] = "socialHola";
104+ map["socialActions"] = socialActionMap;
102 map["attributes"] = attributeMap;
105 map["attributes"] = attributeMap;
103 return map;
106 return map;
104 }
107 }
tests/mocks/Unity/fake_expandablefilterwidget.cppnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2016 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 "fake_expandablefilterwidget.h"
18+
19+#include "fake_filters.h"
20+
21+FakeExpandbleFilterWidget::FakeExpandbleFilterWidget(const QString &id, const QString &tag, const QString &title, QObject* parent)
22+ : unity::shell::scopes::ExpandableFilterWidgetInterface(parent)
23+ , m_filterId(id)
24+ , m_filterTag(tag)
25+ , m_title(title)
26+{
27+ m_filters = new Filters(this);
28+}
29+
30+QString FakeExpandbleFilterWidget::filterId() const
31+{
32+ return m_filterId;
33+}
34+
35+QString FakeExpandbleFilterWidget::filterTag() const
36+{
37+ return m_filterTag;
38+}
39+
40+QString FakeExpandbleFilterWidget::title() const
41+{
42+ return m_title;
43+}
44+
45+unity::shell::scopes::FiltersInterface* FakeExpandbleFilterWidget::filters() const
46+{
47+ return m_filters;
48+}
49+
50+void FakeExpandbleFilterWidget::addFilter(unity::shell::scopes::FilterBaseInterface* f)
51+{
52+ m_filters->addFilter(f);
53+}
tests/mocks/Unity/fake_expandablefilterwidget.hnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef FAKE_EXPANDABLEFILTERWIDGET_H
18+#define FAKE_EXPANDABLEFILTERWIDGET_H
19+
20+#include <unity/shell/scopes/ExpandableFilterWidgetInterface.h>
21+
22+class Filters;
23+
24+class FakeExpandbleFilterWidget : public unity::shell::scopes::ExpandableFilterWidgetInterface
25+{
26+ Q_OBJECT
27+
28+public:
29+ FakeExpandbleFilterWidget(const QString &id, const QString &tag, const QString &title, QObject* parent);
30+
31+ QString filterId() const override;
32+ QString filterTag() const override;
33+ QString title() const override;
34+
35+ unity::shell::scopes::FiltersInterface* filters() const override;
36+
37+ // mock API
38+ void addFilter(unity::shell::scopes::FilterBaseInterface* f);
39+
40+private:
41+ QString m_filterId;
42+ QString m_filterTag;
43+ QString m_title;
44+
45+ Filters *m_filters;
46+};
47+
48+#endif
tests/mocks/Unity/fake_filters.cpp+19−8
@@ -19,10 +19,14 @@
19#include "fake_optionselectorfilter.h"
19#include "fake_optionselectorfilter.h"
20#include "fake_rangeinputfilter.h"
20#include "fake_rangeinputfilter.h"
21#include "fake_valuesliderfilter.h"
21#include "fake_valuesliderfilter.h"
22−#include "fake_scope.h"
22+#include "fake_expandablefilterwidget.h"
23
23
24−Filters::Filters(Scope* parent)
24+Filters::Filters(QObject* parent)
25 : unity::shell::scopes::FiltersInterface(parent)
25 : unity::shell::scopes::FiltersInterface(parent)
26+{
27+}
28+
29+void Filters::addFakeFilters()
26{
30{
27 addFilter(new FakeOptionSelectorFilter("OSF1", "Tag1", "Which Cake you like More", false, QStringList() << "cheese" << "carrot" << "chocolate", this));
31 addFilter(new FakeOptionSelectorFilter("OSF1", "Tag1", "Which Cake you like More", false, QStringList() << "cheese" << "carrot" << "chocolate", this));
28
32
@@ -41,6 +45,11 @@ Filters::Filters(Scope* parent)
41 addFilter(vsf);
45 addFilter(vsf);
42
46
43 addFilter(new FakeOptionSelectorFilter("OSF2", "Tag2", "Which Countries have you been to?", true, QStringList() << "Germany" << "UK" << "New Zealand", this));
47 addFilter(new FakeOptionSelectorFilter("OSF2", "Tag2", "Which Countries have you been to?", true, QStringList() << "Germany" << "UK" << "New Zealand", this));
48+
49+ auto expandableFilter = new FakeExpandbleFilterWidget("EFW", "Tag5", "Advanced Settings", this);
50+ expandableFilter->addFilter(new FakeValueSliderFilter("VS2", "Tag6", 15, 10, 150, labels, this));
51+
52+ addFilter(expandableFilter);
44}
53}
45
54
46void Filters::addFilter(unity::shell::scopes::FilterBaseInterface *f)
55void Filters::addFilter(unity::shell::scopes::FilterBaseInterface *f)
@@ -58,9 +67,10 @@ void Filters::addFilter(unity::shell::scopes::FilterBaseInterface *f)
58 }
67 }
59 break;
68 break;
60
69
61− case FiltersInterface::ValueSliderFilter: {
70+ case FiltersInterface::ExpandableFilterWidget:
71+ // Can't be active
72+ case FiltersInterface::ValueSliderFilter:
62− // Not counting value slider as active in the mock
73+ // Not counting value as active in the mock
63− }
64
74
65 case Invalid:
75 case Invalid:
66 break;
76 break;
@@ -113,9 +123,10 @@ int Filters::activeFiltersCount() const
113 }
123 }
114 break;
124 break;
115
125
116− case FiltersInterface::ValueSliderFilter: {
126+ case FiltersInterface::ExpandableFilterWidget:
127+ // Can't be active
128+ case FiltersInterface::ValueSliderFilter:
117− // Not counting value slider as active in the mock
129+ // Not counting value as active in the mock
118− }
119
130
120 case Invalid:
131 case Invalid:
121 break;
132 break;
tests/mocks/Unity/fake_filters.h+4−4
@@ -20,25 +20,25 @@
20#include <unity/shell/scopes/FiltersInterface.h>
20#include <unity/shell/scopes/FiltersInterface.h>
21#include <unity/shell/scopes/FilterBaseInterface.h>
21#include <unity/shell/scopes/FilterBaseInterface.h>
22
22
23−class Scope;
24−
25class Filters : public unity::shell::scopes::FiltersInterface
23class Filters : public unity::shell::scopes::FiltersInterface
26{
24{
27 Q_OBJECT
25 Q_OBJECT
28
26
29public:
27public:
30− Filters(Scope* parent);
28+ Filters(QObject* parent);
31
29
32 int rowCount(const QModelIndex &parent) const override;
30 int rowCount(const QModelIndex &parent) const override;
33 QVariant data(const QModelIndex &index, int role) const override;
31 QVariant data(const QModelIndex &index, int role) const override;
34
32
33+ // Mock API
35 int activeFiltersCount() const;
34 int activeFiltersCount() const;
35+ void addFakeFilters();
36+ void addFilter(unity::shell::scopes::FilterBaseInterface* f);
36
37
37Q_SIGNALS:
38Q_SIGNALS:
38 void activeFiltersCountChanged();
39 void activeFiltersCountChanged();
39
40
40private:
41private:
41− void addFilter(unity::shell::scopes::FilterBaseInterface* f);
42
42
43 QVector<unity::shell::scopes::FilterBaseInterface*> m_filters;
43 QVector<unity::shell::scopes::FilterBaseInterface*> m_filters;
44};
44};
tests/mocks/Unity/fake_resultsmodel.cpp+2
@@ -72,6 +72,8 @@ ResultsModel::data(const QModelIndex& index, int role) const
72 case RoleUri:
72 case RoleUri:
73 case RoleCategoryId:
73 case RoleCategoryId:
74 case RoleDndUri:
74 case RoleDndUri:
75+ case RoleAttributes:
76+ case RoleSocialActions:
75 return QString();
77 return QString();
76 case RoleResult:
78 case RoleResult:
77 return QString("Result.%1.%2").arg(m_categoryId).arg(index.row());
79 return QString("Result.%1.%2").arg(m_categoryId).arg(index.row());
tests/mocks/Unity/fake_scope.cpp+1
@@ -50,6 +50,7 @@ Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* pare
50 , m_returnNullPreview(returnNullPreview)
50 , m_returnNullPreview(returnNullPreview)
51{
51{
52 m_primaryNavigationFilter = new FakeOptionSelectorFilter("OSF3", "PFTag", "Which food you like More", false, primaryNavigationFilterOptionLabels, this);
52 m_primaryNavigationFilter = new FakeOptionSelectorFilter("OSF3", "PFTag", "Which food you like More", false, primaryNavigationFilterOptionLabels, this);
53+ m_filters->addFakeFilters();
53 connect(m_filters, &Filters::activeFiltersCountChanged, this, &Scope::activeFiltersCountChanged);
54 connect(m_filters, &Filters::activeFiltersCountChanged, this, &Scope::activeFiltersCountChanged);
54}
55}
55
56
tests/plugins/Dash/cardcreator/1.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
16Item {
16Item {
17 id: artShapeHolder;
17 id: artShapeHolder;
tests/plugins/Dash/cardcreator/1.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15Item {
15Item {
16 id: artShapeHolder;
16 id: artShapeHolder;
tests/plugins/Dash/cardcreator/10.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/10.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/11.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/11.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/12.resnew file+139
@@ -0,0 +1,139 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool showHeader: true;
12+ implicitWidth: childrenRect.width;
13+ enabled: true;
14+signal action(var actionId);
15+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
16+Item {
17+ id: artShapeHolder;
18+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
19+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
20+ anchors { horizontalCenter: parent.horizontalCenter; }
21+ Loader {
22+ id: artShapeLoader;
23+ objectName: "artShapeLoader";
24+ readonly property string cardArt: cardData && cardData["art"] || "";
25+ active: cardArt != "";
26+ asynchronous: true;
27+ visible: status == Loader.Ready;
28+ sourceComponent: Item {
29+ id: artShape;
30+ objectName: "artShape";
31+ visible: image.status == Image.Ready;
32+ readonly property alias image: artImage;
33+ Loader {
34+ anchors.fill: parent;
35+ visible: true;
36+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
37+ Component {
38+ id: artShapeShapeComponent;
39+ UbuntuShape {
40+ source: artImage;
41+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
42+ radius: "medium";
43+ aspect: {
44+ switch (root.artShapeStyle) {
45+ case "inset": return UbuntuShape.Inset;
46+ case "shadow": return UbuntuShape.DropShadow;
47+ default:
48+ case "flat": return UbuntuShape.Flat;
49+ }
50+ }
51+ }
52+ }
53+ Component {
54+ id: artShapeIconComponent;
55+ ProportionalShape { source: artImage; aspect: UbuntuShape.DropShadow; }
56+ }
57+ }
58+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
59+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 0.75;
60+ Component.onCompleted: { updateWidthHeightBindings(); }
61+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
62+ function updateWidthHeightBindings() {
63+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
64+ width = root.fixedArtShapeSize.width;
65+ height = root.fixedArtShapeSize.height;
66+ } else {
67+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
68+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
69+ }
70+ }
71+ CroppedImageMinimumSourceSize {
72+ id: artImage;
73+ objectName: "artImage";
74+ source: artShapeLoader.cardArt;
75+ asynchronous: true;
76+ visible: !true;
77+ width: root.width;
78+ height: width / artShape.aspect;
79+ }
80+ }
81+ }
82+ }
83+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
84+Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { right: parent.right;
88+ left: parent.left;
89+ top: artShapeHolder.bottom;
90+ topMargin: units.gu(1);
91+ }
92+ elide: Text.ElideRight;
93+ fontSize: "small";
94+ wrapMode: Text.Wrap;
95+ maximumLineCount: 2;
96+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
98+ visible: showHeader ;
99+ width: undefined;
100+ text: root.title;
101+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102+ horizontalAlignment: Text.AlignLeft;
103+ }
104+Label {
105+ id: subtitleLabel;
106+ objectName: "subtitleLabel";
107+ anchors { left: titleLabel.left;
108+ leftMargin: titleLabel.leftMargin;
109+ right: titleLabel.right;
110+ top: titleLabel.bottom;
111+ }
112+ anchors.topMargin: units.dp(2);
113+ elide: Text.ElideRight;
114+ maximumLineCount: 1;
115+ fontSize: "x-small";
116+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
117+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
118+ visible: titleLabel.visible && titleLabel.text;
119+ text: cardData && cardData["subtitle"] || "";
120+ font.weight: Font.Light;
121+ }
122+CardSocialActions {
123+ id: socialActionsRow;
124+ objectName: "socialActionsRow";
125+ anchors { top: subtitleLabel.bottom; left: parent.left; right: parent.right; topMargin: units.gu(1); }
126+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
127+ model: cardData && cardData["socialActions"];
128+ onClicked: root.action(actionId);
129+}
130+UbuntuShape {
131+ id: touchdown;
132+ objectName: "touchdown";
133+ anchors { fill: artShapeHolder }
134+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
135+ radius: "medium";
136+ borderSource: "radius_pressed.sci"
137+}
138+implicitHeight: socialActionsRow.y + socialActionsRow.height + units.gu(1);
139+}
tests/plugins/Dash/cardcreator/12.res.cardcreatornew file+138
@@ -0,0 +1,138 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+signal action(var actionId);
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 > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ Loader {
33+ anchors.fill: parent;
34+ visible: true;
35+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
36+ Component {
37+ id: artShapeShapeComponent;
38+ UbuntuShape {
39+ source: artImage;
40+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
41+ radius: "medium";
42+ aspect: {
43+ switch (root.artShapeStyle) {
44+ case "inset": return UbuntuShape.Inset;
45+ case "shadow": return UbuntuShape.DropShadow;
46+ default:
47+ case "flat": return UbuntuShape.Flat;
48+ }
49+ }
50+ }
51+ }
52+ Component {
53+ id: artShapeIconComponent;
54+ ProportionalShape { source: artImage; aspect: UbuntuShape.DropShadow; }
55+ }
56+ }
57+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
58+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 0.75;
59+ Component.onCompleted: { updateWidthHeightBindings(); }
60+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
61+ function updateWidthHeightBindings() {
62+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
63+ width = root.fixedArtShapeSize.width;
64+ height = root.fixedArtShapeSize.height;
65+ } else {
66+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
67+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
68+ }
69+ }
70+ CroppedImageMinimumSourceSize {
71+ id: artImage;
72+ objectName: "artImage";
73+ source: artShapeLoader.cardArt;
74+ asynchronous: false;
75+ visible: !true;
76+ width: root.width;
77+ height: width / artShape.aspect;
78+ }
79+ }
80+ }
81+ }
82+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
83+Label {
84+ id: titleLabel;
85+ objectName: "titleLabel";
86+ anchors { right: parent.right;
87+ left: parent.left;
88+ top: artShapeHolder.bottom;
89+ topMargin: units.gu(1);
90+ }
91+ elide: Text.ElideRight;
92+ fontSize: "small";
93+ wrapMode: Text.Wrap;
94+ maximumLineCount: 2;
95+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
96+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
97+ visible: showHeader ;
98+ width: undefined;
99+ text: root.title;
100+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
101+ horizontalAlignment: Text.AlignLeft;
102+ }
103+Label {
104+ id: subtitleLabel;
105+ objectName: "subtitleLabel";
106+ anchors { left: titleLabel.left;
107+ leftMargin: titleLabel.leftMargin;
108+ right: titleLabel.right;
109+ top: titleLabel.bottom;
110+ }
111+ anchors.topMargin: units.dp(2);
112+ elide: Text.ElideRight;
113+ maximumLineCount: 1;
114+ fontSize: "x-small";
115+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
116+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
117+ visible: titleLabel.visible && titleLabel.text;
118+ text: cardData && cardData["subtitle"] || "";
119+ font.weight: Font.Light;
120+ }
121+CardSocialActions {
122+ id: socialActionsRow;
123+ objectName: "socialActionsRow";
124+ anchors { top: subtitleLabel.bottom; left: parent.left; right: parent.right; topMargin: units.gu(1); }
125+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
126+ model: cardData && cardData["socialActions"];
127+ onClicked: root.action(actionId);
128+}
129+UbuntuShape {
130+ id: touchdown;
131+ objectName: "touchdown";
132+ anchors { fill: artShapeHolder }
133+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
134+ radius: "medium";
135+ borderSource: "radius_pressed.sci"
136+}
137+implicitHeight: socialActionsRow.y + socialActionsRow.height + units.gu(1);
138+}
tests/plugins/Dash/cardcreator/12.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"},"subtitle":{"field":"price"},"title":{"field":"title"},"attributes":{},"social-actions":{}}
3+result: 12.res
tests/plugins/Dash/cardcreator/2.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/2.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/3.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
16Item {
16Item {
17 id: artShapeHolder;
17 id: artShapeHolder;
tests/plugins/Dash/cardcreator/3.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15Item {
15Item {
16 id: artShapeHolder;
16 id: artShapeHolder;
tests/plugins/Dash/cardcreator/4.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15readonly property size artShapeSize: Qt.size(-1, -1);
15readonly property size artShapeSize: Qt.size(-1, -1);
16readonly property int headerHeight: row.height;
16readonly property int headerHeight: row.height;
17Row {
17Row {
tests/plugins/Dash/cardcreator/4.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14readonly property size artShapeSize: Qt.size(-1, -1);
14readonly property size artShapeSize: Qt.size(-1, -1);
15readonly property int headerHeight: row.height;
15readonly property int headerHeight: row.height;
16Row {
16Row {
tests/plugins/Dash/cardcreator/5.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: false;
13 enabled: false;
14−
14+signal action(var actionId);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
16Item {
16Item {
17 id: artShapeHolder;
17 id: artShapeHolder;
tests/plugins/Dash/cardcreator/5.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: false;
12 enabled: false;
13−
13+signal action(var actionId);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
14readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15Item {
15Item {
16 id: artShapeHolder;
16 id: artShapeHolder;
tests/plugins/Dash/cardcreator/6.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/6.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/7.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/7.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/8.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15Loader {
15Loader {
16 id: backgroundLoader;
16 id: backgroundLoader;
17 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/8.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14Loader {
14Loader {
15 id: backgroundLoader;
15 id: backgroundLoader;
16 objectName: "backgroundLoader";
16 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/9.res+1−1
@@ -11,7 +11,7 @@ AbstractButton {
11 property bool showHeader: true;
11 property bool showHeader: true;
12 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
13 enabled: true;
13 enabled: true;
14−
14+signal action(var actionId);
15readonly property size artShapeSize: Qt.size(-1, -1);
15readonly property size artShapeSize: Qt.size(-1, -1);
16readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
16readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
17Label {
17Label {
tests/plugins/Dash/cardcreator/9.res.cardcreator+1−1
@@ -10,7 +10,7 @@ AbstractButton {
10 property bool showHeader: true;
10 property bool showHeader: true;
11 implicitWidth: childrenRect.width;
11 implicitWidth: childrenRect.width;
12 enabled: true;
12 enabled: true;
13−
13+signal action(var actionId);
14readonly property size artShapeSize: Qt.size(-1, -1);
14readonly property size artShapeSize: Qt.size(-1, -1);
15readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
15readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
16Label {
16Label {
tests/qmltests/CMakeLists.txt+1
@@ -57,6 +57,7 @@ add_unity8_qmltest(Dash/Filters FilterOptionSelector)
57add_unity8_qmltest(Dash/Filters FilterRangeInput)
57add_unity8_qmltest(Dash/Filters FilterRangeInput)
58add_unity8_qmltest(Dash/Filters FilterValueSlider)
58add_unity8_qmltest(Dash/Filters FilterValueSlider)
59add_unity8_qmltest(Dash/Filters FilterWidgetFactory)
59add_unity8_qmltest(Dash/Filters FilterWidgetFactory)
60+add_unity8_qmltest(Dash/Filters FilterExpandableWidget)
60add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
61add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
61add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
62add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
62add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
63add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
tests/qmltests/Dash/CardHelpers.js+9−7
@@ -16,7 +16,8 @@
16
16
17.pragma library
17.pragma library
18
18
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "overlayColor", "quickPreviewData"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "social-actions", "overlayColor", "quickPreviewData"]
20+var dataKeys = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "socialActions", "overlayColor", "quickPreviewData"]
20
21
21var defaultLayout = ' \
22var defaultLayout = ' \
22{ \
23{ \
@@ -38,7 +39,8 @@ var defaultLayout = ' \
38 "mascot": null, \
39 "mascot": null, \
39 "emblem": null, \
40 "emblem": null, \
40 "summary": null, \
41 "summary": null, \
41− "attributes": { "max-count": 2 } \
42+ "attributes": { "max-count": 2 }, \
43+ "social-actions": null \
42 }, \
44 }, \
43 "resources": {} \
45 "resources": {} \
44}'
46}'
@@ -52,10 +54,10 @@ var fullMapping = ' \
52 "emblem": "emblem", \
54 "emblem": "emblem", \
53 "overlayColor": "overlayColor", \
55 "overlayColor": "overlayColor", \
54 "summary": "summary", \
56 "summary": "summary", \
55− "attributes": "attributes" \
57+ "attributes": "attributes", \
58+ "social-actions": "socialActions" \
56}'
59}'
57
60
58−
59function tryParse(json, errorLabel) {
61function tryParse(json, errorLabel) {
60 var o = undefined;
62 var o = undefined;
61 if (errorLabel !== undefined) {
63 if (errorLabel !== undefined) {
@@ -82,12 +84,12 @@ function mapData(json, layout, errorLabel) {
82 for (var k in components) {
84 for (var k in components) {
83 try {
85 try {
84 if (typeof layout[components[k]] == "object") {
86 if (typeof layout[components[k]] == "object") {
85− d[components[k]] = o[layout[components[k]]['field']];
87+ d[dataKeys[k]] = o[layout[components[k]]['field']];
86 } else {
88 } else {
87− d[components[k]] = o[layout[components[k]]];
89+ d[dataKeys[k]] = o[layout[components[k]]];
88 }
90 }
89 } catch(err) {
91 } catch(err) {
90− d[components[k]] = undefined;
92+ d[dataKeys[k]] = undefined;
91 }
93 }
92 }
94 }
93 }
95 }
tests/qmltests/Dash/Filters/tst_FilterExpandableWidget.qmlnew file+99
@@ -0,0 +1,99 @@
1+/*
2+ * Copyright 2016 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.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import "../../../../qml/Dash/Filters"
21+import Unity.Test 0.1 as UT
22+import Unity 0.2
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: theme.palette.selected.background
29+
30+ QtObject {
31+ id: widgetData1
32+
33+ property string title: "Expand me!"
34+ property ListModel filters: ListModel {
35+ dynamicRoles: true
36+ }
37+ }
38+
39+ QtObject {
40+ id: sliderObject
41+
42+ property string title
43+ property int value: 30
44+ property int minValue: 10
45+ property int maxValue: 150
46+
47+ property ListModel values: ListModel {
48+ ListElement {
49+ value: 10
50+ label: "10"
51+ }
52+ ListElement {
53+ value: 30
54+ label: "30"
55+ }
56+ ListElement {
57+ value: 100
58+ label: "100"
59+ }
60+ ListElement {
61+ value: 150
62+ label: "150"
63+ }
64+ }
65+ }
66+
67+ Component.onCompleted: {
68+ widgetData1.filters.append({"type": Filters.ValueSliderFilter, "filter": sliderObject, id: "trololo"});
69+ }
70+
71+ FilterWidgetFactory {
72+ id: factory
73+ widgetId: "testExpandableWidget"
74+ widgetType: Filters.ExpandableFilterWidget
75+ widgetData: widgetData1
76+ anchors {
77+ left: parent.left
78+ right: parent.right
79+ }
80+ height: implicitHeight
81+ clip: true
82+ }
83+
84+ UT.UnityTestCase {
85+ name: "FilterExpandableWidget"
86+ when: windowShown
87+
88+ function test_expandedChanges() {
89+ var expandingItem = findChild(factory, "expandingItem");
90+ verify(!expandingItem.expanded)
91+ compare(expandingItem.height, expandingItem.collapsedHeight)
92+ mouseClick(expandingItem);
93+ verify(expandingItem.expanded)
94+ tryCompare(expandingItem, "height", expandingItem.expandedHeight)
95+ verify(expandingItem.collapsedHeight < expandingItem.expandedHeight / 2);
96+ }
97+
98+ }
99+}
tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml+2−1
@@ -43,7 +43,8 @@ Rectangle {
43 return [
43 return [
44 { tag: "OptionSelector", type: Filters.OptionSelectorFilter, source: "FilterOptionSelector.qml" },
44 { tag: "OptionSelector", type: Filters.OptionSelectorFilter, source: "FilterOptionSelector.qml" },
45 { tag: "RangeInputFilter", type: Filters.RangeInputFilter, source: "FilterRangeInput.qml" },
45 { tag: "RangeInputFilter", type: Filters.RangeInputFilter, source: "FilterRangeInput.qml" },
46− { tag: "ValueSliderFilter", type: Filters.ValueSliderFilter, source: "FilterValueSlider.qml" }
46+ { tag: "ValueSliderFilter", type: Filters.ValueSliderFilter, source: "FilterValueSlider.qml" },
47+ { tag: "ExpandableFilter", type: Filters.ExpandableFilterWidget, source: "FilterExpandableWidget.qml" }
47 ];
48 ];
48 }
49 }
49
50
tests/qmltests/Dash/Previews/tst_PreviewActions.qml+100−17
@@ -26,17 +26,17 @@ Rectangle {
26 width: units.gu(60)
26 width: units.gu(60)
27 height: units.gu(80)
27 height: units.gu(80)
28
28
29− property var actionDataOneAction: {
29+ readonly property var actionDataOneAction: {
30 "actions": [{"label": "Some Label", "id": "someid"}]
30 "actions": [{"label": "Some Label", "id": "someid"}]
31 }
31 }
32
32
33− property var actionDataTwoActions: {
33+ readonly property var actionDataTwoActions: {
34 "actions": [{"label": "Some Label A", "id": "someid1"},
34 "actions": [{"label": "Some Label A", "id": "someid1"},
35 {"label": "Some Label B", "id": "someid2"}
35 {"label": "Some Label B", "id": "someid2"}
36 ]
36 ]
37 }
37 }
38
38
39− property var actionDataFiveActions: {
39+ readonly property var actionDataFiveActions: {
40 "actions": [{"label": "Some Label C", "id": "someid3"},
40 "actions": [{"label": "Some Label C", "id": "someid3"},
41 {"label": "Some Label D", "id": "someid4"},
41 {"label": "Some Label D", "id": "someid4"},
42 {"label": "Some Label E", "id": "someid5"},
42 {"label": "Some Label E", "id": "someid5"},
@@ -45,6 +45,15 @@ Rectangle {
45 ]
45 ]
46 }
46 }
47
47
48+ property var actionModelActions: {
49+ "actions": [{"label": "Some Label H", "id": "someid8"},
50+ {"label": "Some Label I", "id": "someid9"},
51+ {"label": "Some Label J", "id": "someid10"},
52+ {"label": "Some Label K", "id": "someid11"},
53+ {"label": "Some Label L", "id": "someid12"}
54+ ]
55+ }
56+
48 SignalSpy {
57 SignalSpy {
49 id: spy
58 id: spy
50 signalName: "triggered"
59 signalName: "triggered"
@@ -88,26 +97,32 @@ Rectangle {
88 onTriggered: console.log("triggered", widgetId, actionId);
97 onTriggered: console.log("triggered", widgetId, actionId);
89 width: units.gu(60)
98 width: units.gu(60)
90 }
99 }
100+
101+ PreviewActions {
102+ id: changeModelActions
103+ width: units.gu(60)
104+ }
105+
106+ PreviewActions {
107+ id: updateModelActions
108+ width: units.gu(60)
109+ }
91 }
110 }
92
111
93 UT.UnityTestCase {
112 UT.UnityTestCase {
94 name: "PreviewActionTest"
113 name: "PreviewActionTest"
95 when: windowShown
114 when: windowShown
96
115
97− function cleanup()
98− {
99− spy.clear();
100− }
101−
102 function checkButtonPressSignal(target, id)
116 function checkButtonPressSignal(target, id)
103 {
117 {
104− var button = findChild(root, "button" + id);
118+ var button = findChild(target, "button" + id);
105 verify(button != null);
119 verify(button != null);
106 spy.target = target;
120 spy.target = target;
107 mouseClick(button);
121 mouseClick(button);
108 compare(spy.count, 1);
122 compare(spy.count, 1);
109 compare(spy.signalArguments[0][0], target.widgetId);
123 compare(spy.signalArguments[0][0], target.widgetId);
110 compare(spy.signalArguments[0][1], id);
124 compare(spy.signalArguments[0][1], id);
125+ spy.clear();
111 }
126 }
112
127
113 function test_checkButtons_data() {
128 function test_checkButtons_data() {
@@ -120,7 +135,7 @@ Rectangle {
120 }
135 }
121
136
122 function test_checkButtons(data) {
137 function test_checkButtons(data) {
123− checkButtonPressSignal(data.target, data.id, data.buttonNumber)
138+ checkButtonPressSignal(data.target, data.id)
124 }
139 }
125
140
126 function test_comboButton_data() {
141 function test_comboButton_data() {
@@ -132,17 +147,85 @@ Rectangle {
132 ]
147 ]
133 }
148 }
134
149
135− function test_comboButton(data) {
150+ function pressMoreButton(buttonGroup)
151+ {
136− var button = findChild(root, "moreLessButton");
152+ var button = findChild(buttonGroup, "moreLessButton");
137− var buttonColumn = findChild(root, "buttonColumn");
153+ var buttonColumn = findChild(buttonGroup, "buttonColumn");
138 verify(button != null);
154 verify(button != null);
139− var twoActionsY = twoActions.y
140 mouseClick(button);
155 mouseClick(button);
141− tryCompareFunction(function () { return twoActions.y <= twoActionsY; }, false);
142 tryCompare(buttonColumn, "height", buttonColumn.implicitHeight);
156 tryCompare(buttonColumn, "height", buttonColumn.implicitHeight);
157+ }
158+
159+ function test_comboButton(data) {
160+ var twoActionsY = twoActions.y
161+ pressMoreButton(buttonAndCombo);
162+ tryCompareFunction(function () { return twoActions.y <= twoActionsY; }, false);
143− checkButtonPressSignal(buttonAndCombo, data.id, data.buttonNumber);
163+ checkButtonPressSignal(buttonAndCombo, data.id);
144− mouseClick(button);
164+ mouseClick(findChild(buttonAndCombo, "moreLessButton"));
145 tryCompare(twoActions, "y", twoActionsY);
165 tryCompare(twoActions, "y", twoActionsY);
146 }
166 }
167+
168+ function test_modelChange() {
169+ changeModelActions.widgetData = actionDataOneAction;
170+ waitForRendering(changeModelActions);
171+ checkButtonPressSignal(changeModelActions, "someid");
172+
173+ changeModelActions.widgetData = actionDataTwoActions;
174+ waitForRendering(changeModelActions);
175+ checkButtonPressSignal(changeModelActions, "someid1");
176+ checkButtonPressSignal(changeModelActions, "someid2");
177+
178+ changeModelActions.widgetData = actionDataFiveActions;
179+ waitForRendering(changeModelActions);
180+ pressMoreButton(changeModelActions);
181+ checkButtonPressSignal(changeModelActions, "someid3");
182+ checkButtonPressSignal(changeModelActions, "someid4");
183+ checkButtonPressSignal(changeModelActions, "someid5");
184+ checkButtonPressSignal(changeModelActions, "someid6");
185+ }
186+
187+ function test_modelUpdate() {
188+ updateModelActions.widgetData = actionModelActions;
189+ waitForRendering(updateModelActions);
190+ pressMoreButton(updateModelActions);
191+ checkButtonPressSignal(updateModelActions, "someid8");
192+ checkButtonPressSignal(updateModelActions, "someid9");
193+ checkButtonPressSignal(updateModelActions, "someid10");
194+ checkButtonPressSignal(updateModelActions, "someid11");
195+ checkButtonPressSignal(updateModelActions, "someid12");
196+
197+ updateModelActions["actions"].pop();
198+ updateModelActions.actionsChanged();
199+
200+ // Check that some12 is gone
201+ verify(!findChild(updateModelActions, "buttonsomeid12"));
202+
203+ updateModelActions["actions"][3].id = "someidmoo";
204+ updateModelActions.actionsChanged();
205+ checkButtonPressSignal(updateModelActions, "someidmoo");
206+
207+ verify(findChild(updateModelActions, "moreLessButton"));
208+
209+ updateModelActions["actions"].pop();
210+ updateModelActions["actions"].pop();
211+ updateModelActions.actionsChanged();
212+
213+ // Check there is no more/less button anymore
214+ verify(!findChild(updateModelActions, "moreLessButton"));
215+
216+ updateModelActions["actions"][1].id = "someidbar";
217+ updateModelActions.actionsChanged();
218+ checkButtonPressSignal(updateModelActions, "someidbar");
219+
220+ updateModelActions["actions"].pop();
221+ updateModelActions.actionsChanged();
222+
223+ // Check the loader for the second button/combo is gone
224+ compare(findChild(updateModelActions, "loader").source, "");
225+
226+ updateModelActions["actions"][0].id = "someidlolar";
227+ updateModelActions.actionsChanged();
228+ checkButtonPressSignal(updateModelActions, "someidlolar");
229+ }
147 }
230 }
148}
231}
tests/qmltests/Dash/Previews/tst_PreviewIconActions.qml+14−4
@@ -69,9 +69,12 @@ Rectangle {
69 Timer {
69 Timer {
70 id: timer
70 id: timer
71 property int index: -1
71 property int index: -1
72+ property bool changeIcon: true
72 interval: 500
73 interval: 500
73 onTriggered: {
74 onTriggered: {
75+ if (changeIcon) {
74− actionDataActions.actions[index].icon = Qt.resolvedUrl("../../UnityLogo.png");
76+ actionDataActions.actions[index].icon = Qt.resolvedUrl("../../UnityLogo.png");
77+ }
75 preview.widgetDataChanged();
78 preview.widgetDataChanged();
76 index = -1;
79 index = -1;
77 }
80 }
@@ -89,12 +92,14 @@ Rectangle {
89
92
90 function test_checkButtonWithTemporary_data() {
93 function test_checkButtonWithTemporary_data() {
91 return [
94 return [
92− {tag: "with temporary", temporaryIcon: "emblem", index: 0},
95+ {tag: "with temporary change icon", temporaryIcon: "emblem", index: 0, changeIcon: true},
96+ {tag: "with temporary no change icon", temporaryIcon: "emblem", index: 1, changeIcon: false},
93− {tag: "without temporary", temporaryIcon: undefined, index: 2},
97+ {tag: "without temporary", temporaryIcon: undefined, index: 2, changeIcon: true},
94 ];
98 ];
95 }
99 }
96
100
97 function test_checkButtonWithTemporary(data) {
101 function test_checkButtonWithTemporary(data) {
102+ waitForRendering(preview);
98 spy.target = preview;
103 spy.target = preview;
99
104
100 var buttonId = actionDataActions.actions[data.index].id;
105 var buttonId = actionDataActions.actions[data.index].id;
@@ -108,6 +113,7 @@ Rectangle {
108
113
109 compare(image.source, buttonIcon);
114 compare(image.source, buttonIcon);
110 compare(label.text, buttonLabel);
115 compare(label.text, buttonLabel);
116+ timer.changeIcon = data.changeIcon;
111 mouseClick(button);
117 mouseClick(button);
112 compare(spy.count, 1);
118 compare(spy.count, 1);
113 compare(spy.signalArguments[0][1], buttonId);
119 compare(spy.signalArguments[0][1], buttonId);
@@ -119,7 +125,11 @@ Rectangle {
119 tryCompareFunction(function() {
125 tryCompareFunction(function() {
120 var button = findChild(root, "button" + buttonId);
126 var button = findChild(root, "button" + buttonId);
121 var image = findChildsByType(button, "QQuickImage")[0];
127 var image = findChildsByType(button, "QQuickImage")[0];
128+ if (data.changeIcon) {
122− return image.source && image.source.toString().indexOf("UnityLogo") > -1;
129+ return image.source && image.source.toString().indexOf("UnityLogo") > -1;
130+ } else {
131+ return image.source && image.source == buttonIcon;
132+ }
123 }, true);
133 }, true);
124 }
134 }
125 }
135 }
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+17
@@ -44,6 +44,11 @@ Rectangle {
44 "fallback": "../../graphics/phone_background.jpg"
44 "fallback": "../../graphics/phone_background.jpg"
45 }
45 }
46
46
47+ property var widgetData4: {
48+ "source": "fadsasf",
49+ "fallback": "../../graphics/phone_background.jpg"
50+ }
51+
47 Loader {
52 Loader {
48 id: loader
53 id: loader
49 width: parent.width
54 width: parent.width
@@ -112,5 +117,17 @@ Rectangle {
112 waitForRendering(lazyImage);
117 waitForRendering(lazyImage);
113 tryCompare(lazyImage, "state", "ready");
118 tryCompare(lazyImage, "state", "ready");
114 }
119 }
120+
121+ function test_fallback_then_source() {
122+ zoomableImage.widgetData = widgetData4;
123+ waitForRendering(zoomableImage);
124+ waitForRendering(lazyImage);
125+ tryCompare(lazyImage, "state", "ready");
126+ verify(lazyImage.sourceImage.source.toString().indexOf("phone_background") > -1);
127+ zoomableImage.widgetData["source"] = "../../graphics/tablet_background.jpg"
128+ zoomableImage.widgetDataChanged();
129+ tryCompare(lazyImage, "state", "ready");
130+ verify(lazyImage.sourceImage.source.toString().indexOf("tablet_background") > -1);
131+ }
115 }
132 }
116}
133}
tests/qmltests/Dash/tst_Card.qml+38−3
@@ -36,6 +36,10 @@ Rectangle {
36 "subtitle": "bar",
36 "subtitle": "bar",
37 "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.",
37 "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.",
38 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
38 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
39+ "socialActions": [{"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
40+ {"id":"share","icon":"image://theme/cancel"},
41+ {"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
42+ {"id":"share","icon":"image://theme/cancel"}],
39 "quickPreviewData": {"uri": "/some/file", "duration": "14"}
43 "quickPreviewData": {"uri": "/some/file", "duration": "14"}
40 }'
44 }'
41
45
@@ -211,6 +215,11 @@ Rectangle {
211 }
215 }
212 }
216 }
213
217
218+ SignalSpy {
219+ id: signalSpy
220+ signalName: "action"
221+ }
222+
214 UT.UnityTestCase {
223 UT.UnityTestCase {
215 id: testCase
224 id: testCase
216 name: "Card"
225 name: "Card"
@@ -274,9 +283,11 @@ Rectangle {
274 { tag: "Wide", width: units.gu(18), index: 0 },
283 { tag: "Wide", width: units.gu(18), index: 0 },
275 { tag: "Horizontal", width: units.gu(38), index: 5 },
284 { tag: "Horizontal", width: units.gu(38), index: 5 },
276 // Make sure card ends with header when there's no summary
285 // Make sure card ends with header when there's no summary
277− { tag: "NoSummary", height: function() { var cardToolRow = findChild(cardTool, "outerRow");
286+ { tag: "NoSummary", height: function() { var socialActionsRow = findChild(cardTool, "socialActionsRow");
278− return cardToolRow.y + cardToolRow.height + units.gu(1) }, index: 6 },
287+ return socialActionsRow.y + socialActionsRow.height + units.gu(1) }, index: 6 },
288+ { tag: "HorizontalNoSummary", height: function() { var socialActionsRow = findChild(cardTool, "socialActionsRow");
289+ return socialActionsRow.y + socialActionsRow.height + units.gu(1) },
279− { tag: "HorizontalNoSummary", height: function() { return headerRow.height + units.gu(2) }, card_layout: "horizontal", index: 6 },
290+ card_layout: "horizontal", index: 6 },
280 ]
291 ]
281 }
292 }
282
293
@@ -641,5 +652,29 @@ Rectangle {
641 verify((card.width - titleToCard.x - titleToCard.width) === units.gu(1));
652 verify((card.width - titleToCard.x - titleToCard.width) === units.gu(1));
642 }
653 }
643 }
654 }
655+
656+ function test_action() {
657+ selector.selectedIndex = 0;
658+ waitForRendering(card);
659+
660+ signalSpy.target = card;
661+ var socialActionsRow = findChild(card, "socialActionsRow");
662+ var delegate0 = findChild(socialActionsRow, "delegate0");
663+ var icon0 = findChild(delegate0, "icon");
664+ var delegate1 = findChild(socialActionsRow, "delegate1");
665+ var icon1 = findChild(delegate1, "icon");
666+
667+ compare(icon0.source, "image://theme/ok");
668+ mouseClick(delegate0, delegate0.height / 2, delegate0.height / 2);
669+ tryCompare(signalSpy, "count", 1);
670+ compare(signalSpy.signalArguments[0][0], "like");
671+ compare(icon0.source, "image://theme/undo");
672+
673+ compare(icon1.source, "image://theme/cancel");
674+ mouseClick(delegate1, delegate1.height / 2, delegate1.height / 2);
675+ tryCompare(signalSpy, "count", 2);
676+ compare(signalSpy.signalArguments[1][0], "share");
677+ compare(icon1.source, "image://theme/cancel");
678+ }
644 }
679 }
645}
680}