on xenial help sign in
Implemented alert/wiggle feature for launcher-icons.
da0fcd1Mirco Müller
debian/control+3−3
@@ -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.97),
33+ libunity-api-dev (>= 7.98),
34 libusermetricsoutput1-dev,
34 libusermetricsoutput1-dev,
35 libxcb1-dev,
35 libxcb1-dev,
36 pkg-config,
36 pkg-config,
@@ -101,7 +101,7 @@ Depends: gsettings-desktop-schemas,
101 qtdeclarative5-ubuntu-telephony0.1,
101 qtdeclarative5-ubuntu-telephony0.1,
102 qtdeclarative5-ubuntu-web-plugin,
102 qtdeclarative5-ubuntu-web-plugin,
103 ubuntu-system-settings,
103 ubuntu-system-settings,
104− unity-launcher-impl-4,
104+ unity-launcher-impl-7,
105 unity8-common (= ${source:Version}),
105 unity8-common (= ${source:Version}),
106 unity8-private (= ${binary:Version}),
106 unity8-private (= ${binary:Version}),
107 unity8-private | unity-launcher-impl,
107 unity8-private | unity-launcher-impl,
@@ -191,7 +191,7 @@ Depends: accountsservice-ubuntu-schemas (>= 0.0.3),
191 ${misc:Depends},
191 ${misc:Depends},
192 ${shlibs:Depends},
192 ${shlibs:Depends},
193Provides: unity-launcher-impl,
193Provides: unity-launcher-impl,
194− unity-launcher-impl-4,
194+ unity-launcher-impl-7,
195Description: Unity 8 private libs
195Description: Unity 8 private libs
196 The Unity 8 shell is the primary user interface for Ubuntu devices.
196 The Unity 8 shell is the primary user interface for Ubuntu devices.
197 .
197 .
plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
4
plugins/Greeter/Unity/Launcher/launcheritem.cpp+14
@@ -31,6 +31,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
31 m_count(0),
31 m_count(0),
32 m_countVisible(false),
32 m_countVisible(false),
33 m_focused(false),
33 m_focused(false),
34+ m_alerting(false),
34 m_quickList(new QuickListModel(this))
35 m_quickList(new QuickListModel(this))
35{
36{
36 QuickListEntry nameAction;
37 QuickListEntry nameAction;
@@ -165,6 +166,19 @@ void LauncherItem::setFocused(bool focused)
165 }
166 }
166}
167}
167
168
169+bool LauncherItem::alerting() const
170+{
171+ return m_alerting;
172+}
173+
174+void LauncherItem::setAlerting(bool alerting)
175+{
176+ if (m_alerting != alerting) {
177+ m_alerting = alerting;
178+ Q_EMIT alertingChanged(alerting);
179+ }
180+}
181+
168unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
182unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
169{
183{
170 return m_quickList;
184 return m_quickList;
plugins/Greeter/Unity/Launcher/launcheritem.h+4
@@ -41,6 +41,7 @@ public:
41 int count() const override;
41 int count() const override;
42 bool countVisible() const override;
42 bool countVisible() const override;
43 bool focused() const override;
43 bool focused() const override;
44+ bool alerting() const override;
44
45
45 unity::shell::launcher::QuickListModelInterface *quickList() const override;
46 unity::shell::launcher::QuickListModelInterface *quickList() const override;
46
47
@@ -54,6 +55,8 @@ private:
54 void setCount(int count);
55 void setCount(int count);
55 void setCountVisible(bool countVisible);
56 void setCountVisible(bool countVisible);
56 void setFocused(bool focused);
57 void setFocused(bool focused);
58+ void setAlerting(bool alerting);
59+
57
60
58private:
61private:
59 QString m_appId;
62 QString m_appId;
@@ -66,6 +69,7 @@ private:
66 int m_count;
69 int m_count;
67 bool m_countVisible;
70 bool m_countVisible;
68 bool m_focused;
71 bool m_focused;
72+ bool m_alerting;
69 QuickListModel *m_quickList;
73 QuickListModel *m_quickList;
70
74
71 friend class LauncherModel;
75 friend class LauncherModel;
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+7
@@ -72,6 +72,13 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
72 return QVariant();
72 return QVariant();
73}
73}
74
74
75+void LauncherModel::setAlerting(const QString &appId, bool alerting)
76+{
77+ Q_UNUSED(appId)
78+ Q_UNUSED(alerting)
79+ qWarning() << "This is a read only implementation. Cannot set alert-state of items.";
80+}
81+
75unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
82unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
76{
83{
77 if (index < 0 || index >= m_list.count()) {
84 if (index < 0 || index >= m_list.count()) {
plugins/Greeter/Unity/Launcher/launchermodelas.h+1
@@ -41,6 +41,7 @@ public:
41
41
42 QVariant data(const QModelIndex &index, int role) const override;
42 QVariant data(const QModelIndex &index, int role) const override;
43
43
44+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
44 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
45 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
45 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
46 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
46 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
47 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
4
plugins/Unity/Launcher/dbusinterface.cpp+26−8
@@ -62,6 +62,7 @@ QString DBusInterface::introspect(const QString &path) const
62 "<interface name=\"com.canonical.Unity.Launcher.Item\">"
62 "<interface name=\"com.canonical.Unity.Launcher.Item\">"
63 "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
63 "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
64 "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
64 "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
65+ "<method name=\"Alert\" />"
65 "</interface>";
66 "</interface>";
66 return nodeiface;
67 return nodeiface;
67}
68}
@@ -122,6 +123,20 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
122 return false;
123 return false;
123 }
124 }
124
125
126+ /* Break down the path to just the app id */
127+ bool validpath = true;
128+ QString pathtemp = message.path();
129+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
130+ validpath = false;
131+ }
132+ pathtemp.remove("/com/canonical/Unity/Launcher/");
133+ if (pathtemp.indexOf('/') >= 0) {
134+ validpath = false;
135+ }
136+
137+ /* Find ourselves an appid */
138+ QString appid = decodeAppId(pathtemp);
139+
125 // First handle methods of the Launcher interface
140 // First handle methods of the Launcher interface
126 if (message.interface() == "com.canonical.Unity.Launcher") {
141 if (message.interface() == "com.canonical.Unity.Launcher") {
127 if (message.member() == "Refresh") {
142 if (message.member() == "Refresh") {
@@ -129,6 +144,13 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
129 Q_EMIT refreshCalled();
144 Q_EMIT refreshCalled();
130 return connection.send(reply);
145 return connection.send(reply);
131 }
146 }
147+ } else if (message.interface() == "com.canonical.Unity.Launcher.Item") {
148+ // Handle methods of the Launcher-Item interface
149+ if (message.member() == "Alert" && validpath) {
150+ QDBusMessage reply = message.createReply();
151+ Q_EMIT alertCalled(appid);
152+ return connection.send(reply);
153+ }
132 }
154 }
133
155
134 // Now handle dynamic properties (for launcher emblems)
156 // Now handle dynamic properties (for launcher emblems)
@@ -136,22 +158,18 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
136 return false;
158 return false;
137 }
159 }
138
160
139− if (message.member() != "GetAll" && message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
161+ if (message.member() == "Get" && (message.arguments().count() != 2 || message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item")) {
140 return false;
162 return false;
141 }
163 }
142
164
143− /* Break down the path to just the app id */
165+ if (message.member() == "Set" && (message.arguments().count() != 3 || message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item")) {
144− QString pathtemp = message.path();
145− if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
146 return false;
166 return false;
147 }
167 }
148− pathtemp.remove("/com/canonical/Unity/Launcher/");
168+
149− if (pathtemp.indexOf('/') >= 0) {
169+ if (!validpath) {
150 return false;
170 return false;
151 }
171 }
152
172
153− /* Find ourselves an appid */
154− QString appid = decodeAppId(pathtemp);
155 int index = m_launcherModel->findApplication(appid);
173 int index = m_launcherModel->findApplication(appid);
156 LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
174 LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
157
175
plugins/Unity/Launcher/dbusinterface.h+1
@@ -37,6 +37,7 @@ Q_SIGNALS:
37 void countChanged(const QString &appId, int count);
37 void countChanged(const QString &appId, int count);
38 void countVisibleChanged(const QString &appId, bool countVisible);
38 void countVisibleChanged(const QString &appId, bool countVisible);
39 void refreshCalled();
39 void refreshCalled();
40+ void alertCalled(const QString &appId);
40
41
41private:
42private:
42 static QString decodeAppId(const QString& path);
43 static QString decodeAppId(const QString& path);
plugins/Unity/Launcher/launcheritem.cpp+23
@@ -34,6 +34,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
34 m_count(0),
34 m_count(0),
35 m_countVisible(false),
35 m_countVisible(false),
36 m_focused(false),
36 m_focused(false),
37+ m_alerting(false),
37 m_quickList(new QuickListModel(this))
38 m_quickList(new QuickListModel(this))
38{
39{
39 QuickListEntry nameAction;
40 QuickListEntry nameAction;
@@ -150,6 +151,9 @@ void LauncherItem::setCount(int count)
150 if (m_count != count) {
151 if (m_count != count) {
151 m_count = count;
152 m_count = count;
152 Q_EMIT countChanged(count);
153 Q_EMIT countChanged(count);
154+ if (m_countVisible) {
155+ setAlerting(true);
156+ }
153 }
157 }
154}
158}
155
159
@@ -163,6 +167,9 @@ void LauncherItem::setCountVisible(bool countVisible)
163 if (m_countVisible != countVisible) {
167 if (m_countVisible != countVisible) {
164 m_countVisible = countVisible;
168 m_countVisible = countVisible;
165 Q_EMIT countVisibleChanged(countVisible);
169 Q_EMIT countVisibleChanged(countVisible);
170+ if (countVisible) {
171+ setAlerting(true);
172+ }
166 }
173 }
167}
174}
168
175
@@ -175,10 +182,26 @@ void LauncherItem::setFocused(bool focused)
175{
182{
176 if (m_focused != focused) {
183 if (m_focused != focused) {
177 m_focused = focused;
184 m_focused = focused;
185+ if (focused) {
186+ setAlerting(false);
187+ }
178 Q_EMIT focusedChanged(focused);
188 Q_EMIT focusedChanged(focused);
179 }
189 }
180}
190}
181
191
192+bool LauncherItem::alerting() const
193+{
194+ return m_alerting;
195+}
196+
197+void LauncherItem::setAlerting(bool alerting)
198+{
199+ if (m_alerting != alerting) {
200+ m_alerting = alerting;
201+ Q_EMIT alertingChanged(alerting);
202+ }
203+}
204+
182unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
205unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
183{
206{
184 return m_quickList;
207 return m_quickList;
plugins/Unity/Launcher/launcheritem.h+3
@@ -44,6 +44,7 @@ public:
44 int count() const override;
44 int count() const override;
45 bool countVisible() const override;
45 bool countVisible() const override;
46 bool focused() const override;
46 bool focused() const override;
47+ bool alerting() const override;
47
48
48 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49
50
@@ -57,6 +58,7 @@ private:
57 void setCount(int count);
58 void setCount(int count);
58 void setCountVisible(bool countVisible);
59 void setCountVisible(bool countVisible);
59 void setFocused(bool focused);
60 void setFocused(bool focused);
61+ void setAlerting(bool alerting);
60
62
61private:
63private:
62 QString m_appId;
64 QString m_appId;
@@ -69,6 +71,7 @@ private:
69 int m_count;
71 int m_count;
70 bool m_countVisible;
72 bool m_countVisible;
71 bool m_focused;
73 bool m_focused;
74+ bool m_alerting;
72 QuickListModel *m_quickList;
75 QuickListModel *m_quickList;
73
76
74 friend class LauncherModel;
77 friend class LauncherModel;
plugins/Unity/Launcher/launchermodel.cpp+32−2
@@ -41,6 +41,7 @@ LauncherModel::LauncherModel(QObject *parent):
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
43 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
43 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
44+ connect(m_dbusIface, &DBusInterface::alertCalled, this, &LauncherModel::alert);
44
45
45 connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
46 connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
46
47
@@ -82,11 +83,25 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
82 return item->progress();
83 return item->progress();
83 case RoleFocused:
84 case RoleFocused:
84 return item->focused();
85 return item->focused();
86+ case RoleAlerting:
87+ return item->alerting();
85 }
88 }
86
89
87 return QVariant();
90 return QVariant();
88}
91}
89
92
93+void LauncherModel::setAlerting(const QString &appId, bool alerting) {
94+ int index = findApplication(appId);
95+ if (index >= 0) {
96+ QModelIndex modelIndex = this->index(index);
97+ LauncherItem *item = m_list.at(index);
98+ if (!item->focused()) {
99+ item->setAlerting(alerting);
100+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
101+ }
102+ }
103+}
104+
90unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
105unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
91{
106{
92 if (index < 0 || index >= m_list.count()) {
107 if (index < 0 || index >= m_list.count()) {
@@ -334,17 +349,23 @@ void LauncherModel::countChanged(const QString &appId, int count)
334 if (idx >= 0) {
349 if (idx >= 0) {
335 LauncherItem *item = m_list.at(idx);
350 LauncherItem *item = m_list.at(idx);
336 item->setCount(count);
351 item->setCount(count);
352+ if (item->countVisible()) {
353+ setAlerting(item->appId(), true);
354+ }
337 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
355 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
338 m_asAdapter->syncItems(m_list);
356 m_asAdapter->syncItems(m_list);
339 }
357 }
340}
358}
341
359
342−void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
360+void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
343{
361{
344 int idx = findApplication(appId);
362 int idx = findApplication(appId);
345 if (idx >= 0) {
363 if (idx >= 0) {
346 LauncherItem *item = m_list.at(idx);
364 LauncherItem *item = m_list.at(idx);
347 item->setCountVisible(countVisible);
365 item->setCountVisible(countVisible);
366+ if (countVisible) {
367+ setAlerting(item->appId(), true);
368+ }
348 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
369 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
349
370
350 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
371 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
@@ -364,7 +385,6 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
364 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
385 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
365 m_list.append(item);
386 m_list.append(item);
366 endInsertRows();
387 endInsertRows();
367− Q_EMIT hint();
368 }
388 }
369 }
389 }
370 m_asAdapter->syncItems(m_list);
390 m_asAdapter->syncItems(m_list);
@@ -453,6 +473,16 @@ void LauncherModel::refresh()
453 m_asAdapter->syncItems(m_list);
473 m_asAdapter->syncItems(m_list);
454}
474}
455
475
476+void LauncherModel::alert(const QString &appId)
477+{
478+ int idx = findApplication(appId);
479+ if (idx >= 0) {
480+ LauncherItem *item = m_list.at(idx);
481+ setAlerting(item->appId(), true);
482+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleAlerting);
483+ }
484+}
485+
456void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
486void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
457{
487{
458 Q_UNUSED(parent);
488 Q_UNUSED(parent);
plugins/Unity/Launcher/launchermodel.h+3−1
@@ -45,6 +45,7 @@ public:
45
45
46 QVariant data(const QModelIndex &index, int role) const override;
46 QVariant data(const QModelIndex &index, int role) const override;
47
47
48+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
48 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
49 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
49 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
50 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
50 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
51 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
@@ -63,6 +64,7 @@ public:
63public Q_SLOTS:
64public Q_SLOTS:
64 void requestRemove(const QString &appId) override;
65 void requestRemove(const QString &appId) override;
65 Q_INVOKABLE void refresh();
66 Q_INVOKABLE void refresh();
67+ Q_INVOKABLE void alert(const QString &appId);
66
68
67private:
69private:
68 void storeAppList();
70 void storeAppList();
@@ -71,7 +73,7 @@ private:
71
73
72private Q_SLOTS:
74private Q_SLOTS:
73 void countChanged(const QString &appId, int count);
75 void countChanged(const QString &appId, int count);
74− void countVisibleChanged(const QString &appId, int count);
76+ void countVisibleChanged(const QString &appId, bool count);
75 void progressChanged(const QString &appId, int progress);
77 void progressChanged(const QString &appId, int progress);
76
78
77 void applicationAdded(const QModelIndex &parent, int row);
79 void applicationAdded(const QModelIndex &parent, int row);
qml/Launcher/LauncherDelegate.qml+87−1
@@ -15,7 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.1
19
19
20Item {
20Item {
21 id: root
21 id: root
@@ -27,6 +27,8 @@ Item {
27 property bool itemFocused: false
27 property bool itemFocused: false
28 property real maxAngle: 0
28 property real maxAngle: 0
29 property bool inverted: false
29 property bool inverted: false
30+ property bool alerting: false
31+ readonly property alias wiggling: wiggleAnim.running
30
32
31 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
33 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
32 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
34 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -39,6 +41,82 @@ Item {
39 property real offset: 0
41 property real offset: 0
40 property real itemOpacity: 1
42 property real itemOpacity: 1
41 property real brightness: 0
43 property real brightness: 0
44+ property double maxWiggleAngle: 5.0
45+
46+ QtObject {
47+ id: priv
48+
49+ readonly property int wiggleDuration: UbuntuAnimation.SnapDuration
50+ property real wiggleAngle: 0
51+ }
52+
53+ SequentialAnimation {
54+ id: wiggleAnim
55+
56+ running: alerting
57+ loops: 1
58+ alwaysRunToEnd: true
59+
60+ NumberAnimation {
61+ target: priv
62+ property: "wiggleAngle"
63+ from: 0
64+ to: maxWiggleAngle
65+ duration: priv.wiggleDuration
66+ easing.type: Easing.InQuad
67+ }
68+
69+ NumberAnimation {
70+ target: priv
71+ property: "wiggleAngle"
72+ from: maxWiggleAngle
73+ to: -maxWiggleAngle
74+ duration: priv.wiggleDuration
75+ easing.type: Easing.InOutQuad
76+ }
77+
78+ NumberAnimation {
79+ target: priv
80+ property: "wiggleAngle"
81+ from: -maxWiggleAngle
82+ to: maxWiggleAngle
83+ duration: priv.wiggleDuration
84+ easing.type: Easing.InOutQuad
85+ }
86+
87+ NumberAnimation {
88+ target: priv
89+ property: "wiggleAngle"
90+ from: maxWiggleAngle
91+ to: -maxWiggleAngle
92+ duration: priv.wiggleDuration
93+ easing.type: Easing.InOutQuad
94+ }
95+
96+ NumberAnimation {
97+ target: priv
98+ property: "wiggleAngle"
99+ from: -maxWiggleAngle
100+ to: maxWiggleAngle
101+ duration: priv.wiggleDuration
102+ easing.type: Easing.InOutQuad
103+ }
104+
105+ NumberAnimation {
106+ target: priv
107+ property: "wiggleAngle"
108+ from: maxWiggleAngle
109+ to: 0
110+ duration: priv.wiggleDuration
111+ easing.type: Easing.OutQuad
112+ }
113+
114+ UbuntuNumberAnimation {
115+ target: root
116+ property: "alerting"
117+ to: 0
118+ }
119+ }
42
120
43 Item {
121 Item {
44 id: iconItem
122 id: iconItem
@@ -167,6 +245,14 @@ Item {
167 }
245 }
168
246
169 transform: [
247 transform: [
248+ // The rotation about the icon's center/z-axis for the wiggle
249+ // needs to happen here too, because there's no other way to
250+ // align the wiggle with the icon-folding otherwise
251+ Rotation {
252+ axis { x: 0; y: 0; z: 1 }
253+ origin { x: iconItem.width / 2; y: iconItem.height / 2; z: 0 }
254+ angle: priv.wiggleAngle
255+ },
170 // Rotating 3 times at top/bottom because that increases the perspective.
256 // Rotating 3 times at top/bottom because that increases the perspective.
171 // This is a hack, but as QML does not support real 3D coordinates
257 // This is a hack, but as QML does not support real 3D coordinates
172 // getting a higher perspective can only be done by a hack. This is the most
258 // getting a higher perspective can only be done by a hack. This is the most
qml/Launcher/LauncherPanel.qml+75−4
@@ -86,6 +86,7 @@ Rectangle {
86 height: parent.height - dashItem.height - parent.spacing*2
86 height: parent.height - dashItem.height - parent.spacing*2
87
87
88 Item {
88 Item {
89+ id: launcherListViewItem
89 anchors.fill: parent
90 anchors.fill: parent
90 clip: true
91 clip: true
91
92
@@ -109,6 +110,9 @@ Rectangle {
109 preferredHighlightBegin: (height - itemHeight) / 2
110 preferredHighlightBegin: (height - itemHeight) / 2
110 preferredHighlightEnd: (height + itemHeight) / 2
111 preferredHighlightEnd: (height + itemHeight) / 2
111
112
113+ // for the single peeking icon, when alert-state is set on delegate
114+ property int peekingIndex: -1
115+
112 // The size of the area the ListView is extended to make sure items are not
116 // The size of the area the ListView is extended to make sure items are not
113 // destroyed when dragging them outside the list. This needs to be at least
117 // destroyed when dragging them outside the list. This needs to be at least
114 // itemHeight to prevent folded items from disappearing and DragArea limits
118 // itemHeight to prevent folded items from disappearing and DragArea limits
@@ -159,6 +163,17 @@ Rectangle {
159 to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY - launcherListView.topMargin
163 to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY - launcherListView.topMargin
160 }
164 }
161
165
166+ UbuntuNumberAnimation {
167+ id: moveAnimation
168+ target: launcherListView
169+ property: "contentY"
170+ function moveTo(contentY) {
171+ from = launcherListView.contentY;
172+ to = contentY;
173+ start();
174+ }
175+ }
176+
162 displaced: Transition {
177 displaced: Transition {
163 NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
178 NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
164 }
179 }
@@ -180,10 +195,67 @@ Rectangle {
180 progress: model.progress
195 progress: model.progress
181 itemFocused: model.focused
196 itemFocused: model.focused
182 inverted: root.inverted
197 inverted: root.inverted
198+ alerting: model.alerting
183 z: -Math.abs(offset)
199 z: -Math.abs(offset)
184 maxAngle: 55
200 maxAngle: 55
185 property bool dragging: false
201 property bool dragging: false
186
202
203+ SequentialAnimation {
204+ id: peekingAnimation
205+
206+ // revealing
207+ PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 1 : 0 }
208+ PropertyAction { target: launcherListViewItem; property: "clip"; value: 0 }
209+
210+ UbuntuNumberAnimation {
211+ target: launcherDelegate
212+ alwaysRunToEnd: true
213+ loops: 1
214+ properties: "x"
215+ to: (units.gu(.5) + launcherListView.width * .5) * (root.inverted ? -1 : 1)
216+ duration: UbuntuAnimation.BriskDuration
217+ }
218+
219+ // hiding
220+ UbuntuNumberAnimation {
221+ target: launcherDelegate
222+ alwaysRunToEnd: true
223+ loops: 1
224+ properties: "x"
225+ to: 0
226+ duration: UbuntuAnimation.BriskDuration
227+ }
228+
229+ PropertyAction { target: launcherListViewItem; property: "clip"; value: 1 }
230+ PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 0 : 1 }
231+ }
232+
233+ onAlertingChanged: {
234+ if(alerting) {
235+ if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
236+ var itemPosition = index * launcherListView.itemHeight;
237+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
238+ var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
239+ if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
240+ moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
241+ } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
242+ moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
243+ }
244+ if (!dragging && launcher.state !== "visible") {
245+ peekingAnimation.start()
246+ }
247+ }
248+
249+ if (launcherListView.peekingIndex === -1) {
250+ launcherListView.peekingIndex = index
251+ }
252+ } else {
253+ if (launcherListView.peekingIndex === index) {
254+ launcherListView.peekingIndex = -1
255+ }
256+ }
257+ }
258+
187 ThinDivider {
259 ThinDivider {
188 id: dropIndicator
260 id: dropIndicator
189 objectName: "dropIndicator"
261 objectName: "dropIndicator"
@@ -521,7 +593,7 @@ Rectangle {
521 Image {
593 Image {
522 anchors {
594 anchors {
523 left: parent.left
595 left: parent.left
524− leftMargin: quickList.item ? (quickList.item.width - units.gu(1)) / 2 - width / 2 : 0
596+ leftMargin: quickList.item !== undefined ? (quickList.item.width - units.gu(1)) / 2 - width / 2 : 0
525 verticalCenter: parent.verticalCenter
597 verticalCenter: parent.verticalCenter
526 verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) * (root.inverted ? 1 : -1)
598 verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) * (root.inverted ? 1 : -1)
527 }
599 }
@@ -564,10 +636,9 @@ Rectangle {
564
636
565 // internal
637 // internal
566 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) : units.gu(1)
638 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) : units.gu(1)
567− property int offset: item ? itemCenter + (item.height/2) + height + units.gu(1) > parent.height ?
639+ property int offset: item !== undefined ? (itemCenter + (item.height/2) + height + units.gu(1) > parent.height ?
568 -(item.height/2) - height - units.gu(.5) :
640 -(item.height/2) - height - units.gu(.5) :
569− (item.height/2) + units.gu(.5) :
641+ (item.height/2) + units.gu(.5)) : 0
570− 0
571
642
572 Column {
643 Column {
573 id: quickListColumn
644 id: quickListColumn
tests/mocks/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+19
@@ -37,6 +37,7 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
37 m_count(0),
37 m_count(0),
38 m_countVisible(false),
38 m_countVisible(false),
39 m_focused(false),
39 m_focused(false),
40+ m_alerting(false),
40 m_quickList(new MockQuickListModel(this))
41 m_quickList(new MockQuickListModel(this))
41{
42{
42
43
@@ -129,6 +130,9 @@ void MockLauncherItem::setCount(int count)
129 {
130 {
130 m_count = count;
131 m_count = count;
131 Q_EMIT countChanged(count);
132 Q_EMIT countChanged(count);
133+ if (m_countVisible) {
134+ setAlerting(true);
135+ }
132 }
136 }
133}
137}
134
138
@@ -142,6 +146,9 @@ void MockLauncherItem::setCountVisible(bool countVisible)
142 if (m_countVisible != countVisible) {
146 if (m_countVisible != countVisible) {
143 m_countVisible = countVisible;
147 m_countVisible = countVisible;
144 Q_EMIT countVisibleChanged(countVisible);
148 Q_EMIT countVisibleChanged(countVisible);
149+ if (countVisible) {
150+ setAlerting(true);
151+ }
145 }
152 }
146}
153}
147
154
@@ -159,6 +166,18 @@ void MockLauncherItem::setFocused(bool focused)
159 }
166 }
160}
167}
161
168
169+bool MockLauncherItem::alerting() const
170+{
171+ return m_alerting;
172+}
173+
174+void MockLauncherItem::setAlerting(bool alerting)
175+{
176+ if (m_alerting != alerting) {
177+ m_alerting = alerting;
178+ Q_EMIT alertingChanged(alerting);
179+ }
180+}
162
181
163unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
182unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
164{
183{
tests/mocks/Unity/Launcher/MockLauncherItem.h+3
@@ -44,6 +44,7 @@ public:
44 int count() const override;
44 int count() const override;
45 bool countVisible() const override;
45 bool countVisible() const override;
46 bool focused() const override;
46 bool focused() const override;
47+ bool alerting() const override;
47
48
48 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49
50
@@ -55,6 +56,7 @@ private:
55 void setCount(int count);
56 void setCount(int count);
56 void setCountVisible(bool countVisible);
57 void setCountVisible(bool countVisible);
57 void setFocused(bool focused);
58 void setFocused(bool focused);
59+ void setAlerting(bool alerting);
58
60
59 QString m_appId;
61 QString m_appId;
60 QString m_desktopFile;
62 QString m_desktopFile;
@@ -67,6 +69,7 @@ private:
67 int m_count;
69 int m_count;
68 bool m_countVisible;
70 bool m_countVisible;
69 bool m_focused;
71 bool m_focused;
72+ bool m_alerting;
70 MockQuickListModel *m_quickList;
73 MockQuickListModel *m_quickList;
71
74
72 friend class MockLauncherModel;
75 friend class MockLauncherModel;
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+40
@@ -34,6 +34,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
35 item->setProgress(50);
35 item->setProgress(50);
36 item->setCountVisible(true);
36 item->setCountVisible(true);
37+ item->setAlerting(false);
37 m_list.append(item);
38 m_list.append(item);
38 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
39 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
39 m_list.append(item);
40 m_list.append(item);
@@ -43,25 +44,30 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
43 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
44 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
44 item->setCount(1);
45 item->setCount(1);
45 item->setCountVisible(true);
46 item->setCountVisible(true);
47+ item->setAlerting(false);
46 m_list.append(item);
48 m_list.append(item);
47 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
49 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
48 item->setCount(12);
50 item->setCount(12);
49 item->setCountVisible(true);
51 item->setCountVisible(true);
52+ item->setAlerting(false);
50 item->setPinned(true);
53 item->setPinned(true);
51 m_list.append(item);
54 m_list.append(item);
52 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
55 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
53 item->setCount(123);
56 item->setCount(123);
54 item->setCountVisible(true);
57 item->setCountVisible(true);
58+ item->setAlerting(false);
55 m_list.append(item);
59 m_list.append(item);
56 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
60 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
57 item->setCount(1234567890);
61 item->setCount(1234567890);
58 item->setCountVisible(true);
62 item->setCountVisible(true);
63+ item->setAlerting(false);
59 item->setPinned(true);
64 item->setPinned(true);
60 m_list.append(item);
65 m_list.append(item);
61 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
66 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
62 item->setProgress(50);
67 item->setProgress(50);
63 item->setCount(5);
68 item->setCount(5);
64 item->setCountVisible(true);
69 item->setCountVisible(true);
70+ item->setAlerting(false);
65 item->setFocused(true);
71 item->setFocused(true);
66 item->setPinned(true);
72 item->setPinned(true);
67 m_list.append(item);
73 m_list.append(item);
@@ -105,6 +111,8 @@ QVariant MockLauncherModel::data(const QModelIndex& index, int role) const
105 return item->countVisible();
111 return item->countVisible();
106 case RoleFocused:
112 case RoleFocused:
107 return item->focused();
113 return item->focused();
114+ case RoleAlerting:
115+ return item->alerting();
108 }
116 }
109
117
110 return QVariant();
118 return QVariant();
@@ -119,6 +127,18 @@ unity::shell::launcher::LauncherItemInterface *MockLauncherModel::get(int index)
119 return m_list.at(index);
127 return m_list.at(index);
120}
128}
121
129
130+void MockLauncherModel::setAlerting(const QString &appId, bool alerting) {
131+ int index = findApp(appId);
132+ if (index >= 0) {
133+ QModelIndex modelIndex = this->index(index);
134+ MockLauncherItem *item = m_list.at(index);
135+ if (!item->focused()) {
136+ item->setAlerting(alerting);
137+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
138+ }
139+ }
140+}
141+
122void MockLauncherModel::move(int oldIndex, int newIndex)
142void MockLauncherModel::move(int oldIndex, int newIndex)
123{
143{
124 // Make sure its not moved outside the lists
144 // Make sure its not moved outside the lists
@@ -226,6 +246,26 @@ void MockLauncherModel::emitHint()
226 Q_EMIT hint();
246 Q_EMIT hint();
227}
247}
228
248
249+void MockLauncherModel::setCount(const QString &appId, int count)
250+{
251+ int index = findApp(appId);
252+ if (index >= 0) {
253+ m_list.at(index)->setCount(count);
254+ QModelIndex modelIndex = this->index(index);
255+ Q_EMIT dataChanged(modelIndex, modelIndex);
256+ }
257+}
258+
259+void MockLauncherModel::setCountVisible(const QString &appId, bool countVisible)
260+{
261+ int index = findApp(appId);
262+ if (index >= 0) {
263+ m_list.at(index)->setCountVisible(countVisible);
264+ QModelIndex modelIndex = this->index(index);
265+ Q_EMIT dataChanged(modelIndex, modelIndex);
266+ }
267+}
268+
229unity::shell::application::ApplicationManagerInterface *MockLauncherModel::applicationManager() const
269unity::shell::application::ApplicationManagerInterface *MockLauncherModel::applicationManager() const
230{
270{
231 return nullptr;
271 return nullptr;
tests/mocks/Unity/Launcher/MockLauncherModel.h+3
@@ -38,6 +38,7 @@ public:
38
38
39 QVariant data(const QModelIndex& index, int role) const override;
39 QVariant data(const QModelIndex& index, int role) const override;
40
40
41+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
41 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
42 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
42 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
43 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
43 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
44 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
@@ -54,6 +55,8 @@ public:
54
55
55 // For testing
56 // For testing
56 Q_INVOKABLE void emitHint();
57 Q_INVOKABLE void emitHint();
58+ Q_INVOKABLE void setCount(const QString &appId, int count);
59+ Q_INVOKABLE void setCountVisible(const QString &appId, bool countVisible);
57
60
58Q_SIGNALS:
61Q_SIGNALS:
59 void quickListTriggered(const QString &appId, int index);
62 void quickListTriggered(const QString &appId, int index);
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3
3
4include_directories(
4include_directories(
tests/plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
3
3
4include_directories(
4include_directories(
5 ${CMAKE_CURRENT_SOURCE_DIR}
5 ${CMAKE_CURRENT_SOURCE_DIR}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+23−7
@@ -368,11 +368,18 @@ private Q_SLOTS:
368 }
368 }
369
369
370 void testCountEmblems() {
370 void testCountEmblems() {
371+ QSignalSpy spy(launcherModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
372+
371 // Call GetAll on abs-icon
373 // Call GetAll on abs-icon
372 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
374 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
373 QDBusReply<QVariantMap> reply = interface.call("GetAll");
375 QDBusReply<QVariantMap> reply = interface.call("GetAll");
374 QVariantMap map = reply.value();
376 QVariantMap map = reply.value();
375
377
378+ // Check that the alerting-status is still false, and the item on the upper side of the API
379+ int index = launcherModel->findApplication("abs-icon");
380+ QCOMPARE(index >= 0, true);
381+ QVERIFY(launcherModel->get(index)->alerting() == false);
382+
376 // Make sure GetAll returns a map with count and countVisible props
383 // Make sure GetAll returns a map with count and countVisible props
377 QCOMPARE(map.contains("count"), true);
384 QCOMPARE(map.contains("count"), true);
378 QCOMPARE(map.contains("countVisible"), true);
385 QCOMPARE(map.contains("countVisible"), true);
@@ -393,13 +400,8 @@ private Q_SLOTS:
393 QCOMPARE(map.value("count").toInt(), 55);
400 QCOMPARE(map.value("count").toInt(), 55);
394 QCOMPARE(map.value("countVisible").toBool(), true);
401 QCOMPARE(map.value("countVisible").toBool(), true);
395
402
396− // Now the item on the upper side of the API
403+ // Finally check, that the change to "count" implicitly also set the alerting-state to true
397− int index = launcherModel->findApplication("abs-icon");
398− QCOMPARE(index >= 0, true);
399−
400− // And make sure values have changed there as well
401− QCOMPARE(launcherModel->get(index)->countVisible(), true);
404+ QVERIFY(launcherModel->get(index)->alerting() == true);
402− QCOMPARE(launcherModel->get(index)->count(), 55);
403 }
405 }
404
406
405 void testCountEmblemAddsRemovesItem_data() {
407 void testCountEmblemAddsRemovesItem_data() {
@@ -459,6 +461,20 @@ private Q_SLOTS:
459 QCOMPARE(index == -1, !isRunning && !isPinned && !startWhenVisible);
461 QCOMPARE(index == -1, !isRunning && !isPinned && !startWhenVisible);
460 }
462 }
461
463
464+ void testAlert() {
465+ // Check that the alerting-status is still false
466+ int index = launcherModel->findApplication("abs-icon");
467+ QCOMPARE(index >= 0, true);
468+ QVERIFY(launcherModel->get(index)->alerting() == false);
469+
470+ // Call Alert() on "abs-icon"
471+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "com.canonical.Unity.Launcher.Item");
472+ interface.call("Alert");
473+
474+ // Check that the alerting-status is now true
475+ QVERIFY(launcherModel->get(index)->alerting() == true);
476+ }
477+
462 void testRefreshAfterDeletedDesktopFiles_data() {
478 void testRefreshAfterDeletedDesktopFiles_data() {
463 QTest::addColumn<bool>("deleted");
479 QTest::addColumn<bool>("deleted");
464 QTest::newRow("have .desktop files") << false;
480 QTest::newRow("have .desktop files") << false;
tests/qmltests/Launcher/tst_Launcher.qml+144
@@ -93,6 +93,31 @@ Item {
93 onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted
93 onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted
94 Layout.fillWidth: true
94 Layout.fillWidth: true
95 }
95 }
96+
97+ Row {
98+ spacing: units.gu(1)
99+
100+ Button {
101+ text: "set alert"
102+ onClicked: LauncherModel.setAlerting(LauncherModel.get(parseInt(appIdEntry.displayText)).appId, true)
103+ }
104+
105+ TextArea {
106+ id: appIdEntry
107+ anchors.verticalCenter: parent.verticalCenter
108+ width: units.gu(5)
109+ height: units.gu(4)
110+ autoSize: true
111+ text: "2"
112+ maximumLineCount: 1
113+ Layout.fillWidth: true
114+ }
115+
116+ Button {
117+ text: "unset alert"
118+ onClicked: LauncherModel.setAlerting(LauncherModel.get(parseInt(appIdEntry.displayText)).appId, false)
119+ }
120+ }
96 }
121 }
97
122
98 SignalSpy {
123 SignalSpy {
@@ -169,6 +194,16 @@ Item {
169 tryCompare(panel, "x", -panel.width, 1000);
194 tryCompare(panel, "x", -panel.width, 1000);
170 }
195 }
171
196
197+ function waitForWiggleToStart(appIcon) {
198+ verify(appIcon != undefined)
199+ tryCompare(appIcon, "wiggling", true, 1000, "wiggle-anim should not be in stopped state")
200+ }
201+
202+ function waitForWiggleToStop(appIcon) {
203+ verify(appIcon != undefined)
204+ tryCompare(appIcon, "wiggling", false, 1000, "wiggle-anim should not be in running state")
205+ }
206+
172 function positionLauncherListAtBeginning() {
207 function positionLauncherListAtBeginning() {
173 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
208 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
174 listView.contentY = -listView.topMargin;
209 listView.contentY = -listView.topMargin;
@@ -636,5 +671,114 @@ Item {
636 tryCompare(launcher, "state", "", 1000, "Launcher didn't hide after moving mouse away from it");
671 tryCompare(launcher, "state", "", 1000, "Launcher didn't hide after moving mouse away from it");
637 waitUntilLauncherDisappears();
672 waitUntilLauncherDisappears();
638 }
673 }
674+
675+ function test_alertPeekingIcon() {
676+ var listView = findChild(launcher, "launcherListView")
677+ verify(listView != undefined)
678+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
679+ tryCompare(listView, "peekingIndex", 5, 1000, "Wrong appId set as peeking-index")
680+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
681+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
682+ }
683+
684+ function test_alertHidingIcon() {
685+ var listView = findChild(launcher, "launcherListView")
686+ verify(listView != undefined)
687+ var appIcon6 = findChild(launcher, "launcherDelegate6")
688+ verify(appIcon6 != undefined)
689+ LauncherModel.setAlerting(LauncherModel.get(6).appId, true)
690+ waitForWiggleToStart(appIcon6)
691+ LauncherModel.setAlerting(LauncherModel.get(6).appId, false)
692+ waitForWiggleToStop(appIcon6)
693+ tryCompare(appIcon6, "x", 0, 1000, "x-value of appId #6 should not be non-zero")
694+ waitForRendering(listView)
695+ }
696+
697+ function test_alertIgnoreFocusedApp() {
698+ LauncherModel.setAlerting(LauncherModel.get(0).appId, true)
699+ compare(LauncherModel.get(0).alerting, false, "Focused app should not have the alert-state set")
700+ }
701+
702+ function test_alertOnlyOnePeekingIcon() {
703+ var listView = findChild(launcher, "launcherListView")
704+ verify(listView != undefined)
705+ LauncherModel.setAlerting(LauncherModel.get(3).appId, true)
706+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
707+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
708+ tryCompare(listView, "peekingIndex", 3, 1000, "Wrong appId set as peeking-index")
709+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
710+ LauncherModel.setAlerting(LauncherModel.get(3).appId, false)
711+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
712+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
713+ waitForRendering(listView)
714+ }
715+
716+ function test_alertMultipleApps() {
717+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
718+ LauncherModel.setAlerting(LauncherModel.get(3).appId, true)
719+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
720+ LauncherModel.setAlerting(LauncherModel.get(7).appId, true)
721+ compare(LauncherModel.get(1).alerting, true, "Alert-state of appId #1 should not be false")
722+ compare(LauncherModel.get(3).alerting, true, "Alert-state of appId #3 should not be false")
723+ compare(LauncherModel.get(5).alerting, true, "Alert-state of appId #5 should not be false")
724+ compare(LauncherModel.get(7).alerting, true, "Alert-state of appId #7 should not be false")
725+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
726+ LauncherModel.setAlerting(LauncherModel.get(3).appId, false)
727+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
728+ LauncherModel.setAlerting(LauncherModel.get(7).appId, false)
729+ compare(LauncherModel.get(1).alerting, false, "Alert-state of appId #1 should not be true")
730+ compare(LauncherModel.get(3).alerting, false, "Alert-state of appId #1 should not be true")
731+ compare(LauncherModel.get(5).alerting, false, "Alert-state of appId #1 should not be true")
732+ compare(LauncherModel.get(7).alerting, false, "Alert-state of appId #1 should not be true")
733+ }
734+
735+ function test_alertMoveIconIntoView() {
736+ dragLauncherIntoView();
737+ var appIcon1 = findChild(launcher, "launcherDelegate1");
738+ var appIcon7 = findChild(launcher, "launcherDelegate7");
739+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
740+ tryCompare(appIcon1, "angle", 0, 1000, "angle of appId #1 should not be non-zero")
741+ waitForWiggleToStart(appIcon1)
742+ LauncherModel.setAlerting(LauncherModel.get(7).appId, true)
743+ tryCompare(appIcon7, "angle", 0, 1000, "angle of appId #7 should not be non-zero")
744+ waitForWiggleToStart(appIcon7)
745+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
746+ waitForWiggleToStop(appIcon1)
747+ LauncherModel.setAlerting(LauncherModel.get(7).appId, false)
748+ waitForWiggleToStop(appIcon7)
749+ }
750+
751+ function test_alertWigglePeekDrag() {
752+ var appIcon5 = findChild(launcher, "launcherDelegate5");
753+ var listView = findChild(launcher, "launcherListView")
754+ verify(listView != undefined)
755+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
756+ tryCompare(listView, "peekingIndex", 5, 1000, "Wrong appId set as peeking-index")
757+ waitForWiggleToStart(appIcon5)
758+ tryCompare(appIcon5, "wiggling", true, 1000, "appId #6 should not be still")
759+ dragLauncherIntoView();
760+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
761+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
762+ waitForWiggleToStop(appIcon5)
763+ tryCompare(appIcon5, "wiggling", false, 1000, "appId #1 should not be wiggling")
764+ }
765+
766+ function test_alertViaCountAndCountVisible() {
767+ dragLauncherIntoView();
768+ var appIcon1 = findChild(launcher, "launcherDelegate1")
769+ var oldCount = LauncherModel.get(1).count
770+ LauncherModel.setCount(LauncherModel.get(1).appId, 42)
771+ tryCompare(appIcon1, "wiggling", false, 1000, "appId #1 should be still")
772+ LauncherModel.setCountVisible(LauncherModel.get(1).appId, 1)
773+ tryCompare(appIcon1, "wiggling", true, 1000, "appId #1 should not be still")
774+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
775+ waitForWiggleToStop(appIcon1)
776+ LauncherModel.setCount(LauncherModel.get(1).appId, 4711)
777+ tryCompare(appIcon1, "wiggling", true, 1000, "appId #1 should not be still")
778+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
779+ waitForWiggleToStop(appIcon1)
780+ LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
781+ LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
782+ }
639 }
783 }
640}
784}
tests/scripts/READMEnew file+5
@@ -0,0 +1,5 @@
1+This directory holds a few handy shell-scripts to simplify runtime-testing of DBus-APIs (requires qdbus-qt5 to be installed).
2+
3+launcher-icon related:
4+ * list-launcher-icons.sh - get all icons of unity8-launcher
5+ * alert-launcher-icon.sh - trigger alert/wiggle of unfocused launcher-icon
tests/scripts/alert-launcher-icon.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 com.canonical.Unity.Launcher.Item.Alert
tests/scripts/list-launcher-icons.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity | grep Launcher | cut -f6 -d/