on xenial help sign in
Merge from demo branch
commitcc911b9755a08474a6b6e0727cc7679f3f628484 authorMichael Terry <michael.terry@canonical.com> dateTue 20 Aug 2013, 14:18 (13 years ago) parent0bac48861173c7
58 files changed+1475−352
Components/CrossFadeImage.qml+3−1 Dash/Apps/AppInfo.qml+1 Dash/Apps/AppPreview.qml+37−32 Dash/DashApps.qml−9 Dash/Generic/GenericPreview.qml+10−2 Greeter/GreeterContent.qml−1 Launcher/FoldingLauncherDelegate.qml+133 Launcher/Launcher.qml+4−4 Launcher/LauncherDelegate.qml+89−175 Launcher/LauncherPanel.qml+378−91 Launcher/graphics/launcher_bg@18.png Launcher/graphics/launcher_bg@18.sci−5 Launcher/graphics/launcher_bg@30.png Launcher/graphics/launcher_bg@30.sci+5 Launcher/graphics/notification@30.png Launcher/graphics/notification@30.sci+5 Launcher/graphics/progressbar-fill@30.png Launcher/graphics/progressbar-fill@30.sci+5 Launcher/graphics/progressbar-trough@30.png Launcher/graphics/progressbar-trough@30.sci+5 Panel/IndicatorsDataModel.qml+1−1 Shell.qml+37−11 debian/changelog+82 debian/control+3−1 plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+1 plugins/Unity/IndicatorsLegacy/flatmenuproxymodel.cpp+13 plugins/Unity/Launcher/common/quicklistentry.cpp+5 plugins/Unity/Launcher/common/quicklistentry.h+2 plugins/Unity/Launcher/launcheritem.cpp+11−1 plugins/Unity/Launcher/launchermodel.cpp+34−4 plugins/Unity/Launcher/plugin.cpp+2 plugins/Unity/Launcher/quicklistmodel.cpp+13 plugins/Unity/Launcher/quicklistmodel.h+10 plugins/Unity/filters.cpp+4 plugins/Unity/filters.h+2−1 plugins/Unity/genericoptionsmodel.cpp+18 plugins/Unity/genericoptionsmodel.h+3 src/Panel/Indicators/servicefiles/com.canonical.indicator.messages+1−1 tests/data/unity/backgrounds/blue.png tests/data/unity/backgrounds/red.png tests/mocks/CMakeLists.txt+1 tests/mocks/GSettings.1.0/CMakeLists.txt+23 tests/mocks/GSettings.1.0/fake_gsettings.cpp+102 tests/mocks/GSettings.1.0/fake_gsettings.h+90 tests/mocks/GSettings.1.0/plugin.cpp+33 tests/mocks/GSettings.1.0/plugin.h+31 tests/mocks/GSettings.1.0/qmldir+2 tests/mocks/Unity/Launcher/MockLauncherItem.cpp+2−2 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+32−4 tests/mocks/Unity/Launcher/MockLauncherModel.h+3 tests/mocks/Unity/Launcher/MockQuickListModel.cpp+2 tests/mocks/Unity/Launcher/plugin.cpp+3 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+22−1 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp+22−1 tests/qmltests/CMakeLists.txt+1−1 tests/qmltests/Dash/Apps/tst_AppPreview.qml+17−3 tests/qmltests/Launcher/tst_Launcher.qml+155 tests/qmltests/tst_Shell.qml+17
Components/CrossFadeImage.qml+3−1
@@ -85,7 +85,7 @@ Item {
8585 Connections {
8686 target: __nextImage
8787 onStatusChanged: {
88− if (__nextImage.status == Image.Ready) {
88+ if (__nextImage.status == Image.Ready || __nextImage.status == Image.Error) {
8989 swapImages();
9090 }
9191 }
@@ -96,6 +96,7 @@ Item {
9696 anchors.fill: parent
9797 cache: false
9898 asynchronous: true
99+ fillMode: Image.PreserveAspectCrop
99100 z: 1
100101 }
101102
@@ -104,6 +105,7 @@ Item {
104105 anchors.fill: parent
105106 cache: false
106107 asynchronous: true
108+ fillMode: Image.PreserveAspectCrop
107109 z: 0
108110 }
109111
Dash/Apps/AppInfo.qml+1
@@ -53,6 +53,7 @@ Row {
5353 }
5454
5555 Row {
56+ visible: root.rating >= 0
5657 spacing: units.gu(1)
5758
5859 RatingStars {
Dash/Apps/AppPreview.qml+37−32
@@ -38,7 +38,7 @@ DashPreview {
3838 right: parent.right
3939 margins: units.gu(1)
4040 }
41− model: previewData.infoMap["more-screenshots"].value
41+ model: previewData.infoMap["more-screenshots"] != null ? previewData.infoMap["more-screenshots"].value : [previewData.image]
4242
4343 delegate: UbuntuShape {
4444 id: shape
@@ -138,9 +138,9 @@ DashPreview {
138138
139139 appName: root.previewData.title
140140 icon: IconUtil.from_gicon(root.previewData.appIcon)
141− rating: root.previewData.infoMap["rating"] ? root.previewData.infoMap["rating"].value : 0
141+ rating: Math.round(root.previewData.rating * 10)
142+ reviews: root.previewData.numRatings
142143 rated: root.previewData.infoMap["rated"] ? root.previewData.infoMap["rated"].value : 0
143− reviews: root.previewData.infoMap["reviews"] ? root.previewData.infoMap["reviews"].value : 0
144144 }
145145
146146 Label {
@@ -154,42 +154,47 @@ DashPreview {
154154 styleColor: "black"
155155 }
156156
157− ListItem.ThinDivider {}
158−
159− Item {
157+ Column {
158+ visible: root.previewData.rating >= 0
160159 anchors { left: parent.left; right: parent.right }
161− height: rateLabel.height
162−
163− Label {
164− id: rateLabel
165− fontSize: "medium"
166− color: "white"
167− style: Text.Raised
168− styleColor: "black"
169− opacity: .9
170− text: i18n.tr("Rate this")
171−
172− anchors.left: parent.left
173− anchors.verticalCenter: parent.verticalCenter
174− }
160+ spacing: parent.spacing
161+
162+ ListItem.ThinDivider { }
163+
164+ Item {
165+ anchors { left: parent.left; right: parent.right }
166+ height: rateLabel.height
167+
168+ Label {
169+ id: rateLabel
170+ fontSize: "medium"
171+ color: "white"
172+ style: Text.Raised
173+ styleColor: "black"
174+ opacity: .9
175+ text: i18n.tr("Rate this")
176+
177+ anchors.left: parent.left
178+ anchors.verticalCenter: parent.verticalCenter
179+ }
175180
176− // FIXME these need to be made interactive and connected to the scope
177− RatingStars {
178− anchors.right: parent.right
179− anchors.verticalCenter: parent.verticalCenter
181+ // FIXME these need to be made interactive and connected to the scope
182+ RatingStars {
183+ anchors.right: parent.right
184+ anchors.verticalCenter: parent.verticalCenter
185+ }
180186 }
181− }
182187
183− ListItem.ThinDivider {}
188+ ListItem.ThinDivider { }
184189
185− AppReviews {
186− objectName: "appReviews"
187− anchors { left: parent.left; right: parent.right }
190+ AppReviews {
191+ objectName: "appReviews"
192+ anchors { left: parent.left; right: parent.right }
188193
189− model: root.previewData.infoMap["comments"] ? root.previewData.infoMap["comments"].value : undefined
194+ model: root.previewData.infoMap["comments"] ? root.previewData.infoMap["comments"].value : undefined
190195
191− onSendReview: root.sendUserReview(review);
196+ onSendReview: root.sendUserReview(review);
197+ }
192198 }
193−
194199 }
195200}
Dash/DashApps.qml−9
@@ -29,14 +29,6 @@ GenericScopeView {
2929 property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
3030 property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
3131
32− SearchableResultModel {
33− id: appsAvailableForDownloadModel
34−
35− model: AppsAvailableForDownloadModel {}
36− filterRole: 3
37− searchQuery: scopeView.scope.searchQuery
38− }
39−
4032 ListModel {
4133 id: dummyVisibilityModifier
4234
@@ -59,6 +51,5 @@ GenericScopeView {
5951
6052 onScopeChanged: {
6153 scopeView.scope.categories.overrideResults("recent", runningApplicationsModel);
62− scopeView.scope.categories.overrideResults("more", appsAvailableForDownloadModel);
6354 }
6455}
Dash/Generic/GenericPreview.qml+10−2
@@ -74,25 +74,33 @@ DashPreview {
7474 spacing: units.gu(2)
7575
7676 Label {
77+ anchors { left: parent.left; right: parent.right }
7778 visible: text != ""
7879 fontSize: "medium"
7980 opacity: 0.6
8081 color: "white"
81− text: previewData.subtitle
82+ text: previewData.subtitle.replace(/[\r\n]+/g, "<br />")
8283 style: Text.Raised
8384 styleColor: "black"
8485 wrapMode: Text.WordWrap
86+ textFormat: Text.RichText
87+ // FIXME: workaround for https://bugreports.qt-project.org/browse/QTBUG-33020
88+ onWidthChanged: { wrapMode = Text.NoWrap; wrapMode = Text.WordWrap }
8589 }
8690
8791 Label {
92+ anchors { left: parent.left; right: parent.right }
8893 visible: text != ""
8994 fontSize: "small"
9095 opacity: 0.6
9196 color: "white"
92− text: previewData.description
97+ text: previewData.description.replace(/[\r\n]+/g, "<br />")
9398 style: Text.Raised
9499 styleColor: "black"
95100 wrapMode: Text.WordWrap
101+ textFormat: Text.RichText
102+ // FIXME: workaround for https://bugreports.qt-project.org/browse/QTBUG-33020
103+ onWidthChanged: { wrapMode = Text.NoWrap; wrapMode = Text.WordWrap }
96104 }
97105 }
98106}
Greeter/GreeterContent.qml−1
@@ -37,7 +37,6 @@ MouseArea {
3737
3838 source: shell.background
3939 anchors.fill: parent
40− crossFade: false
4140 fadeInFirst: false
4241 }
4342
Launcher/FoldingLauncherDelegate.qmlnew file+133
@@ -0,0 +1,133 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+LauncherDelegate {
21+ id: root
22+
23+ // The angle used for rotating
24+ angle: {
25+ // First/last items are special
26+ if (index == 0 || index == priv.listView.count-1) {
27+ if (priv.distanceFromEdge < 0) {
28+ // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
29+ return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
30+ }
31+ return 0; // Don't fold first/last item as long as inside the view
32+ }
33+
34+ // Are we in the already completely outside the flickable? Fold for the last 5 degrees
35+ if (priv.distanceFromEdge < 0) {
36+ // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
37+ return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
38+ }
39+
40+ // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
41+ if (priv.overlapWithFoldingArea > 0) {
42+ // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
43+ return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
44+ }
45+ return 0;
46+ }
47+
48+ // This is the offset that keeps the items inside the panel
49+ offset: {
50+ // First/last items are special
51+ if (index == 0 || index == priv.listView.count-1) {
52+ // Just keep them bound to the edges in case they're outside of the visible area
53+ if (priv.distanceFromEdge < 0) {
54+ return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
55+ }
56+ return 0;
57+ }
58+
59+ // Are we already completely outside the flickable? Stop the icon here.
60+ if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
61+ return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
62+ }
63+
64+ // We're touching the edge, move slower than the actual flicking speed.
65+ if (priv.distanceFromEdge < 0) {
66+ return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
67+ }
68+ return 0;
69+ }
70+
71+ itemOpacity: {
72+ // First/last items are special
73+ if (index == 0 || index == priv.listView.count-1) {
74+ if (priv.distanceFromEdge < 0) {
75+ // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
76+ return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
77+ }
78+ return 1; // Don't make first/last item transparent as long as inside the view
79+ }
80+
81+ // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
82+ if (priv.distanceFromEdge < 0) {
83+ // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
84+ return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
85+ }
86+
87+ // We are overlapping with the folding area, fade out to 0.75
88+ if (priv.overlapWithFoldingArea > 0) {
89+ // proportion equation: overlap : totalHeight = 1-opacity : 0.25
90+ return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
91+ }
92+ return 1;
93+ }
94+
95+ brightness: {
96+ // First/last items are special
97+ if (index == 0 || index == priv.listView.count-1) {
98+ if (priv.distanceFromEdge < 0) {
99+ return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
100+ }
101+ return 0;
102+ }
103+ // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
104+ if (priv.distanceFromEdge < 0) {
105+ return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
106+ }
107+
108+ // We are overlapping with the folding area, fade out to 0.7
109+ if (priv.overlapWithFoldingArea > 0) {
110+ return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
111+ }
112+ return 0;
113+ }
114+
115+ QtObject {
116+ id: priv
117+
118+ property ListView listView: root.ListView.view
119+ property real totalUnfoldedHeight: listView.itemHeight + listView.spacing
120+ property real totalEffectiveHeight: effectiveHeight + listView.spacing
121+ property real effectiveContentY: listView.contentY - listView.originY
122+ property real effectiveY: y - listView.originY
123+ property real bottomDraggingDistanceOffset: listView.draggedIndex > index ? totalUnfoldedHeight : 0
124+ property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)
125+ property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY + bottomDraggingDistanceOffset
126+
127+ property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
128+ property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
129+
130+ property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
131+ }
132+
133+}
Launcher/Launcher.qml+4−4
@@ -73,7 +73,7 @@ Item {
7373 id: dismissTimer
7474 interval: 5000
7575 onTriggered: {
76− if (!panel.moving) {
76+ if (!panel.preventHiding) {
7777 root.state = ""
7878 } else {
7979 dismissTimer.restart()
@@ -135,9 +135,9 @@ Item {
135135 id: backgroundShade
136136 anchors.fill: parent
137137 color: "black"
138− opacity: root.state == "visible" ? 0.4 : 0
138+ opacity: root.state == "visible" ? 0.6 : 0
139139
140− Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuad} }
140+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
141141 }
142142
143143 LauncherPanel {
@@ -163,7 +163,7 @@ Item {
163163 root.dashItemSelected(index)
164164 }
165165
166− onMovingChanged: {
166+ onPreventHidingChanged: {
167167 if (dismissTimer.running) {
168168 dismissTimer.restart();
169169 }
Launcher/LauncherDelegate.qml+89−175
@@ -21,29 +21,33 @@ Item {
2121 id: root
2222
2323 property string iconName
24−
25− property real angle: 0
24+ property int count: -1
25+ property int progress: -1
2626 property bool highlighted: false
27− property real offset: 0
28− property alias brightness: transformEffect.brightness
29− property real itemOpacity: 1
30−
3127 property real maxAngle: 0
3228 property bool inverted: false
3329
34− readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height
35− readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height
30+ readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
31+ readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
3632
37− property bool dragging:false
33+ property int itemWidth
34+ property int itemHeight
35+ // The angle used for rotating
36+ property real angle: 0
37+ // This is the offset that keeps the items inside the panel
38+ property real offset: 0
39+ property real itemOpacity: 1
40+ property real brightness: 0
3841
39− signal clicked()
40− signal longtap()
41− signal released()
42+ onIconNameChanged: shaderEffectSource.scheduleUpdate();
43+ onCountChanged: shaderEffectSource.scheduleUpdate();
44+ onProgressChanged: shaderEffectSource.scheduleUpdate();
45+ onHighlightedChanged: shaderEffectSource.scheduleUpdate();
4246
4347 Item {
4448 id: iconItem
45− width: parent.width
46− height: parent.height
49+ width: parent.itemWidth
50+ height: parent.itemHeight
4751 anchors.centerIn: parent
4852
4953 UbuntuShape {
@@ -58,31 +62,79 @@ Item {
5862 sourceSize.height: iconShape.height
5963 source: root.iconName
6064 }
61−
62− MouseArea {
63− id: mouseArea
64− anchors.fill: parent
65− onClicked: root.clicked()
66− onCanceled: root.released()
67− preventStealing: false
68−
69− onPressAndHold: {
70− root.state = "moving"
71− }
72− onReleased: {
73− root.state = "docked"
74− }
75− }
7665 }
66+
7767 BorderImage {
7868 id: overlayHighlight
7969 anchors.centerIn: iconItem
8070 rotation: inverted ? 180 : 0
81− source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
82− width: root.width + units.gu(0.5)
83− height: width
84− property bool isSelected: root.highlighted || mouseArea.pressed
85− onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
71+ source: root.highlighted ? "graphics/selected.sci" : "graphics/non-selected.sci"
72+ width: root.itemWidth + units.gu(0.5)
73+ height: root.itemHeight + units.gu(0.5)
74+ }
75+
76+ BorderImage {
77+ objectName: "countEmblem"
78+ anchors {
79+ right: parent.right
80+ top: parent.top
81+ margins: -units.dp(1)
82+ }
83+ width: Math.min(units.gu(7.5), Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
84+ height: units.gu(3)
85+ source: "graphics/notification.sci"
86+ visible: root.count > 0
87+
88+ Label {
89+ id: countLabel
90+ text: root.count
91+ anchors.centerIn: parent
92+ width: units.gu(6)
93+ horizontalAlignment: Text.AlignHCenter
94+ elide: Text.ElideRight
95+ color: "white"
96+ fontSize: "small"
97+ font.bold: true
98+ }
99+ }
100+
101+ BorderImage {
102+ id: progressOverlay
103+ objectName: "progressOverlay"
104+ anchors {
105+ left: iconItem.left
106+ right: iconItem.right
107+ bottom: iconItem.bottom
108+ leftMargin: units.gu(0.5)
109+ rightMargin: units.gu(0.5)
110+ bottomMargin: units.gu(0.5)
111+ }
112+ height: units.gu(1.5)
113+ visible: root.progress > -1
114+ source: "graphics/progressbar-trough.sci"
115+
116+ // For fill calculation we need to remove the 2 units of border defined in .sci file
117+ property int adjustedWidth: width - units.gu(2)
118+
119+ Item {
120+ anchors {
121+ left: parent.left
122+ top: parent.top
123+ bottom: parent.bottom
124+ }
125+ width: Math.min(100, root.progress) / 100 * parent.adjustedWidth + units.gu(1)
126+ clip: true
127+
128+ BorderImage {
129+ anchors {
130+ left: parent.left
131+ top: parent.top
132+ bottom: parent.bottom
133+ }
134+ width: progressOverlay.width
135+ source: "graphics/progressbar-fill.sci"
136+ }
137+ }
86138 }
87139 }
88140
@@ -90,14 +142,13 @@ Item {
90142 id: transformEffect
91143 anchors.centerIn: parent
92144 anchors.verticalCenterOffset: root.offset
93− width: parent.width
94− height: parent.height
145+ width: parent.itemWidth
146+ height: parent.itemHeight
95147 property real itemOpacity: root.itemOpacity
96− property real brightness: 0
148+ property real brightness: Math.max(-1, root.brightness)
97149 property real angle: root.angle
98150 rotation: root.inverted ? 180 : 0
99151
100−
101152 property variant source: ShaderEffectSource {
102153 id: shaderEffectSource
103154 sourceItem: iconItem
@@ -149,141 +200,4 @@ Item {
149200 gl_FragColor = sourceColor;
150201 }"
151202 }
152−
153− QtObject {
154− id: priv
155−
156− property ListView listView: root.ListView.view
157− property real totalUnfoldedHeight: listView.itemSize + listView.spacing
158− property real totalEffectiveHeight: effectiveHeight + listView.spacing
159− property real effectiveContentY: listView.contentY - listView.originY
160− property real effectiveY: y - listView.originY
161− property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)
162− property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY
163−
164− property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
165− property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
166−
167− property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
168− }
169−
170− states: [
171− State {
172− name: "docked"
173− PropertyChanges {
174− target: root
175−
176− // This is the offset that keeps the items inside the panel
177− offset: {
178− // First/last items are special
179− if (index == 0 || index == priv.listView.count-1) {
180− // Just keep them bound to the edges in case they're outside of the visible area
181− if (priv.distanceFromEdge < 0) {
182− return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
183− }
184− return 0;
185− }
186−
187− // Are we already completely outside the flickable? Stop the icon here.
188− if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
189− return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
190− }
191−
192− // We're touching the edge, move slower than the actual flicking speed.
193− if (priv.distanceFromEdge < 0) {
194− return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
195− }
196− return 0;
197− }
198−
199− // The angle used for rotating
200− angle: {
201− // First/last items are special
202− if (index == 0 || index == priv.listView.count-1) {
203− if (priv.distanceFromEdge < 0) {
204− // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
205− return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
206− }
207− return 0; // Don't fold first/last item as long as inside the view
208− }
209−
210− // Are we in the already completely outside the flickable? Fold for the last 5 degrees
211− if (priv.distanceFromEdge < 0) {
212− // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
213− return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
214− }
215−
216− // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
217− if (priv.overlapWithFoldingArea > 0) {
218− // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
219− return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
220− }
221− return 0;
222− }
223−
224− itemOpacity: {
225− // First/last items are special
226− if (index == 0 || index == priv.listView.count-1) {
227− if (priv.distanceFromEdge < 0) {
228− // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
229− return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
230− }
231− return 1; // Don't make first/last item transparent as long as inside the view
232− }
233−
234− // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
235− if (priv.distanceFromEdge < 0) {
236− // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
237− return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
238− }
239−
240− // We are overlapping with the folding area, fade out to 0.75
241− if (priv.overlapWithFoldingArea > 0) {
242− // proportion equation: overlap : totalHeight = 1-opacity : 0.25
243− return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
244− }
245− return 1;
246− }
247−
248− brightness: {
249− // First/last items are special
250− if (index == 0 || index == priv.listView.count-1) {
251− if (priv.distanceFromEdge < 0) {
252− return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
253− }
254− return 0;
255− }
256− // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
257− if (priv.distanceFromEdge < 0) {
258− return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
259− }
260−
261− // We are overlapping with the folding area, fade out to 0.7
262− if (priv.overlapWithFoldingArea > 0) {
263− return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
264− }
265− return 0;
266− }
267− }
268− },
269−
270− State {
271− name: "moving"
272− PropertyChanges {
273− target: launcherDelegate;
274− offset: 0
275− angle: 0
276− }
277− PropertyChanges {
278− target: root
279− highlighted: true
280− dragging: true
281− }
282− PropertyChanges {
283− target: mouseArea
284− preventStealing: true
285− drag.target: root
286− }
287− }
288− ]
289203}
Launcher/LauncherPanel.qml+378−91
@@ -16,8 +16,10 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Ubuntu.Components.ListItems 0.1 as ListItems
1920import Unity 0.1
2021import Unity.Launcher 0.1
22+import Ubuntu.Components.Popups 0.1
2123import "../Components/ListItems"
2224
2325Item {
@@ -29,53 +31,33 @@ Item {
2931 property bool inverted: true
3032 property bool dragging: false
3133 property bool moving: launcherListView.moving || launcherListView.flicking
32− property int dragPosition: 0
34+ property bool preventHiding: moving || dndArea.draggedIndex >= 0 || dndArea.quickListPopover !== null || dndArea.pressed
3335 property int highlightIndex: -1
3436
3537 signal applicationSelected(string desktopFile)
3638 signal dashItemSelected(int index)
3739
38− onDragPositionChanged: {
39− var effectiveDragPosition = root.inverted ? launcherListView.height - dragPosition : dragPosition - mainColumn.anchors.margins
40−
41− var hiddenContentHeight = launcherListView.contentHeight - launcherListView.height
42− // Shortening scrollable height because the first/last item needs to be fully expanded before reaching the top/bottom
43− var scrollableHeight = launcherListView.height - (launcherListView.itemSize + launcherColumn.spacing) *2
44− // As we shortened the scrollableHeight, lets move everything down by the itemSize
45− var shortenedEffectiveDragPosition = effectiveDragPosition - launcherListView.itemSize - launcherColumn.spacing
46− var newContentY = shortenedEffectiveDragPosition * hiddenContentHeight / scrollableHeight
47−
48− // limit top/bottom to prevent overshooting
49− launcherListView.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
50−
51− // Now calculate the current index:
52− // > the current mouse position + the hidden/scolled content on top is the mouse position in the averall view
53− // > adjust that removing all the margins
54− // > divide by itemSize to get index
55− highlightIndex = (effectiveDragPosition + launcherListView.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherListView.itemSize + launcherColumn.spacing)
56− }
57−
5840 BorderImage {
5941 id: background
6042 source: "graphics/launcher_bg.sci"
6143 anchors.fill: parent
44+ anchors.rightMargin: root.inverted ? 0 : -units.gu(1)
45+ anchors.leftMargin: root.inverted ? -units.gu(1) : 0
46+ rotation: root.rotation
6247 }
6348
6449 Column {
6550 id: mainColumn
6651 anchors {
6752 fill: parent
68− topMargin: units.gu(0.5)
69− bottomMargin: units.gu(1)
7053 leftMargin: units.gu(0.5)
7154 rightMargin: units.gu(0.5)
7255 }
73− spacing: units.gu(0.5)
7456
7557 MouseArea {
7658 id: dashItem
7759 width: parent.width
78− height: units.gu(6.5)
60+ height: units.gu(7)
7961 onClicked: root.dashItemSelected(0)
8062 z: 1
8163 Image {
@@ -100,87 +82,392 @@ Item {
10082 anchors.left: parent.left
10183 anchors.right: parent.right
10284 height: parent.height - dashItem.height - parent.spacing*2
103− ListView {
104− id: launcherListView
105− objectName: "launcherListView"
85+
86+ Item {
10687 anchors.fill: parent
107− anchors.topMargin: -itemSize
108− anchors.bottomMargin: -itemSize
109− topMargin: itemSize
110− bottomMargin: itemSize
111− height: parent.height - dashItem.height - parent.spacing*2
112− model: root.model
113− cacheBuffer: itemSize * 3
114− snapMode: ListView.SnapToItem
115− highlightRangeMode: ListView.ApplyRange
116− preferredHighlightBegin: (height - itemSize) / 2
117− preferredHighlightEnd: (height + itemSize) / 2
118−
119− // The height of the area where icons start getting folded
120− property int foldingAreaHeight: itemSize * 0.75
121− property int itemSize: width
122− property int clickFlickSpeed: units.gu(60)
123−
124− delegate: LauncherDelegate {
125− id: launcherDelegate
126− objectName: "launcherDelegate" + index
127− width: launcherListView.itemSize
128− height: launcherListView.itemSize
129− iconName: model.icon
130− inverted: root.inverted
131− highlighted: root.dragging && index === root.highlightIndex
132− z: -Math.abs(offset)
133− state: "docked"
134− maxAngle: 60
135−
136− onClicked: {
137− // First/last item do the scrolling at more than 12 degrees
138− if (index == 0 || index == launcherListView.count -1) {
139− if (angle > 12) {
88+ clip: true
89+
90+ ListView {
91+ id: launcherListView
92+ objectName: "launcherListView"
93+ anchors {
94+ fill: parent
95+ topMargin: -extensionSize + units.gu(0.5)
96+ bottomMargin: -extensionSize + units.gu(1)
97+ }
98+ topMargin: extensionSize
99+ bottomMargin: extensionSize
100+ height: parent.height - dashItem.height - parent.spacing*2
101+ model: root.model
102+ cacheBuffer: itemHeight * 3
103+ snapMode: interactive ? ListView.SnapToItem : ListView.NoSnap
104+ highlightRangeMode: ListView.ApplyRange
105+ preferredHighlightBegin: (height - itemHeight) / 2
106+ preferredHighlightEnd: (height + itemHeight) / 2
107+ spacing: units.gu(0.5)
108+
109+ // The size of the area the ListView is extended to make sure items are not
110+ // destroyed when dragging them outside the list. This needs to be at least
111+ // itemHeight to prevent folded items from disappearing and DragArea limits
112+ // need to be smaller than this size to avoid breakage.
113+ property int extensionSize: itemHeight * 3
114+
115+ // The height of the area where icons start getting folded
116+ property int foldingAreaHeight: itemHeight * 0.75
117+ property int itemWidth: width
118+ property int itemHeight: width * 7.5 / 8
119+ property int clickFlickSpeed: units.gu(60)
120+ property int draggedIndex: dndArea.draggedIndex
121+ property real realContentY: contentY - originY + topMargin
122+ property int realItemHeight: itemHeight + spacing
123+
124+ displaced: Transition {
125+ NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
126+ }
127+
128+ delegate: FoldingLauncherDelegate {
129+ id: launcherDelegate
130+ objectName: "launcherDelegate" + index
131+ itemHeight: launcherListView.itemHeight
132+ itemWidth: launcherListView.itemWidth
133+ width: itemWidth
134+ height: itemHeight
135+ iconName: model.icon
136+ count: model.count
137+ progress: model.progress
138+ inverted: root.inverted
139+ highlighted: dragging && index === root.highlightIndex
140+ z: -Math.abs(offset)
141+ maxAngle: 60
142+ property bool dragging: false
143+
144+ ThinDivider {
145+ id: dropIndicator
146+ objectName: "dropIndicator"
147+ anchors.centerIn: parent
148+ width: parent.width + mainColumn.anchors.leftMargin + mainColumn.anchors.rightMargin
149+ opacity: 0
150+ }
151+
152+ states: [
153+ State {
154+ name: "selected"
155+ when: dndArea.selectedItem === launcherDelegate && fakeDragItem.visible && !dragging
156+ PropertyChanges {
157+ target: launcherDelegate
158+ itemOpacity: 0
159+ }
160+ },
161+ State {
162+ name: "dragging"
163+ when: dragging
164+ PropertyChanges {
165+ target: launcherDelegate
166+ height: units.gu(1)
167+ itemOpacity: 0
168+ }
169+ PropertyChanges {
170+ target: dropIndicator
171+ opacity: 1
172+ }
173+ },
174+ State {
175+ name: "expanded"
176+ when: dndArea.draggedIndex >= 0 && (dndArea.preDragging || dndArea.dragging || dndArea.postDragging) && dndArea.draggedIndex != index
177+ PropertyChanges {
178+ target: launcherDelegate
179+ angle: 0
180+ offset: 0
181+ itemOpacity: 0.6
182+ }
183+ }
184+ ]
185+
186+ transitions: [
187+ Transition {
188+ from: ""
189+ to: "selected"
190+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
191+ },
192+ Transition {
193+ from: "*"
194+ to: "expanded"
195+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
196+ UbuntuNumberAnimation { properties: "angle,offset" }
197+ },
198+ Transition {
199+ from: "expanded"
200+ to: ""
201+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
202+ UbuntuNumberAnimation { properties: "angle,offset" }
203+ },
204+ Transition {
205+ from: "selected"
206+ to: "dragging"
207+ UbuntuNumberAnimation { properties: "height" }
208+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
209+ },
210+ Transition {
211+ from: "dragging"
212+ to: "*"
213+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
214+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
215+ SequentialAnimation {
216+ UbuntuNumberAnimation { properties: "height" }
217+ PropertyAction { target: dndArea; property: "postDragging"; value: false }
218+ PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
219+ }
220+ }
221+ ]
222+ }
223+
224+ MouseArea {
225+ id: dndArea
226+ objectName: "dndArea"
227+ anchors {
228+ fill: parent
229+ topMargin: launcherListView.topMargin
230+ bottomMargin: launcherListView.bottomMargin
231+ }
232+ drag.minimumY: -launcherListView.topMargin
233+ drag.maximumY: height + launcherListView.bottomMargin
234+
235+ property int draggedIndex: -1
236+ property var selectedItem
237+ property bool preDragging: false
238+ property bool dragging: selectedItem !== undefined && selectedItem.dragging
239+ property bool postDragging: false
240+ property int startX
241+ property int startY
242+ property var quickListPopover: null
243+
244+ onPressed: {
245+ selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
246+ selectedItem.highlighted = true
247+ }
248+
249+ onClicked: {
250+ var index = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
251+ var clickedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
252+
253+ // First/last item do the scrolling at more than 12 degrees
254+ if (index == 0 || index == launcherListView.count - 1) {
255+ if (clickedItem.angle > 12) {
256+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
257+ } else if (clickedItem.angle < -12) {
258+ launcherListView.flick(0, launcherListView.clickFlickSpeed);
259+ } else {
260+ root.applicationSelected(LauncherModel.get(index).desktopFile);
261+ }
262+ return;
263+ }
264+
265+ // the rest launches apps up to an angle of 30 degrees
266+ if (clickedItem.angle > 30) {
140267 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
141− } else if (angle < -12) {
268+ } else if (clickedItem.angle < -30) {
142269 launcherListView.flick(0, launcherListView.clickFlickSpeed);
143270 } else {
144271 root.applicationSelected(LauncherModel.get(index).desktopFile);
145272 }
146− return;
147273 }
148274
149− // the rest launches apps up to an angle of 30 degrees
150− if (angle > 30) {
151− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
152− } else if (angle < -30) {
153− launcherListView.flick(0, launcherListView.clickFlickSpeed);
154− } else {
155− root.applicationSelected(LauncherModel.get(index).desktopFile);
275+ onCanceled: {
276+ selectedItem.highlighted = false
277+ selectedItem = undefined;
278+ preDragging = false;
279+ postDragging = false;
280+ }
281+
282+ onReleased: {
283+ var droppedIndex = draggedIndex;
284+ if (dragging) {
285+ postDragging = true;
286+ } else {
287+ draggedIndex = -1;
288+ }
289+
290+ selectedItem.highlighted = false
291+ selectedItem.dragging = false;
292+ selectedItem = undefined;
293+ preDragging = false;
294+
295+ drag.target = undefined
296+
297+ progressiveScrollingTimer.stop();
298+ launcherListView.interactive = true;
299+ if (droppedIndex >= launcherListView.count - 2 && postDragging) {
300+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
301+ }
302+ }
303+
304+ onPressAndHold: {
305+ if (Math.abs(selectedItem.angle) > 30) {
306+ return;
307+ }
308+
309+ draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
310+
311+ // Opening QuickList
312+ var quickListModel = launcherListView.model.get(draggedIndex).quickList
313+ var quickListAppId = launcherListView.model.get(draggedIndex).appId
314+
315+ quickListPopover = PopupUtils.open(popoverComponent, selectedItem,
316+ {model: quickListModel, appId: quickListAppId})
317+
318+ launcherListView.interactive = false
319+
320+ var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
321+
322+ fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
323+ fakeDragItem.x = 0
324+ fakeDragItem.y = mouseY - yOffset + launcherListView.anchors.topMargin + launcherListView.topMargin
325+ fakeDragItem.angle = selectedItem.angle * (root.inverted ? -1 : 1)
326+ fakeDragItem.offset = selectedItem.offset * (root.inverted ? -1 : 1)
327+ fakeDragItem.count = LauncherModel.get(draggedIndex).count
328+ fakeDragItem.progress = LauncherModel.get(draggedIndex).progress
329+ fakeDragItem.flatten()
330+ drag.target = fakeDragItem
331+
332+ startX = mouseX
333+ startY = mouseY
334+ }
335+
336+ onPositionChanged: {
337+ if (draggedIndex >= 0) {
338+ if (!selectedItem.dragging) {
339+ var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
340+ if (!preDragging && distance > units.gu(1.5)) {
341+ preDragging = true;
342+ PopupUtils.close(quickListPopover)
343+ }
344+ if (distance > launcherListView.itemHeight) {
345+ selectedItem.dragging = true
346+ preDragging = false;
347+ }
348+ }
349+ if (!selectedItem.dragging) {
350+ return
351+ }
352+
353+ var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
354+
355+ // Move it down by the the missing size to compensate index calculation with only expanded items
356+ itemCenterY += (launcherListView.itemHeight - selectedItem.height) / 2
357+
358+ if (mouseY > launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin - launcherListView.realItemHeight) {
359+ progressiveScrollingTimer.downwards = false
360+ progressiveScrollingTimer.start()
361+ } else if (mouseY < launcherListView.realItemHeight) {
362+ progressiveScrollingTimer.downwards = true
363+ progressiveScrollingTimer.start()
364+ } else {
365+ progressiveScrollingTimer.stop()
366+ }
367+
368+ var newIndex = (itemCenterY + launcherListView.realContentY) / launcherListView.realItemHeight
369+
370+ if (newIndex > draggedIndex + 1) {
371+ newIndex = draggedIndex + 1
372+ } else if (newIndex < draggedIndex) {
373+ newIndex = draggedIndex -1
374+ } else {
375+ return
376+ }
377+
378+ if (newIndex >= 0 && newIndex < launcherListView.count) {
379+ launcherListView.model.move(draggedIndex, newIndex)
380+ draggedIndex = newIndex
381+ }
382+ }
383+ }
384+ }
385+ Timer {
386+ id: progressiveScrollingTimer
387+ interval: 2
388+ repeat: true
389+ running: false
390+ property bool downwards: true
391+ onTriggered: {
392+ if (downwards) {
393+ var minY = -launcherListView.topMargin
394+ if (launcherListView.contentY > minY) {
395+ launcherListView.contentY = Math.max(launcherListView.contentY - units.dp(2), minY)
396+ }
397+ } else {
398+ var maxY = launcherListView.contentHeight - launcherListView.height + launcherListView.topMargin + launcherListView.originY
399+ if (launcherListView.contentY < maxY) {
400+ launcherListView.contentY = Math.min(launcherListView.contentY + units.dp(2), maxY)
401+ }
402+ }
156403 }
157404 }
158405 }
406+ }
159407
160− MouseArea {
161− id: topFoldingArea
162− anchors {
163− left: parent.left
164− right: parent.right
165− top: parent.top
166− topMargin: launcherListView.topMargin
167− }
168− height: launcherListView.itemSize / 2
169− enabled: launcherListView.contentY > -launcherListView.topMargin
170− onClicked: launcherListView.flick(0, launcherListView.clickFlickSpeed)
408+ LauncherDelegate {
409+ id: fakeDragItem
410+ objectName: "fakeDragItem"
411+ visible: dndArea.draggedIndex >= 0 && !dndArea.postDragging
412+ itemWidth: launcherListView.itemWidth
413+ itemHeight: launcherListView.itemHeight
414+ height: itemHeight
415+ width: itemWidth
416+ rotation: root.rotation
417+ highlighted: true
418+ itemOpacity: 0.8
419+
420+ function flatten() {
421+ fakeDragItemAnimation.start();
171422 }
172423
173− MouseArea {
174− id: bottomFoldingArea
175− anchors {
176− left: parent.left
177− right: parent.right
178− bottom: parent.bottom
179− bottomMargin: launcherListView.bottomMargin
424+ UbuntuNumberAnimation {
425+ id: fakeDragItemAnimation
426+ target: fakeDragItem;
427+ properties: "angle,offset";
428+ to: 0
429+ }
430+ }
431+ }
432+ }
433+
434+ Component {
435+ id: popoverComponent
436+
437+ Popover {
438+ id: popover
439+ property var model
440+ property string appId
441+ contentWidth: quickListColumn.width
442+
443+ // FIXME: There's a bug in the Popover positioning that it covers the item in case it is rotated.
444+ // https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1204470
445+ // For now, let's move the Popover around with callerMargin.
446+ // Remove popupMargin once the bug is fixed.
447+ property int popupMargin: root.inverted ? launcherListView.itemHeight : 0;
448+
449+ callerMargin: units.gu(1) + popupMargin
450+
451+ Column {
452+ id: quickListColumn
453+ width: units.gu(30)
454+ height: childrenRect.height
455+
456+ Repeater {
457+ model: popover.model
458+
459+ ListItems.Standard {
460+ objectName: "quickListEntry" + index
461+ text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
462+ highlightWhenPressed: model.clickable
463+ onClicked: {
464+ if (!model.clickable) {
465+ return;
466+ }
467+ LauncherModel.quickListActionInvoked(appId, index);
468+ PopupUtils.close(popover);
469+ }
180470 }
181− height: launcherListView.itemSize / 2
182− enabled: launcherListView.contentHeight - launcherListView.height - launcherListView.contentY > -launcherListView.bottomMargin
183− onClicked: launcherListView.flick(0, -launcherListView.clickFlickSpeed)
184471 }
185472 }
186473 }
Launcher/graphics/launcher_bg@18.pngdeleted

Binary file not shown.

Launcher/graphics/launcher_bg@18.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 0
2−border.top: 0
3−border.bottom: 0
4−border.right: 24
5−source: launcher_bg@18.png
Launcher/graphics/launcher_bg@30.pngnew file

Binary file not shown.

Launcher/graphics/launcher_bg@30.scinew file+5
@@ -0,0 +1,5 @@
1+border.left: 4
2+border.top: 4
3+border.bottom: 4
4+border.right: 66
5+source: launcher_bg@30.png
Launcher/graphics/notification@30.pngnew file

Binary file not shown.

Launcher/graphics/notification@30.scinew file+5
@@ -0,0 +1,5 @@
1+border.left: 36
2+border.top: 36
3+border.bottom: 36
4+border.right: 36
5+source: notification@30.png
Launcher/graphics/progressbar-fill@30.pngnew file

Binary file not shown.

Launcher/graphics/progressbar-fill@30.scinew file+5
@@ -0,0 +1,5 @@
1+border.left: 36
2+border.top: 16
3+border.bottom: 28
4+border.right: 36
5+source: progressbar-fill@30.png
Launcher/graphics/progressbar-trough@30.pngnew file

Binary file not shown.

Launcher/graphics/progressbar-trough@30.scinew file+5
@@ -0,0 +1,5 @@
1+border.left: 36
2+border.top: 16
3+border.bottom: 26
4+border.right: 36
5+source: progressbar-trough@30.png
Panel/IndicatorsDataModel.qml+1−1
@@ -33,7 +33,7 @@ Indicators.IndicatorsModel {
3333 'widgetSource' : ApplicationPaths.shellAppDirectory()+'/Panel/Indicators/DefaultIndicatorWidget2.qml',
3434 'pageSource' : ApplicationPaths.shellAppDirectory()+'/Panel/Indicators/DefaultIndicatorPage2.qml'
3535 },
36− 'indicator-messaging' : {
36+ 'indicator-messages' : {
3737 'title': 'Messaging',
3838 'description' : '',
3939 'widgetSource' : ApplicationPaths.shellAppDirectory()+'/Panel/Indicators/MessagingIndicatorWidget.qml',
Shell.qml+37−11
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import GSettings 1.0
1819import Ubuntu.Application 0.1
1920import Ubuntu.Components 0.1
2021import Ubuntu.Gestures 0.1
@@ -43,8 +44,8 @@ FocusScope {
4344 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
4445
4546 property real edgeSize: units.gu(2)
46− property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
47− property url background: default_background
47+ property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
48+ property url background
4849 readonly property real panelHeight: panel.panelHeight
4950
5051 property bool dashShown: dash.shown
@@ -130,6 +131,36 @@ FocusScope {
130131 }
131132 }
132133
134+ GSettings {
135+ id: backgroundSettings
136+ schema.id: "org.gnome.desktop.background"
137+ }
138+ property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
139+ onGSettingsPictureChanged: {
140+ shell.background = gSettingsPicture
141+ }
142+
143+ // This is a dummy image that is needed to determine if the picture url
144+ // in backgroundSettings points to a valid picture file.
145+ // We can't do this with the real background image because setting a
146+ // new source in onStatusChanged triggers a binding loop detection
147+ // inside Image, which causes it not to render even though a valid source
148+ // would be set. We don't mind about this image staying black and just
149+ // use it for verification to populate the source for the real
150+ // background image.
151+ Image {
152+ source: shell.background
153+ height: 0
154+ width: 0
155+ sourceSize.height: 0
156+ sourceSize.width: 0
157+ onStatusChanged: {
158+ if (status == Image.Error && source != shell.defaultBackground) {
159+ shell.background = defaultBackground
160+ }
161+ }
162+ }
163+
133164 VolumeControl {
134165 id: volumeControl
135166 }
@@ -154,12 +185,12 @@ FocusScope {
154185 // through the translucent parts of the shell surface.
155186 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
156187
157− Image {
188+ CrossFadeImage {
158189 id: backgroundImage
159− source: shell.background
160− sourceSize.width: parent.width
161− sourceSize.height: parent.height
190+ objectName: "backgroundImage"
191+
162192 anchors.fill: parent
193+ source: shell.background
163194 }
164195
165196 Rectangle {
@@ -248,7 +279,6 @@ FocusScope {
248279 }
249280 }
250281
251−
252282 Item {
253283 id: stagesOuterContainer
254284
@@ -313,7 +343,6 @@ FocusScope {
313343 ignoreUnknownSignals: true
314344 }
315345
316−
317346 Stage {
318347 id: mainStage
319348
@@ -491,9 +520,6 @@ FocusScope {
491520
492521 onUnlocked: greeter.hide()
493522 onSelected: {
494− // Update background
495− var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
496− shell.background = bgPath ? bgPath : default_background
497523 // Update edge demo hint
498524 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole)
499525 shell.showEdgeDemo = AccountsService.getUserProperty(user, "demo-edges")
debian/changelog+82
@@ -1,3 +1,85 @@
1+unity8 (7.81.3+13.10.20130820.2-0ubuntu1) saucy; urgency=low
2+
3+ [ Michael Zanetti ]
4+ * initial support for quicklists For now they support pinning and
5+ removing of items.
6+
7+ [ Ubuntu daily release ]
8+ * Automatic snapshot from revision 217
9+
10+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 20 Aug 2013 10:09:36 +0000
11+
12+unity8 (7.81.3+13.10.20130820-0ubuntu1) saucy; urgency=low
13+
14+ [ Andrea Cimitan ]
15+ * Implement background changing through gsettings. Make sure it does
16+ fallback to default background when needed.
17+
18+ [ Michael Zanetti ]
19+ * Implement background changing through gsettings. Make sure it does
20+ fallback to default background when needed.
21+ * Add Drag'n'drop support to Launcher As dragging an item pins it to
22+ the launcher this also contains initial quicklist and pinning
23+ support in the plugin part.
24+
25+ [ Michał Sawicz ]
26+ * Fix generic preview wrapping and force rich text parsing.
27+
28+ [ Michal Hruby ]
29+ * Remove the override for Apps available for download, click scope
30+ provides these now.
31+ * Hide rating widgets when scope provides rating set to < 0.0. Also
32+ fallback to regular preview image if there are no more_screenshots
33+ specified.
34+
35+ [ Albert Astals ]
36+ * Make sure minYExtent is updated before setting the new content
37+ height Otherwise bad things can happen in the positioning .
38+
39+ [ Ubuntu daily release ]
40+ * Automatic snapshot from revision 215
41+
42+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 20 Aug 2013 02:08:42 +0000
43+
44+unity8 (7.81.3+13.10.20130816.3-0ubuntu1) saucy; urgency=low
45+
46+ [ Michal Hruby ]
47+ * Implement overrideResults() method, which allows us to seamlessly
48+ combine real scope data with mocked data.
49+
50+ [ Michał Sawicz ]
51+ * Wait for activeFocus before typing in autopilot tests. (LP:
52+ #1212580)
53+
54+ [ Nick Dedekind ]
55+ * Fixed network indicator password dialog not appearing. (LP:
56+ #1212730)
57+ * Remove time & battery indicator service info.
58+
59+ [ Albert Astals ]
60+ * Dash category expansion.
61+ * Fix crash in the shell test Give the item a parent, otherwise the
62+ qml engine decides to adopt it and when we do the deleteLater on
63+ them, they have been already deleted. Since we are the parents, we
64+ don't need to call the deleteLAter eiether they'll be properly
65+ deleted on our deletion.
66+
67+ [ Michael Zanetti ]
68+ * make entering text in lockscreen tests more robust. (LP: #1212580)
69+
70+ [ Michael Terry ]
71+ * Define the 'build' target as PHONY so make doesn't get confused by
72+ our 'build' script.
73+ * Add a test for the Powerd plugin shell support.
74+
75+ [ Pawel Stolowski ]
76+ * Bindings for filters.
77+
78+ [ Ubuntu daily release ]
79+ * Automatic snapshot from revision 208
80+
81+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 16 Aug 2013 14:11:35 +0000
82+
183unity8 (7.81.3+13.10.20130814.3-0ubuntu1) saucy; urgency=low
284
385 [ Ted Gould ]
debian/control+3−1
@@ -62,8 +62,10 @@ Description: Indicators client test application
6262
6363Package: unity8
6464Architecture: any
65−Depends: qmenumodel-qml (>= 0.2.7),
65+Depends: gsettings-desktop-schemas,
66+ qmenumodel-qml (>= 0.2.7),
6667 qtdeclarative5-dee-plugin,
68+ qtdeclarative5-gsettings1.0,
6769 qtdeclarative5-ubuntu-ui-toolkit-plugin,
6870 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
6971 qtdeclarative5-xmllistmodel-plugin,
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+1
@@ -1244,6 +1244,7 @@ void ListViewWithPageHeader::updatePolish()
12441244 }
12451245
12461246 m_contentHeightDirty = false;
1247+ adjustMinYExtent();
12471248 setContentHeight(contentHeight);
12481249 }
12491250}
plugins/Unity/IndicatorsLegacy/flatmenuproxymodel.cpp+13
@@ -80,6 +80,19 @@ void FlatMenuProxyModel::setSourceModel(QAbstractItemModel *source)
8080 SIGNAL(modelReset()),
8181 SLOT(onModelReset()), Qt::QueuedConnection);
8282
83+ //Connection args
84+ connect(source,
85+ SIGNAL(busTypeChanged(DBusEnums::BusType)),
86+ SIGNAL(busTypeChanged()));
87+
88+ connect(source,
89+ SIGNAL(busNameChanged(const QString&)),
90+ SIGNAL(busNameChanged()));
91+
92+ connect(source,
93+ SIGNAL(objectPathChanged(const QString&)),
94+ SIGNAL(objectPathChanged()));
95+
8396 connect(source,
8497 SIGNAL(statusChanged(DBusEnums::ConnectionStatus)),
8598 SIGNAL(statusChanged()), Qt::QueuedConnection);
plugins/Unity/Launcher/common/quicklistentry.cpp+5
@@ -52,3 +52,8 @@ void QuickListEntry::setIcon(const QString &icon)
5252{
5353 m_icon = icon;
5454}
55+
56+bool QuickListEntry::clickable() const
57+{
58+ return !m_actionId.isEmpty();
59+}
plugins/Unity/Launcher/common/quicklistentry.h+2
@@ -35,6 +35,8 @@ public:
3535 QString icon() const;
3636 void setIcon(const QString &icon);
3737
38+ bool clickable() const;
39+
3840private:
3941 QString m_actionId;
4042 QString m_text;
plugins/Unity/Launcher/launcheritem.cpp+11−1
@@ -33,7 +33,13 @@ LauncherItem::LauncherItem(const QString &appId, const QString &desktopFile, con
3333 m_count(0),
3434 m_quickList(new QuickListModel(this))
3535{
36−
36+ QuickListEntry nameAction;
37+ nameAction.setText(m_name);
38+ m_quickList->appendAction(nameAction);
39+ QuickListEntry pinningAction;
40+ pinningAction.setActionId("pin_item");
41+ pinningAction.setText("Pin to Launcher");
42+ m_quickList->appendAction(pinningAction);
3743}
3844
3945QString LauncherItem::appId() const
@@ -65,6 +71,10 @@ void LauncherItem::setPinned(bool pinned)
6571{
6672 if (m_pinned != pinned) {
6773 m_pinned = pinned;
74+ QuickListEntry entry;
75+ entry.setActionId("pin_item");
76+ entry.setText(pinned ? "Remove from Launcher": "Pin to Launcher");
77+ m_quickList->updateAction(entry);
6878 Q_EMIT pinnedChanged(pinned);
6979 }
7080}
plugins/Unity/Launcher/launchermodel.cpp+34−4
@@ -82,8 +82,25 @@ unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) con
8282
8383void LauncherModel::move(int oldIndex, int newIndex)
8484{
85− // Perform the move in our list
86− beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
85+ // Make sure its not moved outside the lists
86+ if (newIndex < 0) {
87+ newIndex = 0;
88+ }
89+ if (newIndex >= m_list.count()) {
90+ newIndex = m_list.count()-1;
91+ }
92+
93+ // Nothing to do?
94+ if (oldIndex == newIndex) {
95+ return;
96+ }
97+
98+ // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
99+ // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
100+ // adjust the model's index by +1 in case we're moving upwards
101+ int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
102+
103+ beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
87104 m_list.move(oldIndex, newIndex);
88105 endMoveRows();
89106
@@ -142,10 +159,23 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
142159 return;
143160 }
144161
145− QuickListModel *model = qobject_cast<QuickListModel*>(m_list.at(index)->quickList());
162+ LauncherItem *item = m_list.at(index);
163+ QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
146164 if (model) {
147165 QString actionId = model->get(actionIndex).actionId();
148− m_backend->triggerQuickListAction(appId, actionId);
166+
167+ // Check if this is one of the launcher actions we handle ourselves
168+ if (actionId == "pin_item") {
169+ if (item->pinned()) {
170+ requestRemove(appId);
171+ } else {
172+ pin(appId);
173+ }
174+
175+ // Nope, we don't know this action, let the backend forward it to the application
176+ } else {
177+ m_backend->triggerQuickListAction(appId, actionId);
178+ }
149179 }
150180}
151181
plugins/Unity/Launcher/plugin.cpp+2
@@ -41,7 +41,9 @@ void UnityLauncherPlugin::registerTypes(const char *uri)
4141
4242 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
4343 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
44+ qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListInterface", "Abstract Interface. Cannot be instantiated.");
4445
4546 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
4647 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");
48+ qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", "Can't create a QuickListModel in QML. Get them from the LauncherItems.");
4749}
plugins/Unity/Launcher/quicklistmodel.cpp+13
@@ -37,6 +37,17 @@ void QuickListModel::appendAction(const QuickListEntry &entry)
3737 endInsertRows();
3838}
3939
40+void QuickListModel::updateAction(const QuickListEntry &entry)
41+{
42+ for (int i = 0; i < m_list.count(); ++i) {
43+ if (m_list.at(i).actionId() == entry.actionId()) {
44+ m_list.replace(i, entry);
45+ Q_EMIT dataChanged(index(i), index(i));
46+ return;
47+ }
48+ }
49+}
50+
4051QuickListEntry QuickListModel::get(int index) const
4152{
4253 return m_list.at(index);
@@ -55,6 +66,8 @@ QVariant QuickListModel::data(const QModelIndex &index, int role) const
5566 return m_list.at(index.row()).text();
5667 case RoleIcon:
5768 return m_list.at(index.row()).icon();
69+ case RoleClickable:
70+ return m_list.at(index.row()).clickable();
5871 }
5972 return QVariant();
6073}
plugins/Unity/Launcher/quicklistmodel.h+10
@@ -36,6 +36,16 @@ public:
3636 ~QuickListModel();
3737
3838 void appendAction(const QuickListEntry &entry);
39+
40+ /**
41+ * @brief Update an existing action
42+ * @param entry The new, updated entry
43+ *
44+ * This will only do something if entry.actionId is found in the model.
45+ * To add a new entry, use appendAction().
46+ */
47+ void updateAction(const QuickListEntry &entry);
48+
3949 QuickListEntry get(int index) const;
4050
4151 int rowCount(const QModelIndex &parent = QModelIndex()) const;
plugins/Unity/filters.cpp+4
@@ -23,6 +23,7 @@
2323
2424// Local
2525#include "filter.h"
26+#include "genericoptionsmodel.h"
2627
2728// Qt
2829#include <QDebug>
@@ -77,6 +78,8 @@ QVariant Filters::data(const QModelIndex& index, int role) const
7778 return filter->collapsed();
7879 case Filters::RoleFiltering:
7980 return filter->filtering();
81+ case Filters::RoleOptions:
82+ return QVariant::fromValue(filter->options());
8083 default:
8184 break;
8285 }
@@ -93,6 +96,7 @@ QHash<int, QByteArray> Filters::roleNames() const
9396 roles[Filters::RoleVisible] = "visible";
9497 roles[Filters::RoleCollapsed] = "collapsed";
9598 roles[Filters::RoleFiltering] = "filtering";
99+ roles[Filters::RoleOptions] = "options";
96100 return roles;
97101}
98102
plugins/Unity/filters.h+2−1
@@ -48,7 +48,8 @@ public:
4848 RoleRendererName,
4949 RoleVisible,
5050 RoleCollapsed,
51− RoleFiltering
51+ RoleFiltering,
52+ RoleOptions
5253 };
5354
5455 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
plugins/Unity/genericoptionsmodel.cpp+18
@@ -76,6 +76,24 @@ int GenericOptionsModel::rowCount(const QModelIndex& /* parent */) const
7676 return m_options.size ();
7777}
7878
79+void GenericOptionsModel::setActive(QVector<AbstractFilterOption *>::size_type idx, bool value)
80+{
81+ if (idx < m_options.size())
82+ {
83+ m_options[idx]->setActive(value);
84+ }
85+}
86+
87+void GenericOptionsModel::setActive(const QString &id, bool value)
88+{
89+ Q_FOREACH(auto opt, m_options) {
90+ if (opt->id() == id) {
91+ opt->setActive(value);
92+ break;
93+ }
94+ }
95+}
96+
7997AbstractFilterOption* GenericOptionsModel::getRawOption(QVector<AbstractFilterOption *>::size_type idx) const
8098{
8199 if (idx < m_options.size())
plugins/Unity/genericoptionsmodel.h+3
@@ -49,6 +49,9 @@ public:
4949 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
5050 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
5151
52+ Q_INVOKABLE void setActive(QVector<AbstractFilterOption *>::size_type idx, bool value);
53+ Q_INVOKABLE void setActive(const QString &id, bool value);
54+
5255 AbstractFilterOption* getRawOption(QVector<AbstractFilterOption *>::size_type idx) const;
5356
5457public Q_SLOTS:
src/Panel/Indicators/servicefiles/com.canonical.indicator.messages+1−1
@@ -1,5 +1,5 @@
11[Indicator Service]
2−Name=indicator-messaging
2+Name=indicator-messages
33ObjectPath=/com/canonical/indicator/messages
44Position=50
55
tests/data/unity/backgrounds/blue.pngnew file

Binary file not shown.

tests/data/unity/backgrounds/red.pngnew file

Binary file not shown.

tests/mocks/CMakeLists.txt+1
@@ -1,4 +1,5 @@
11add_subdirectory(AccountsService)
2+add_subdirectory(GSettings.1.0)
23add_subdirectory(HudClient)
34add_subdirectory(libusermetrics)
45add_subdirectory(LightDM)
tests/mocks/GSettings.1.0/CMakeLists.txtnew file+23
@@ -0,0 +1,23 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ ${Qt5Core_INCLUDE_DIRS}
4+ ${Qt5Quick_INCLUDE_DIRS}
5+)
6+
7+set(GSettingsQML_SOURCES
8+ fake_gsettings.cpp
9+ plugin.cpp
10+)
11+
12+add_library(FakeGSettingsQml MODULE ${GSettingsQML_SOURCES})
13+target_link_libraries(FakeGSettingsQml
14+ ${Qt5Core_LIBRARIES}
15+ ${Qt5Quick_LIBRARIES}
16+)
17+
18+qt5_use_modules(FakeGSettingsQml Qml)
19+
20+# copy qmldir file into build directory for shadow builds
21+file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
22+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
23+ )
tests/mocks/GSettings.1.0/fake_gsettings.cppnew file+102
@@ -0,0 +1,102 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "fake_gsettings.h"
18+
19+#include <QList>
20+
21+GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
22+
23+GSettingsControllerQml::GSettingsControllerQml() {
24+}
25+
26+GSettingsControllerQml::~GSettingsControllerQml() {
27+ s_controllerInstance = 0;
28+}
29+
30+GSettingsControllerQml* GSettingsControllerQml::instance() {
31+ if (!s_controllerInstance) {
32+ s_controllerInstance = new GSettingsControllerQml();
33+ }
34+ return s_controllerInstance;
35+}
36+
37+void GSettingsControllerQml::registerSettingsObject(GSettingsQml *obj) {
38+ m_registeredGSettings.append(obj);
39+}
40+
41+void GSettingsControllerQml::unRegisterSettingsObject(GSettingsQml *obj) {
42+ m_registeredGSettings.removeOne(obj);
43+}
44+
45+void GSettingsControllerQml::setPictureUri(const QString &str) {
46+ Q_FOREACH (GSettingsQml *obj, m_registeredGSettings) {
47+ obj->setPictureUri(str);
48+ }
49+}
50+
51+GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
52+}
53+
54+QByteArray GSettingsSchemaQml::id() const {
55+ return m_id;
56+}
57+
58+void GSettingsSchemaQml::setId(const QByteArray &id) {
59+ if (m_id.isEmpty()) {
60+ qWarning("GSettings.schema.id may only be set on construction");
61+ return;
62+ }
63+
64+ m_id = id;
65+}
66+
67+QByteArray GSettingsSchemaQml::path() const {
68+ return m_path;
69+}
70+
71+void GSettingsSchemaQml::setPath(const QByteArray &path) {
72+ if (m_path.isEmpty()) {
73+ qWarning("GSettings.schema.path may only be set on construction");
74+ return;
75+ }
76+
77+ m_path = path;
78+}
79+
80+GSettingsQml::GSettingsQml(QObject *parent): QObject(parent) {
81+ m_schema = new GSettingsSchemaQml(this);
82+ GSettingsControllerQml::instance()->registerSettingsObject(this);
83+}
84+
85+GSettingsQml::~GSettingsQml() {
86+ GSettingsControllerQml::instance()->unRegisterSettingsObject(this);
87+}
88+
89+GSettingsSchemaQml * GSettingsQml::schema() const {
90+ return m_schema;
91+}
92+
93+QString GSettingsQml::pictureUri() const {
94+ return m_pictureUri;
95+}
96+
97+void GSettingsQml::setPictureUri(const QString &str) {
98+ if (str != m_pictureUri) {
99+ m_pictureUri = str;
100+ Q_EMIT pictureUriChanged(m_pictureUri);
101+ }
102+}
tests/mocks/GSettings.1.0/fake_gsettings.hnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef FAKE_GSETTINGS_H
18+#define FAKE_GSETTINGS_H
19+
20+#include <QList>
21+#include <QtQml>
22+
23+class GSettingsSchemaQml: public QObject
24+{
25+ Q_OBJECT
26+
27+ Q_PROPERTY(QByteArray id READ id WRITE setId)
28+
29+public:
30+ GSettingsSchemaQml(QObject *parent = NULL);
31+
32+ QByteArray id() const;
33+ void setId(const QByteArray &id);
34+
35+ QByteArray path() const;
36+ void setPath(const QByteArray &path);
37+
38+private:
39+ QByteArray m_id;
40+ QByteArray m_path;
41+};
42+
43+class GSettingsQml: public QObject
44+{
45+ Q_OBJECT
46+
47+ Q_PROPERTY(GSettingsSchemaQml* schema READ schema NOTIFY schemaChanged)
48+ Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
49+
50+public:
51+ GSettingsQml(QObject *parent = NULL);
52+ ~GSettingsQml();
53+
54+ GSettingsSchemaQml * schema() const;
55+ QString pictureUri() const;
56+
57+ void setPictureUri(const QString &str);
58+
59+Q_SIGNALS:
60+ void schemaChanged();
61+ void pictureUriChanged(const QString&);
62+
63+private:
64+ GSettingsSchemaQml* m_schema;
65+
66+ QString m_pictureUri;
67+
68+ friend class GSettingsSchemaQml;
69+};
70+
71+class GSettingsControllerQml: public QObject
72+{
73+ Q_OBJECT
74+
75+public:
76+ static GSettingsControllerQml* instance();
77+ ~GSettingsControllerQml();
78+
79+ void registerSettingsObject(GSettingsQml *obj);
80+ void unRegisterSettingsObject(GSettingsQml *obj);
81+ Q_INVOKABLE void setPictureUri(const QString &str);
82+
83+private:
84+ GSettingsControllerQml();
85+
86+ static GSettingsControllerQml* s_controllerInstance;
87+ QList<GSettingsQml *> m_registeredGSettings;
88+};
89+
90+#endif // FAKE_GSETTINGS_H
tests/mocks/GSettings.1.0/plugin.cppnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "plugin.h"
18+#include "fake_gsettings.h"
19+
20+#include <QtQml>
21+
22+static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23+{
24+ return GSettingsControllerQml::instance();
25+}
26+
27+void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
28+{
29+ qmlRegisterSingletonType<GSettingsControllerQml>(uri, 1, 0, "GSettingsController", controllerProvider);
30+ qmlRegisterType<GSettingsQml>(uri, 1, 0, "GSettings");
31+ qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
32+ "GSettingsSchema can only be used inside of a GSettings component");
33+}
tests/mocks/GSettings.1.0/plugin.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef PLUGIN_H
18+#define PLUGIN_H
19+
20+#include <QtQml/QQmlExtensionPlugin>
21+
22+class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
23+{
24+ Q_OBJECT
25+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26+
27+public:
28+ void registerTypes(const char *uri);
29+};
30+
31+#endif // PLUGIN_H
tests/mocks/GSettings.1.0/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module GSettings
2+plugin FakeGSettingsQml
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+2−2
@@ -31,8 +31,8 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
3131 m_pinned(false),
3232 m_running(false),
3333 m_recent(false),
34− m_progress(8),
35− m_count(8),
34+ m_progress(-1),
35+ m_count(0),
3636 m_quickList(new MockQuickListModel(this))
3737{
3838
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+32−4
@@ -23,22 +23,32 @@
2323MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(parent)
2424{
2525 MockLauncherItem *item = new MockLauncherItem("phone-app", "/usr/share/applications/phone-app.desktop", "Phone", "phone-app", this);
26+ item->setProgress(0);
2627 m_list.append(item);
2728 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
29+ item->setProgress(10);
2830 m_list.append(item);
2931 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
32+ item->setProgress(50);
3033 m_list.append(item);
3134 item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
35+ item->setProgress(150);
3236 m_list.append(item);
3337 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
38+ item->setCount(1);
3439 m_list.append(item);
3540 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
41+ item->setCount(12);
3642 m_list.append(item);
3743 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
44+ item->setCount(123);
3845 m_list.append(item);
3946 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
47+ item->setCount(1234567890);
4048 m_list.append(item);
4149 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
50+ item->setProgress(50);
51+ item->setCount(5);
4252 m_list.append(item);
4353 item = new MockLauncherItem("calendar-app", "/usr/share/applications/calendar-app.desktop","Calendar", "calendar", this);
4454 m_list.append(item);
@@ -91,9 +101,29 @@ unity::shell::launcher::LauncherItemInterface *MockLauncherModel::get(int index)
91101
92102void MockLauncherModel::move(int oldIndex, int newIndex)
93103{
94− beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
104+ // Make sure its not moved outside the lists
105+ if (newIndex < 0) {
106+ newIndex = 0;
107+ }
108+ if (newIndex >= m_list.count()) {
109+ newIndex = m_list.count()-1;
110+ }
111+
112+ // Nothing to do?
113+ if (oldIndex == newIndex) {
114+ return;
115+ }
116+
117+ // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
118+ // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
119+ // adjust the model's index by +1 in case we're moving upwards
120+ int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
121+
122+ beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
95123 m_list.move(oldIndex, newIndex);
96124 endMoveRows();
125+
126+ pin(m_list.at(newIndex)->appId());
97127}
98128
99129void MockLauncherModel::pin(const QString &appId, int index)
@@ -131,9 +161,7 @@ void MockLauncherModel::requestRemove(const QString &appId)
131161
132162void MockLauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
133163{
134− Q_UNUSED(appId)
135− Q_UNUSED(actionIndex)
136− // Nothing to mock yet...
164+ Q_EMIT quickListTriggered(appId, actionIndex);
137165}
138166
139167int MockLauncherModel::findApp(const QString &appId)
tests/mocks/Unity/Launcher/MockLauncherModel.h+3
@@ -44,6 +44,9 @@ public:
4444 Q_INVOKABLE void requestRemove(const QString &appId);
4545 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
4646
47+Q_SIGNALS:
48+ void quickListTriggered(const QString &appId, int index);
49+
4750private:
4851 int findApp(const QString &appId);
4952
tests/mocks/Unity/Launcher/MockQuickListModel.cpp+2
@@ -35,6 +35,8 @@ QVariant MockQuickListModel::data(const QModelIndex &index, int role) const
3535 return QLatin1String("test menu entry ") + QString::number(index.row());
3636 case RoleIcon:
3737 return QLatin1String("copy.png");
38+ case RoleClickable:
39+ return index.row() == 1 ? false : true;
3840 }
3941 return QVariant();
4042}
tests/mocks/Unity/Launcher/plugin.cpp+3
@@ -20,6 +20,7 @@
2020#include "plugin.h"
2121#include "MockLauncherModel.h"
2222#include "MockLauncherItem.h"
23+#include "MockQuickListModel.h"
2324
2425#include <unity/shell/launcher/LauncherModelInterface.h>
2526#include <unity/shell/launcher/LauncherItemInterface.h>
@@ -39,7 +40,9 @@ void TestLauncherPlugin::registerTypes(const char* uri)
3940 // @uri Unity.Launcher
4041 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
4142 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
43+ qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListModelInterface", "Abstract Interface. Cannot be instantiated.");
4244
4345 qmlRegisterSingletonType<MockLauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
4446 qmlRegisterUncreatableType<MockLauncherItem>(uri, 0, 1, "LauncherItem", "Can't create LauncherItems in QML. Get them from the LauncherModel");
47+ qmlRegisterUncreatableType<MockQuickListModel>(uri, 0, 1, "QuickListModel", "Can't create QuickLists in QML. Get them from the LauncherItems");
4548}
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+22−1
@@ -19,6 +19,7 @@
1919#include <QAbstractItemModel>
2020#include <QQmlEngine>
2121#include <QQuickView>
22+#include <QSignalSpy>
2223#include <QtTestGui>
2324#pragma GCC diagnostic push
2425#pragma GCC diagnostic ignored "-pedantic"
@@ -1521,7 +1522,7 @@ private Q_SLOTS:
15211522 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
15221523 verifyItem(0, -458., 1000., false);
15231524 verifyItem(1, 542., 350., true);
1524− QCOMPARE(lvwph->m_minYExtent, 362.5);
1525+ QCOMPARE(lvwph->m_minYExtent, 525.);
15251526 QCOMPARE(lvwph->m_clipItem->y(), 658.);
15261527 QCOMPARE(lvwph->m_clipItem->clip(), false);
15271528 QCOMPARE(lvwph->m_headerItem->y(), 0.);
@@ -1634,6 +1635,26 @@ private Q_SLOTS:
16341635 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
16351636 }
16361637
1638+ void addingRemoveItemsShouldNotChangeContentY()
1639+ {
1640+ QSignalSpy spy(lvwph, SIGNAL(contentYChanged()));
1641+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 150));
1642+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 6));
1643+
1644+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 1);
1645+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
1646+ verifyItem(0, 50., 150., false);
1647+ QCOMPARE(lvwph->m_minYExtent, 0.);
1648+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
1649+ QCOMPARE(lvwph->m_clipItem->clip(), false);
1650+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
1651+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
1652+ QCOMPARE(lvwph->contentY(), 0.);
1653+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1654+
1655+ QCOMPARE(spy.count(), 0);
1656+ }
1657+
16371658private:
16381659 QQuickView *view;
16391660 ListViewWithPageHeader *lvwph;
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp+22−1
@@ -19,6 +19,7 @@
1919#include <QAbstractItemModel>
2020#include <QQmlEngine>
2121#include <QQuickView>
22+#include <QSignalSpy>
2223#include <QtTestGui>
2324#pragma GCC diagnostic push
2425#pragma GCC diagnostic ignored "-pedantic"
@@ -1875,7 +1876,7 @@ private Q_SLOTS:
18751876 QCOMPARE(lvwph->m_firstVisibleIndex, 1);
18761877 verifyItem(0, -498., 1040., false, "Regular", true);
18771878 verifyItem(1, 542., 390., true, "Mild", true);
1878− QCOMPARE(lvwph->m_minYExtent, 1250/3.);
1879+ QCOMPARE(lvwph->m_minYExtent, 525.);
18791880 QCOMPARE(lvwph->m_clipItem->y(), 738.);
18801881 QCOMPARE(lvwph->m_clipItem->clip(), false);
18811882 QCOMPARE(lvwph->m_headerItem->y(), 0.);
@@ -2055,6 +2056,26 @@ private Q_SLOTS:
20552056 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
20562057 }
20572058
2059+ void addingRemoveItemsShouldNotChangeContentY()
2060+ {
2061+ QSignalSpy spy(lvwph, SIGNAL(contentYChanged()));
2062+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 150), Q_ARG(QVariant, "Agressive"));
2063+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 6));
2064+
2065+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 1);
2066+ QCOMPARE(lvwph->m_firstVisibleIndex, 0);
2067+ verifyItem(0, 50., 190., false, "Agressive", false);
2068+ QCOMPARE(lvwph->m_minYExtent, 0.);
2069+ QCOMPARE(lvwph->m_clipItem->y(), 0.);
2070+ QCOMPARE(lvwph->m_clipItem->clip(), false);
2071+ QCOMPARE(lvwph->m_headerItem->y(), 0.);
2072+ QCOMPARE(lvwph->m_headerItem->height(), 50.);
2073+ QCOMPARE(lvwph->contentY(), 0.);
2074+ QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
2075+
2076+ QCOMPARE(spy.count(), 0);
2077+ }
2078+
20582079private:
20592080 QQuickView *view;
20602081 ListViewWithPageHeader *lvwph;
tests/qmltests/CMakeLists.txt+1−1
@@ -26,7 +26,7 @@ set(qmltest_DEFAULT_TARGETS qmluitests)
2626set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
2727set(qmltest_DEFAULT_PROPERTIES "")
2828
29−add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS}
29+add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks
3030 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
3131add_qml_test(Components DemoOverlay)
3232add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/qmltests/Dash/Apps/tst_AppPreview.qml+17−3
@@ -35,7 +35,7 @@ Item {
3535
3636 property string commentary: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a mi vitae augue rhoncus lobortis ut rutrum metus. Curabitur tortor leo, tristique sed mollis quis, condimentum venenatis nibh.";
3737 QtObject { id: screenshots; property var value: ["fake_image1.png", "fake_image2.png", "fake_image3.png"] }
38− QtObject { id: rating; property int value: 8 }
38+ QtObject { id: rating; property real value: 0.8 }
3939 QtObject { id: rated; property int value: 120 }
4040 QtObject { id: reviews; property int value: 8 }
4141 QtObject { id: progress; property string value: "source" }
@@ -51,12 +51,12 @@ Item {
5151 property string title: "Unity App"
5252 property string appIcon: "fake_image.png"
5353 property string description: "This is an Application description"
54+ property real rating: rating.value
55+ property int numRatings: reviews.value
5456 property var execute: root.fake_call
5557 property var infoMap: {
5658 "more-screenshots": screenshots,
57− "rating": rating,
5859 "rated": rated,
59− "reviews": reviews,
6060 "comments": comments
6161 }
6262 property var actions: [
@@ -88,6 +88,7 @@ Item {
8888 var reviewField = findChild(appPreview, "reviewField");
8989 reviewField.focus = false;
9090 reviewField.text = "";
91+ data.rating = rating.value;
9192 }
9293
9394 function test_actions() {
@@ -148,5 +149,18 @@ Item {
148149 compare(comment, comments.value[i][3], "Comment don't match");
149150 }
150151 }
152+
153+ function test_negative_rating() {
154+ data.rating = -1.0;
155+ var rated = findChild(appPreview, "reviewsLabel");
156+ verify(rated.visible == false);
157+ var commentsArea = findChild(appPreview, "commentsArea");
158+ verify(commentsArea.visible == false);
159+ var appReviews = findChild(appPreview, "appReviews");
160+ verify(appReviews.visible == false);
161+ var buttons = findChild(appPreview, "gridButtons");
162+ verify(buttons.visible == true);
163+ }
164+
151165 }
152166}
tests/qmltests/Launcher/tst_Launcher.qml+155
@@ -19,6 +19,7 @@ import QtTest 1.0
1919import Unity.Test 0.1 as UT
2020import ".."
2121import "../../../Launcher"
22+import Unity.Launcher 0.1
2223
2324/* Nothing is shown at first. If you drag from left edge you will bring up the
2425 launcher. */
@@ -48,6 +49,11 @@ Item {
4849 property int maxPanelX: 0
4950 }
5051
52+ SignalSpy {
53+ id: signalSpy
54+ target: LauncherModel
55+ }
56+
5157 Connections {
5258 target: testCase.findChild(launcher, "launcherPanel")
5359
@@ -176,6 +182,22 @@ Item {
176182 revealer.waitUntilLauncherDisappears();
177183 launcher.available = true;
178184 }
185+
186+ function test_countEmblems() {
187+ var launcherListView = findChild(launcher, "launcherListView");
188+ for (var i = 0; i < launcherListView.count; ++i) {
189+ var delegate = findChild(launcherListView, "launcherDelegate" + i)
190+ compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).count > 0)
191+ }
192+ }
193+
194+ function test_progressOverlays() {
195+ var launcherListView = findChild(launcher, "launcherListView");
196+ for (var i = 0; i < launcherListView.count; ++i) {
197+ var delegate = findChild(launcherListView, "launcherDelegate" + i)
198+ compare(findChild(delegate, "progressOverlay").visible, LauncherModel.get(i).progress >= 0)
199+ }
200+ }
179201 }
180202
181203 UT.UnityTestCase {
@@ -247,4 +269,137 @@ Item {
247269 }
248270 */
249271 }
272+
273+ UT.UnityTestCase {
274+ id: dndTestCase
275+ name: "Drag and Drop"
276+ when: windowShown && initTestCase.completed
277+
278+ function test_dragndrop_data() {
279+ return [
280+ {tag: "startDrag", fullDrag: false},
281+ {tag: "fullDrag", fullDrag: true},
282+ ];
283+ }
284+
285+ function test_dragndrop(data) {
286+ revealer.dragLauncherIntoView();
287+ var draggedItem = findChild(launcher, "launcherDelegate5")
288+ var item0 = findChild(launcher, "launcherDelegate0")
289+ var fakeDragItem = findChild(launcher, "fakeDragItem")
290+ var initialItemHeight = draggedItem.height
291+ var item5Name = LauncherModel.get(5).iconName
292+ var item4Name = LauncherModel.get(4).iconName
293+
294+ // Initial state
295+ compare(draggedItem.itemOpacity, 1, "Item's opacity is not 1 at beginning")
296+ compare(fakeDragItem.visible, false, "FakeDragItem isn't invisible at the beginning")
297+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
298+
299+ // Doing longpress
300+ mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
301+ // DraggedItem needs to hide and fakeDragItem become visible
302+ tryCompare(draggedItem, "itemOpacity", 0)
303+ tryCompare(fakeDragItem, "visible", true)
304+
305+ // Dragging a bit (> 1.5 gu)
306+ mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
307+ // Other items need to expand and become 0.6 opaque
308+ tryCompare(item0, "angle", 0)
309+ tryCompare(item0, "itemOpacity", 0.6)
310+
311+ if (data.fullDrag) {
312+ // Dragging a bit more
313+ mouseMove(draggedItem, -units.gu(15), draggedItem.height / 2, 100)
314+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
315+ tryCompare(draggedItem, "height", units.gu(1))
316+
317+ // Dragging downwards. Item needs to move in the model
318+ mouseMove(draggedItem, -units.gu(15), -initialItemHeight)
319+ waitForRendering(draggedItem)
320+ compare(LauncherModel.get(5).iconName, item4Name)
321+ compare(LauncherModel.get(5).iconName, item5Name)
322+ }
323+
324+ // Releasing and checking if initial values are restored
325+ mouseRelease(draggedItem)
326+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
327+ tryCompare(draggedItem, "itemOpacity", 1)
328+ tryCompare(fakeDragItem, "visible", false)
329+ }
330+
331+ function test_quicklist_dismiss() {
332+ revealer.dragLauncherIntoView();
333+ var draggedItem = findChild(launcher, "launcherDelegate5")
334+ var item0 = findChild(launcher, "launcherDelegate0")
335+ var fakeDragItem = findChild(launcher, "fakeDragItem")
336+ var dndArea = findChild(launcher, "dndArea")
337+
338+ // Initial state
339+ compare(dndArea.quickListPopover, null)
340+
341+ // Doing longpress
342+ mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
343+ tryCompare(fakeDragItem, "visible", true) // Wait longpress happening
344+ verify(dndArea.quickListPopover != null)
345+
346+ // Dragging a bit (> 1.5 gu)
347+ mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
348+
349+ // QuickList needs to be closed when a drag operation starts
350+ tryCompare(dndArea, "quickListPopover", null)
351+
352+ mouseRelease(draggedItem);
353+ }
354+
355+ function test_quicklist_click_data() {
356+ return [
357+ {tag: "non-clickable", index: 1, clickable: false },
358+ {tag: "clickable", index: 2, clickable: true },
359+ ];
360+ }
361+
362+ function test_quicklist_click(data) {
363+ revealer.dragLauncherIntoView();
364+ var clickedItem = findChild(launcher, "launcherDelegate5")
365+ var dndArea = findChild(launcher, "dndArea")
366+
367+ // Initial state
368+ compare(dndArea.quickListPopover, null)
369+
370+ // Doing longpress
371+ mousePress(clickedItem, clickedItem.width / 2, clickedItem.height / 2)
372+ tryCompare(clickedItem, "itemOpacity", 0) // Wait for longpress to happen
373+ verify(dndArea.quickListPopover != null)
374+
375+ mouseRelease(clickedItem);
376+
377+ var quickListEntry = findChild(dndArea.quickListPopover, "quickListEntry" + data.index)
378+
379+ signalSpy.clear();
380+ signalSpy.signalName = "quickListTriggered"
381+
382+ mouseClick(quickListEntry, quickListEntry.width / 2, quickListEntry.height / 2)
383+
384+ if (data.clickable) {
385+ // QuickList needs to be closed when some clickable item is clicked
386+ tryCompare(dndArea, "quickListPopover", null)
387+
388+ compare(signalSpy.count, 1, "Quicklist signal wasn't triggered")
389+ compare(signalSpy.signalArguments[0][0], LauncherModel.get(5).appId)
390+ compare(signalSpy.signalArguments[0][1], 2)
391+
392+ } else {
393+
394+ // QuickList must not be closed when a non-clickable item is clicked
395+ verify(dndArea.quickListPopover != null)
396+
397+ compare(signalSpy.count, 0, "Quicklist signal must NOT be triggered when clicking a non-clickable item")
398+
399+ // Click somewhere in the empty space to dismiss the quicklist
400+ mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
401+ tryCompare(dndArea, "quickListPopover", null)
402+ }
403+ }
404+ }
250405}
tests/qmltests/tst_Shell.qml+17
@@ -19,6 +19,7 @@
1919
2020import QtQuick 2.0
2121import QtTest 1.0
22+import GSettings 1.0
2223import Ubuntu.Application 0.1
2324import Unity.Test 0.1 as UT
2425import Powerd 0.1
@@ -315,5 +316,21 @@ Item {
315316 }
316317 } while (!isStill);
317318 }
319+
320+ function test_wallpaper_data() {
321+ return [
322+ {tag: "red", url: "tests/data/unity/backgrounds/red.png", expectedUrl: "tests/data/unity/backgrounds/red.png"},
323+ {tag: "blue", url: "tests/data/unity/backgrounds/blue.png", expectedUrl: "tests/data/unity/backgrounds/blue.png"},
324+ {tag: "invalid", url: "invalid", expectedUrl: shell.defaultBackground},
325+ {tag: "empty", url: "", expectedUrl: shell.defaultBackground}
326+ ]
327+ }
328+
329+ function test_wallpaper(data) {
330+ var backgroundImage = findChild(shell, "backgroundImage")
331+ GSettingsController.setPictureUri(data.url)
332+ tryCompareFunction(function() { return backgroundImage.source.toString().indexOf(data.expectedUrl) !== -1; }, true)
333+ tryCompare(backgroundImage, "status", Image.Ready)
334+ }
318335 }
319336}