on xenial help sign in
merge lp:~mzanetti/unity8/alerting-pips
d8e6e9dLukáš Tinkl
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=17)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=18)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
debian/control+1−1
@@ -33,7 +33,7 @@ Build-Depends: cmake,
33 libubuntugestures5-dev,
33 libubuntugestures5-dev,
34 libubuntugestures5-private-dev,
34 libubuntugestures5-private-dev,
35 libudev-dev,
35 libudev-dev,
36− libunity-api-dev (>= 7.113),
36+ libunity-api-dev (>= 7.115),
37 libusermetricsoutput1-dev,
37 libusermetricsoutput1-dev,
38# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
38# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
39 libx11-dev[!armhf],
39 libx11-dev[!armhf],
plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=9)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3
3
4add_definitions(-DSM_BUSNAME=systemBus)
4add_definitions(-DSM_BUSNAME=systemBus)
plugins/Greeter/Unity/Launcher/launcheritem.cpp+14
@@ -32,6 +32,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
32 m_countVisible(false),
32 m_countVisible(false),
33 m_focused(false),
33 m_focused(false),
34 m_alerting(false),
34 m_alerting(false),
35+ m_surfaceCount(0),
35 m_quickList(new QuickListModel(this))
36 m_quickList(new QuickListModel(this))
36{
37{
37 QuickListEntry nameAction;
38 QuickListEntry nameAction;
@@ -179,6 +180,19 @@ void LauncherItem::setAlerting(bool alerting)
179 }
180 }
180}
181}
181
182
183+int LauncherItem::surfaceCount() const
184+{
185+ return m_surfaceCount;
186+}
187+
188+void LauncherItem::setSurfaceCount(int surfaceCount)
189+{
190+ if (m_surfaceCount != surfaceCount) {
191+ m_surfaceCount = surfaceCount;
192+ Q_EMIT surfaceCountChanged(surfaceCount);
193+ }
194+}
195+
182unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
196unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
183{
197{
184 return m_quickList;
198 return m_quickList;
plugins/Greeter/Unity/Launcher/launcheritem.h+3−1
@@ -42,6 +42,7 @@ public:
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 bool alerting() const override;
45+ int surfaceCount() const override;
45
46
46 unity::shell::launcher::QuickListModelInterface *quickList() const override;
47 unity::shell::launcher::QuickListModelInterface *quickList() const override;
47
48
@@ -56,7 +57,7 @@ private:
56 void setCountVisible(bool countVisible);
57 void setCountVisible(bool countVisible);
57 void setFocused(bool focused);
58 void setFocused(bool focused);
58 void setAlerting(bool alerting);
59 void setAlerting(bool alerting);
59−
60+ void setSurfaceCount(int surfaceCount);
60
61
61private:
62private:
62 QString m_appId;
63 QString m_appId;
@@ -70,6 +71,7 @@ private:
70 bool m_countVisible;
71 bool m_countVisible;
71 bool m_focused;
72 bool m_focused;
72 bool m_alerting;
73 bool m_alerting;
74+ int m_surfaceCount;
73 QuickListModel *m_quickList;
75 QuickListModel *m_quickList;
74
76
75 friend class LauncherModel;
77 friend class LauncherModel;
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+2−7
@@ -69,18 +69,13 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
69 return item->focused();
69 return item->focused();
70 case RoleRunning:
70 case RoleRunning:
71 return item->running();
71 return item->running();
72+ case RoleSurfaceCount:
73+ return item->surfaceCount();
72 }
74 }
73
75
74 return QVariant();
76 return QVariant();
75}
77}
76
78
77−void LauncherModel::setAlerting(const QString &appId, bool alerting)
78−{
79− Q_UNUSED(appId)
80− Q_UNUSED(alerting)
81− qWarning() << "This is a read only implementation. Cannot set alert-state of items.";
82−}
83−
84unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
79unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
85{
80{
86 if (index < 0 || index >= m_list.count()) {
81 if (index < 0 || index >= m_list.count()) {
plugins/Greeter/Unity/Launcher/launchermodelas.h−1
@@ -41,7 +41,6 @@ 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;
45 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
44 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
46 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
45 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
47 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
46 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
plugins/Unity/Launcher/CMakeLists.txt+3−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=9)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3
3
4add_definitions(-DSM_BUSNAME=systemBus)
4add_definitions(-DSM_BUSNAME=systemBus)
@@ -23,6 +23,8 @@ set(QMLLAUNCHERPLUGIN_SRC
23 asadapter.cpp
23 asadapter.cpp
24 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
24 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
25 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
25 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
26+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
27+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
26 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
28 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
27 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
29 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
28 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
30 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
plugins/Unity/Launcher/Launcher.qmltypesdeleted−182
@@ -1,182 +0,0 @@
1−import QtQuick.tooling 1.1
2−
3−// This file describes the plugin-supplied types contained in the library.
4−// It is used for QML tooling purposes only.
5−//
6−// This file was auto-generated by:
7−// 'qmlplugindump -notrelocatable Unity.Launcher 0.1 plugins'
8−
9−Module {
10− Component {
11− name: "LauncherItem"
12− prototype: "unity::shell::launcher::LauncherItemInterface"
13− exports: ["Unity.Launcher/LauncherItem 0.1"]
14− isCreatable: false
15− exportMetaObjectRevisions: [0]
16− }
17− Component {
18− name: "LauncherModel"
19− prototype: "unity::shell::launcher::LauncherModelInterface"
20− exports: ["Unity.Launcher/LauncherModel 0.1"]
21− isCreatable: false
22− isSingleton: true
23− exportMetaObjectRevisions: [0]
24− Method {
25− name: "requestRemove"
26− Parameter { name: "appId"; type: "string" }
27− }
28− Method { name: "refresh" }
29− Method {
30− name: "get"
31− type: "unity::shell::launcher::LauncherItemInterface*"
32− Parameter { name: "index"; type: "int" }
33− }
34− Method {
35− name: "move"
36− Parameter { name: "oldIndex"; type: "int" }
37− Parameter { name: "newIndex"; type: "int" }
38− }
39− Method {
40− name: "pin"
41− Parameter { name: "appId"; type: "string" }
42− Parameter { name: "index"; type: "int" }
43− }
44− Method {
45− name: "pin"
46− Parameter { name: "appId"; type: "string" }
47− }
48− Method {
49− name: "quickListActionInvoked"
50− Parameter { name: "appId"; type: "string" }
51− Parameter { name: "actionIndex"; type: "int" }
52− }
53− Method {
54− name: "setUser"
55− Parameter { name: "username"; type: "string" }
56− }
57− Method {
58− name: "getUrlForAppId"
59− type: "string"
60− Parameter { name: "appId"; type: "string" }
61− }
62− }
63− Component {
64− name: "QuickListModel"
65− prototype: "unity::shell::launcher::QuickListModelInterface"
66− exports: ["Unity.Launcher/QuickListModel 0.1"]
67− isCreatable: false
68− exportMetaObjectRevisions: [0]
69− }
70− Component {
71− name: "unity::shell::launcher::LauncherItemInterface"
72− prototype: "QObject"
73− exports: ["Unity.Launcher/LauncherItemInterface 0.1"]
74− isCreatable: false
75− exportMetaObjectRevisions: [0]
76− Property { name: "appId"; type: "string"; isReadonly: true }
77− Property { name: "name"; type: "string"; isReadonly: true }
78− Property { name: "icon"; type: "string"; isReadonly: true }
79− Property { name: "pinned"; type: "bool"; isReadonly: true }
80− Property { name: "running"; type: "bool"; isReadonly: true }
81− Property { name: "recent"; type: "bool"; isReadonly: true }
82− Property { name: "progress"; type: "int"; isReadonly: true }
83− Property { name: "count"; type: "int"; isReadonly: true }
84− Property { name: "countVisible"; type: "bool"; isReadonly: true }
85− Property { name: "focused"; type: "bool"; isReadonly: true }
86− Property {
87− name: "quickList"
88− type: "unity::shell::launcher::QuickListModelInterface"
89− isReadonly: true
90− isPointer: true
91− }
92− Signal {
93− name: "nameChanged"
94− Parameter { name: "name"; type: "string" }
95− }
96− Signal {
97− name: "iconChanged"
98− Parameter { name: "icon"; type: "string" }
99− }
100− Signal {
101− name: "pinnedChanged"
102− Parameter { name: "pinned"; type: "bool" }
103− }
104− Signal {
105− name: "runningChanged"
106− Parameter { name: "running"; type: "bool" }
107− }
108− Signal {
109− name: "recentChanged"
110− Parameter { name: "running"; type: "bool" }
111− }
112− Signal {
113− name: "progressChanged"
114− Parameter { name: "progress"; type: "int" }
115− }
116− Signal {
117− name: "countChanged"
118− Parameter { name: "count"; type: "int" }
119− }
120− Signal {
121− name: "countVisibleChanged"
122− Parameter { name: "countVisible"; type: "bool" }
123− }
124− Signal {
125− name: "focusedChanged"
126− Parameter { name: "focused"; type: "bool" }
127− }
128− }
129− Component {
130− name: "unity::shell::launcher::LauncherModelInterface"
131− prototype: "QAbstractListModel"
132− exports: ["Unity.Launcher/LauncherModelInterface 0.1"]
133− isCreatable: false
134− exportMetaObjectRevisions: [0]
135− Property {
136− name: "applicationManager"
137− type: "unity::shell::application::ApplicationManagerInterface"
138− isPointer: true
139− }
140− Property { name: "onlyPinned"; type: "bool" }
141− Signal { name: "hint" }
142− Method {
143− name: "move"
144− Parameter { name: "oldIndex"; type: "int" }
145− Parameter { name: "newIndex"; type: "int" }
146− }
147− Method {
148− name: "get"
149− type: "unity::shell::launcher::LauncherItemInterface*"
150− Parameter { name: "index"; type: "int" }
151− }
152− Method {
153− name: "pin"
154− Parameter { name: "appId"; type: "string" }
155− Parameter { name: "index"; type: "int" }
156− }
157− Method {
158− name: "pin"
159− Parameter { name: "appId"; type: "string" }
160− }
161− Method {
162− name: "requestRemove"
163− Parameter { name: "appId"; type: "string" }
164− }
165− Method {
166− name: "quickListActionInvoked"
167− Parameter { name: "appId"; type: "string" }
168− Parameter { name: "actionIndex"; type: "int" }
169− }
170− Method {
171− name: "setUser"
172− Parameter { name: "username"; type: "string" }
173− }
174− }
175− Component {
176− name: "unity::shell::launcher::QuickListModelInterface"
177− prototype: "QAbstractListModel"
178− exports: ["Unity.Launcher/QuickListInterface 0.1"]
179− isCreatable: false
180− exportMetaObjectRevisions: [0]
181− }
182−}
plugins/Unity/Launcher/launcheritem.cpp+14−9
@@ -35,6 +35,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
35 m_countVisible(false),
35 m_countVisible(false),
36 m_focused(false),
36 m_focused(false),
37 m_alerting(false),
37 m_alerting(false),
38+ m_surfaceCount(0),
38 m_quickList(new QuickListModel(this))
39 m_quickList(new QuickListModel(this))
39{
40{
40 Q_ASSERT(parent != nullptr);
41 Q_ASSERT(parent != nullptr);
@@ -162,9 +163,6 @@ void LauncherItem::setCount(int count)
162 if (m_count != count) {
163 if (m_count != count) {
163 m_count = count;
164 m_count = count;
164 Q_EMIT countChanged(count);
165 Q_EMIT countChanged(count);
165− if (m_countVisible) {
166− setAlerting(true);
167− }
168 }
166 }
169}
167}
170
168
@@ -178,9 +176,6 @@ void LauncherItem::setCountVisible(bool countVisible)
178 if (m_countVisible != countVisible) {
176 if (m_countVisible != countVisible) {
179 m_countVisible = countVisible;
177 m_countVisible = countVisible;
180 Q_EMIT countVisibleChanged(countVisible);
178 Q_EMIT countVisibleChanged(countVisible);
181− if (countVisible) {
182− setAlerting(true);
183− }
184 }
179 }
185}
180}
186
181
@@ -193,9 +188,6 @@ void LauncherItem::setFocused(bool focused)
193{
188{
194 if (m_focused != focused) {
189 if (m_focused != focused) {
195 m_focused = focused;
190 m_focused = focused;
196− if (focused) {
197− setAlerting(false);
198− }
199 Q_EMIT focusedChanged(focused);
191 Q_EMIT focusedChanged(focused);
200 }
192 }
201}
193}
@@ -213,6 +205,19 @@ void LauncherItem::setAlerting(bool alerting)
213 }
205 }
214}
206}
215
207
208+int LauncherItem::surfaceCount() const
209+{
210+ return m_surfaceCount;
211+}
212+
213+void LauncherItem::setSurfaceCount(int surfaceCount)
214+{
215+ if (m_surfaceCount != surfaceCount) {
216+ m_surfaceCount = surfaceCount;
217+ Q_EMIT surfaceCountChanged(surfaceCount);
218+ }
219+}
220+
216unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
221unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
217{
222{
218 return m_quickList;
223 return m_quickList;
plugins/Unity/Launcher/launcheritem.h+3
@@ -45,6 +45,7 @@ public:
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 bool alerting() const override;
48+ int surfaceCount() const override;
48
49
49 unity::shell::launcher::QuickListModelInterface *quickList() const override;
50 unity::shell::launcher::QuickListModelInterface *quickList() const override;
50
51
@@ -59,6 +60,7 @@ private:
59 void setCountVisible(bool countVisible);
60 void setCountVisible(bool countVisible);
60 void setFocused(bool focused);
61 void setFocused(bool focused);
61 void setAlerting(bool alerting);
62 void setAlerting(bool alerting);
63+ void setSurfaceCount(int surfaceCount);
62
64
63private:
65private:
64 QString m_appId;
66 QString m_appId;
@@ -72,6 +74,7 @@ private:
72 bool m_countVisible;
74 bool m_countVisible;
73 bool m_focused;
75 bool m_focused;
74 bool m_alerting;
76 bool m_alerting;
77+ int m_surfaceCount;
75 QuickListModel *m_quickList;
78 QuickListModel *m_quickList;
76 QuickListEntry m_quitAction;
79 QuickListEntry m_quitAction;
77
80
plugins/Unity/Launcher/launchermodel.cpp+58−26
@@ -25,6 +25,7 @@
25#include "asadapter.h"
25#include "asadapter.h"
26
26
27#include <unity/shell/application/ApplicationInfoInterface.h>
27#include <unity/shell/application/ApplicationInfoInterface.h>
28+#include <unity/shell/application/MirSurfaceListInterface.h>
28
29
29#include <QDesktopServices>
30#include <QDesktopServices>
30#include <QDebug>
31#include <QDebug>
@@ -88,6 +89,8 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
88 return item->alerting();
89 return item->alerting();
89 case RoleRunning:
90 case RoleRunning:
90 return item->running();
91 return item->running();
92+ case RoleSurfaceCount:
93+ return item->surfaceCount();
91 default:
94 default:
92 qWarning() << Q_FUNC_INFO << "missing role, implement me";
95 qWarning() << Q_FUNC_INFO << "missing role, implement me";
93 return QVariant();
96 return QVariant();
@@ -96,18 +99,6 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
96 return QVariant();
99 return QVariant();
97}
100}
98
101
99−void LauncherModel::setAlerting(const QString &appId, bool alerting) {
100− int index = findApplication(appId);
101− if (index >= 0) {
102− QModelIndex modelIndex = this->index(index);
103− LauncherItem *item = m_list.at(index);
104− if (!item->focused()) {
105− item->setAlerting(alerting);
106− Q_EMIT dataChanged(modelIndex, modelIndex, {RoleAlerting});
107− }
108− }
109−}
110−
111unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
102unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
112{
103{
113 if (index < 0 || index >= m_list.count()) {
104 if (index < 0 || index >= m_list.count()) {
@@ -358,11 +349,13 @@ void LauncherModel::countChanged(const QString &appId, int count)
358 if (idx >= 0) {
349 if (idx >= 0) {
359 LauncherItem *item = m_list.at(idx);
350 LauncherItem *item = m_list.at(idx);
360 item->setCount(count);
351 item->setCount(count);
352+ QVector<int> changedRoles = {RoleCount};
361− if (item->countVisible()) {
353+ if (item->countVisible() && !item->alerting() && !item->focused()) {
354+ changedRoles << RoleAlerting;
362− setAlerting(item->appId(), true);
355+ item->setAlerting(true);
363 }
356 }
364 m_asAdapter->syncItems(m_list);
357 m_asAdapter->syncItems(m_list);
365− Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
358+ Q_EMIT dataChanged(index(idx), index(idx), changedRoles);
366 }
359 }
367}
360}
368
361
@@ -372,10 +365,12 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
372 if (idx >= 0) {
365 if (idx >= 0) {
373 LauncherItem *item = m_list.at(idx);
366 LauncherItem *item = m_list.at(idx);
374 item->setCountVisible(countVisible);
367 item->setCountVisible(countVisible);
368+ QVector<int> changedRoles = {RoleCount};
375− if (countVisible) {
369+ if (countVisible && !item->alerting() && !item->focused()) {
370+ changedRoles << RoleAlerting;
376− setAlerting(item->appId(), true);
371+ item->setAlerting(true);
377 }
372 }
378− Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
373+ Q_EMIT dataChanged(index(idx), index(idx), changedRoles);
379
374
380 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
375 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
381 if (!countVisible && !item->pinned() && !item->recent()) {
376 if (!countVisible && !item->pinned() && !item->recent()) {
@@ -498,8 +493,10 @@ void LauncherModel::alert(const QString &appId)
498 int idx = findApplication(appId);
493 int idx = findApplication(appId);
499 if (idx >= 0) {
494 if (idx >= 0) {
500 LauncherItem *item = m_list.at(idx);
495 LauncherItem *item = m_list.at(idx);
496+ if (!item->focused() && !item->alerting()) {
501− setAlerting(item->appId(), true);
497+ item->setAlerting(true);
502− Q_EMIT dataChanged(index(idx), index(idx), {RoleAlerting});
498+ Q_EMIT dataChanged(index(idx), index(idx), {RoleAlerting});
499+ }
503 }
500 }
504}
501}
505
502
@@ -525,28 +522,50 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
525 item->setRecent(true);
522 item->setRecent(true);
526 Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRecent});
523 Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRecent});
527 }
524 }
525+ if (item->surfaceCount() != app->surfaceCount()) {
526+ item->setSurfaceCount(app->surfaceCount());
527+ Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleSurfaceCount});
528+ }
529+
528 item->setRunning(true);
530 item->setRunning(true);
529 } else {
531 } else {
530 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
532 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
531 item->setRecent(true);
533 item->setRecent(true);
532 item->setRunning(true);
534 item->setRunning(true);
533 item->setFocused(app->focused());
535 item->setFocused(app->focused());
534−
536+ item->setSurfaceCount(app->surfaceCount());
535 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
537 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
536 m_list.append(item);
538 m_list.append(item);
537 endInsertRows();
539 endInsertRows();
538 }
540 }
541+ connect(app, &ApplicationInfoInterface::surfaceCountChanged, this, &LauncherModel::applicationSurfaceCountChanged);
539 m_asAdapter->syncItems(m_list);
542 m_asAdapter->syncItems(m_list);
540 Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRunning});
543 Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRunning});
541}
544}
542
545
546+void LauncherModel::applicationSurfaceCountChanged(int count)
547+{
548+ ApplicationInfoInterface *app = static_cast<ApplicationInfoInterface*>(sender());
549+ int idx = findApplication(app->appId());
550+ if (idx < 0) {
551+ qWarning() << "Received a surface count changed event from an app that's not in the Launcher model";
552+ return;
553+ }
554+ LauncherItem *item = m_list.at(idx);
555+ if (item->surfaceCount() != count) {
556+ item->setSurfaceCount(count);
557+ Q_EMIT dataChanged(index(idx), index(idx), {RoleSurfaceCount});
558+ }
559+}
560+
543void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
561void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
544{
562{
545 Q_UNUSED(parent)
563 Q_UNUSED(parent)
546
564
565+ ApplicationInfoInterface *app = m_appManager->get(row);
547 int appIndex = -1;
566 int appIndex = -1;
548 for (int i = 0; i < m_list.count(); ++i) {
567 for (int i = 0; i < m_list.count(); ++i) {
549− if (m_list.at(i)->appId() == m_appManager->get(row)->appId()) {
568+ if (m_list.at(i)->appId() == app->appId()) {
550 appIndex = i;
569 appIndex = i;
551 break;
570 break;
552 }
571 }
@@ -557,17 +576,24 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
557 return;
576 return;
558 }
577 }
559
578
579+ disconnect(app, &ApplicationInfoInterface::surfaceCountChanged, this, &LauncherModel::applicationSurfaceCountChanged);
580+
560 LauncherItem * item = m_list.at(appIndex);
581 LauncherItem * item = m_list.at(appIndex);
561− item->setRunning(false);
562
582
563 if (!item->pinned()) {
583 if (!item->pinned()) {
564 beginRemoveRows(QModelIndex(), appIndex, appIndex);
584 beginRemoveRows(QModelIndex(), appIndex, appIndex);
565 m_list.takeAt(appIndex)->deleteLater();
585 m_list.takeAt(appIndex)->deleteLater();
566 endRemoveRows();
586 endRemoveRows();
567 m_asAdapter->syncItems(m_list);
587 m_asAdapter->syncItems(m_list);
588+ } else {
589+ QVector<int> changedRoles = {RoleRunning};
590+ item->setRunning(false);
591+ if (item->focused()) {
592+ changedRoles << RoleFocused;
593+ item->setFocused(false);
594+ }
568− Q_EMIT dataChanged(index(appIndex), index(appIndex), {RolePinned});
595+ Q_EMIT dataChanged(index(appIndex), index(appIndex), changedRoles);
569 }
596 }
570− Q_EMIT dataChanged(index(appIndex), index(appIndex), {RoleRunning});
571}
597}
572
598
573void LauncherModel::focusedAppIdChanged()
599void LauncherModel::focusedAppIdChanged()
@@ -576,8 +602,14 @@ void LauncherModel::focusedAppIdChanged()
576 for (int i = 0; i < m_list.count(); ++i) {
602 for (int i = 0; i < m_list.count(); ++i) {
577 LauncherItem *item = m_list.at(i);
603 LauncherItem *item = m_list.at(i);
578 if (!item->focused() && item->appId() == appId) {
604 if (!item->focused() && item->appId() == appId) {
605+ QVector<int> changedRoles;
606+ changedRoles << RoleFocused;
579 item->setFocused(true);
607 item->setFocused(true);
580− Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
608+ if (item->alerting()) {
609+ changedRoles << RoleAlerting;
610+ item->setAlerting(false);
611+ }
612+ Q_EMIT dataChanged(index(i), index(i), changedRoles);
581 } else if (item->focused() && item->appId() != appId) {
613 } else if (item->focused() && item->appId() != appId) {
582 item->setFocused(false);
614 item->setFocused(false);
583 Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
615 Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
plugins/Unity/Launcher/launchermodel.h+1−1
@@ -45,7 +45,6 @@ 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;
49 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
48 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
50 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
49 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
51 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
50 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
@@ -79,6 +78,7 @@ private Q_SLOTS:
79 void applicationAdded(const QModelIndex &parent, int row);
78 void applicationAdded(const QModelIndex &parent, int row);
80 void applicationRemoved(const QModelIndex &parent, int row);
79 void applicationRemoved(const QModelIndex &parent, int row);
81 void focusedAppIdChanged();
80 void focusedAppIdChanged();
81+ void applicationSurfaceCountChanged(int);
82
82
83private:
83private:
84 QList<LauncherItem*> m_list;
84 QList<LauncherItem*> m_list;
qml/Launcher/LauncherDelegate.qml+4−8
@@ -32,6 +32,7 @@ Item {
32 property bool alerting: false
32 property bool alerting: false
33 property bool highlighted: false
33 property bool highlighted: false
34 property bool shortcutHintShown: false
34 property bool shortcutHintShown: false
35+ property int surfaceCount: 1
35
36
36 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
37 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
37 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
38 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -114,12 +115,6 @@ Item {
114 duration: priv.wiggleDuration
115 duration: priv.wiggleDuration
115 easing.type: Easing.OutQuad
116 easing.type: Easing.OutQuad
116 }
117 }
117−
118− UbuntuNumberAnimation {
119− target: root
120− property: "alerting"
121− to: 0
122− }
123 }
118 }
124
119
125 Item {
120 Item {
@@ -221,12 +216,13 @@ Item {
221 }
216 }
222 spacing: units.gu(.5)
217 spacing: units.gu(.5)
223 Repeater {
218 Repeater {
219+ objectName: "surfacePipRepeater"
224− model: 1 // TODO: This should be "Math.min(3, app.surfaceCount)" once we have multiple surfaces
220+ model: Math.min(3, root.surfaceCount)
225 Rectangle {
221 Rectangle {
226 objectName: "runningHighlight" + index
222 objectName: "runningHighlight" + index
227 width: units.gu(0.25)
223 width: units.gu(0.25)
228 height: units.gu(.5)
224 height: units.gu(.5)
229− color: "white"
225+ color: root.alerting ? theme.palette.normal.activity : "white"
230 visible: root.itemRunning
226 visible: root.itemRunning
231 }
227 }
232 }
228 }
qml/Launcher/LauncherPanel.qml+2
@@ -239,6 +239,7 @@ Rectangle {
239 alerting: model.alerting
239 alerting: model.alerting
240 highlighted: root.highlightIndex == index
240 highlighted: root.highlightIndex == index
241 shortcutHintShown: root.shortcutHintsShown && index <= 9
241 shortcutHintShown: root.shortcutHintsShown && index <= 9
242+ surfaceCount: model.surfaceCount
242 z: -Math.abs(offset)
243 z: -Math.abs(offset)
243 maxAngle: 55
244 maxAngle: 55
244 property bool dragging: false
245 property bool dragging: false
@@ -271,6 +272,7 @@ Rectangle {
271
272
272 PropertyAction { target: launcherListViewItem; property: "clip"; value: 1 }
273 PropertyAction { target: launcherListViewItem; property: "clip"; value: 1 }
273 PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 0 : 1 }
274 PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 0 : 1 }
275+ PropertyAction { target: launcherListView; property: "peekingIndex"; value: -1 }
274 }
276 }
275
277
276 onAlertingChanged: {
278 onAlertingChanged: {
qml/Stages/ApplicationWindow.qml+25−9
@@ -121,15 +121,12 @@ FocusScope {
121
121
122 property bool surfaceOldEnoughToBeResized: false
122 property bool surfaceOldEnoughToBeResized: false
123
123
124− property var focusedSurface: {
125− if (promptSurfacesRepeater.count == 0) {
124+ property Item focusedSurface: promptSurfacesRepeater.count === 0 ? surfaceContainer
126− return surfaceContainer;
127− } else {
128− return promptSurfacesRepeater.itemAt(promptSurfacesRepeater.count - 1);
125+ : promptSurfacesRepeater.first
129− }
130− }
131 onFocusedSurfaceChanged: {
126 onFocusedSurfaceChanged: {
127+ if (focusedSurface) {
132− focusedSurface.focus = true;
128+ focusedSurface.focus = true;
129+ }
133 }
130 }
134 }
131 }
135
132
@@ -158,6 +155,7 @@ FocusScope {
158 objectName: "screenshotImage"
155 objectName: "screenshotImage"
159 anchors.fill: parent
156 anchors.fill: parent
160 antialiasing: !root.interactive
157 antialiasing: !root.interactive
158+ z: 1
161
159
162 function take() {
160 function take() {
163 // Save memory by using a half-resolution (thus quarter size) screenshot.
161 // Save memory by using a half-resolution (thus quarter size) screenshot.
@@ -175,6 +173,7 @@ FocusScope {
175 visible: active
173 visible: active
176 active: false
174 active: false
177 anchors.fill: parent
175 anchors.fill: parent
176+ z: screenshotImage.z + 1
178 sourceComponent: Component {
177 sourceComponent: Component {
179 Splash {
178 Splash {
180 id: splash
179 id: splash
@@ -191,6 +190,7 @@ FocusScope {
191
190
192 SurfaceContainer {
191 SurfaceContainer {
193 id: surfaceContainer
192 id: surfaceContainer
193+ z: splashLoader.z + 1
194 requestedWidth: root.requestedWidth
194 requestedWidth: root.requestedWidth
195 requestedHeight: root.requestedHeight
195 requestedHeight: root.requestedHeight
196 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
196 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
@@ -208,12 +208,28 @@ FocusScope {
208 }
208 }
209 }
209 }
210 delegate: SurfaceContainer {
210 delegate: SurfaceContainer {
211+ id: promptSurfaceContainer
211− interactive: index == (promptSurfacesRepeater.count - 1) && root.interactive
212+ interactive: index === 0 && root.interactive
212 surface: model.surface
213 surface: model.surface
213 width: root.width
214 width: root.width
214 height: root.height
215 height: root.height
215 isPromptSurface: true
216 isPromptSurface: true
217+ z: surfaceContainer.z + (promptSurfacesRepeater.count - index)
218+ property int index: model.index
219+ onIndexChanged: updateFirst()
220+ Component.onCompleted: updateFirst()
221+ function updateFirst() {
222+ if (index === 0) {
223+ promptSurfacesRepeater.first = promptSurfaceContainer;
224+ }
225+ }
226+ }
227+ onCountChanged: {
228+ if (count === 0) {
229+ first = null;
230+ }
216 }
231 }
232+ property Item first: null
217 }
233 }
218
234
219 // SurfaceContainer size drives ApplicationWindow size
235 // SurfaceContainer size drives ApplicationWindow size
tests/mocks/Unity/Application/ApplicationInfo.cpp+29−27
@@ -55,8 +55,10 @@ QString stateToStr(ApplicationInfo::State state)
55ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
55ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
56 : ApplicationInfoInterface(appId, parent)
56 : ApplicationInfoInterface(appId, parent)
57 , m_appId(appId)
57 , m_appId(appId)
58+ , m_surfaceList(new MirSurfaceListModel(this))
59+ , m_promptSurfaceList(new MirSurfaceListModel(this))
58{
60{
59− connect(&m_surfaceList, &MirSurfaceListModel::countChanged,
61+ connect(m_surfaceList, &MirSurfaceListModel::countChanged,
60 this, &ApplicationInfo::onSurfaceCountChanged, Qt::QueuedConnection);
62 this, &ApplicationInfo::onSurfaceCountChanged, Qt::QueuedConnection);
61
63
62 m_surfaceCreationTimer.setSingleShot(true);
64 m_surfaceCreationTimer.setSingleShot(true);
@@ -78,8 +80,8 @@ void ApplicationInfo::createSurface()
78 if (state() == ApplicationInfo::Stopped) { return; }
80 if (state() == ApplicationInfo::Stopped) { return; }
79
81
80 QString surfaceName = name();
82 QString surfaceName = name();
81− if (m_surfaceList.count() > 0) {
83+ if (m_surfaceList->count() > 0) {
82− surfaceName.append(QString(" %1").arg(m_surfaceList.count()+1));
84+ surfaceName.append(QString(" %1").arg(m_surfaceList->count()+1));
83 }
85 }
84
86
85 auto surfaceManager = SurfaceManager::instance();
87 auto surfaceManager = SurfaceManager::instance();
@@ -95,7 +97,7 @@ void ApplicationInfo::createSurface()
95
97
96 surface->setShellChrome(m_shellChrome);
98 surface->setShellChrome(m_shellChrome);
97
99
98− m_surfaceList.appendSurface(surface);
100+ m_surfaceList->appendSurface(surface);
99
101
100 ++m_liveSurfaceCount;
102 ++m_liveSurfaceCount;
101 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
103 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
@@ -186,16 +188,16 @@ void ApplicationInfo::setState(State value)
186 if (value != m_state) {
188 if (value != m_state) {
187 DEBUG_MSG(qPrintable(stateToStr(value)));
189 DEBUG_MSG(qPrintable(stateToStr(value)));
188 if (!m_manualSurfaceCreation && value == ApplicationInfo::Starting) {
190 if (!m_manualSurfaceCreation && value == ApplicationInfo::Starting) {
189− Q_ASSERT(m_surfaceList.count() == 0);
191+ Q_ASSERT(m_surfaceList->count() == 0);
190 m_surfaceCreationTimer.start();
192 m_surfaceCreationTimer.start();
191 } else if (value == ApplicationInfo::Stopped) {
193 } else if (value == ApplicationInfo::Stopped) {
192 m_surfaceCreationTimer.stop();
194 m_surfaceCreationTimer.stop();
193− for (int i = 0; i < m_surfaceList.count(); ++i) {
195+ for (int i = 0; i < m_surfaceList->count(); ++i) {
194− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
196+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
195 surface->setLive(false);
197 surface->setLive(false);
196 }
198 }
197− for (int i = 0; i < m_promptSurfaceList.count(); ++i) {
199+ for (int i = 0; i < m_promptSurfaceList->count(); ++i) {
198− auto surface = static_cast<MirSurface*>(m_promptSurfaceList.get(i));
200+ auto surface = static_cast<MirSurface*>(m_promptSurfaceList->get(i));
199 surface->setLive(false);
201 surface->setLive(false);
200 }
202 }
201 }
203 }
@@ -209,9 +211,9 @@ void ApplicationInfo::close()
209{
211{
210 DEBUG_MSG("");
212 DEBUG_MSG("");
211
213
212− if (m_surfaceList.count() > 0) {
214+ if (m_surfaceList->count() > 0) {
213− for (int i = 0; i < m_surfaceList.count(); ++i) {
215+ for (int i = 0; i < m_surfaceList->count(); ++i) {
214− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
216+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
215 surface->close();
217 surface->close();
216 }
218 }
217 } else {
219 } else {
@@ -223,15 +225,15 @@ void ApplicationInfo::close()
223void ApplicationInfo::setFullscreen(bool value)
225void ApplicationInfo::setFullscreen(bool value)
224{
226{
225 m_fullscreen = value;
227 m_fullscreen = value;
226− if (m_surfaceList.rowCount() > 0) {
228+ if (m_surfaceList->rowCount() > 0) {
227− m_surfaceList.get(0)->setState(Mir::FullscreenState);
229+ m_surfaceList->get(0)->setState(Mir::FullscreenState);
228 }
230 }
229}
231}
230
232
231bool ApplicationInfo::fullscreen() const
233bool ApplicationInfo::fullscreen() const
232{
234{
233− if (m_surfaceList.rowCount() > 0) {
235+ if (m_surfaceList->rowCount() > 0) {
234− return m_surfaceList.get(0)->state() == Mir::FullscreenState;
236+ return m_surfaceList->get(0)->state() == Mir::FullscreenState;
235 } else {
237 } else {
236 return m_fullscreen;
238 return m_fullscreen;
237 }
239 }
@@ -341,16 +343,16 @@ void ApplicationInfo::setInitialSurfaceSize(const QSize &size)
341void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)
343void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)
342{
344{
343 m_shellChrome = shellChrome;
345 m_shellChrome = shellChrome;
344− if (m_surfaceList.rowCount() > 0) {
346+ if (m_surfaceList->rowCount() > 0) {
345− static_cast<MirSurface*>(m_surfaceList.get(0))->setShellChrome(shellChrome);
347+ static_cast<MirSurface*>(m_surfaceList->get(0))->setShellChrome(shellChrome);
346 }
348 }
347}
349}
348
350
349bool ApplicationInfo::focused() const
351bool ApplicationInfo::focused() const
350{
352{
351 bool someSurfaceHasFocus = false; // to be proven wrong
353 bool someSurfaceHasFocus = false; // to be proven wrong
352− for (int i = 0; i < m_surfaceList.count() && !someSurfaceHasFocus; ++i) {
354+ for (int i = 0; i < m_surfaceList->count() && !someSurfaceHasFocus; ++i) {
353− someSurfaceHasFocus = m_surfaceList.get(i)->focused();
355+ someSurfaceHasFocus = m_surfaceList->get(i)->focused();
354 }
356 }
355 return someSurfaceHasFocus;
357 return someSurfaceHasFocus;
356}
358}
@@ -362,12 +364,12 @@ void ApplicationInfo::setFocused(bool value)
362 }
364 }
363
365
364 if (value) {
366 if (value) {
365− if (m_surfaceList.count() > 0) {
367+ if (m_surfaceList->count() > 0) {
366− m_surfaceList.get(0)->requestFocus();
368+ m_surfaceList->get(0)->requestFocus();
367 }
369 }
368 } else {
370 } else {
369− for (int i = 0; i < m_surfaceList.count(); ++i) {
371+ for (int i = 0; i < m_surfaceList->count(); ++i) {
370− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
372+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
371 if (surface->focused()) {
373 if (surface->focused()) {
372 surface->setFocused(false);
374 surface->setFocused(false);
373 }
375 }
@@ -377,16 +379,16 @@ void ApplicationInfo::setFocused(bool value)
377
379
378void ApplicationInfo::onSurfaceCountChanged()
380void ApplicationInfo::onSurfaceCountChanged()
379{
381{
380− if (m_surfaceList.count() == 0 && m_state == Running) {
382+ if (m_surfaceList->count() == 0 && m_state == Running) {
381 setState(Stopped);
383 setState(Stopped);
382 }
384 }
383}
385}
384
386
385void ApplicationInfo::requestFocus()
387void ApplicationInfo::requestFocus()
386{
388{
387− if (m_surfaceList.count() == 0) {
389+ if (m_surfaceList->count() == 0) {
388 Q_EMIT focusRequested();
390 Q_EMIT focusRequested();
389 } else {
391 } else {
390− m_surfaceList.get(0)->requestFocus();
392+ m_surfaceList->get(0)->requestFocus();
391 }
393 }
392}
394}
tests/mocks/Unity/Application/ApplicationInfo.h+5−4
@@ -107,8 +107,9 @@ public:
107
107
108 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
108 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
109
109
110− MirSurfaceListInterface* surfaceList() override { return &m_surfaceList; }
110+ MirSurfaceListInterface* surfaceList() const override { return m_surfaceList; }
111− MirSurfaceListInterface* promptSurfaceList() override { return &m_promptSurfaceList; }
111+ MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
112+ int surfaceCount() const override { return m_surfaceList->count(); }
112
113
113 void setFocused(bool value);
114 void setFocused(bool value);
114
115
@@ -148,8 +149,8 @@ private:
148 bool m_isTouchApp{true};
149 bool m_isTouchApp{true};
149 bool m_exemptFromLifecycle{false};
150 bool m_exemptFromLifecycle{false};
150 QSize m_initialSurfaceSize;
151 QSize m_initialSurfaceSize;
151− MirSurfaceListModel m_surfaceList;
152+ MirSurfaceListModel *m_surfaceList;
152− MirSurfaceListModel m_promptSurfaceList;
153+ MirSurfaceListModel *m_promptSurfaceList;
153 int m_liveSurfaceCount{0};
154 int m_liveSurfaceCount{0};
154 QTimer m_surfaceCreationTimer;
155 QTimer m_surfaceCreationTimer;
155 QList<MirSurface*> m_closingSurfaces;
156 QList<MirSurface*> m_closingSurfaces;
tests/mocks/Unity/Application/MirSurfaceListModel.cpp+13−3
@@ -66,12 +66,22 @@ void MirSurfaceListModel::appendSurface(MirSurface *surface)
66 m_surfaceList.append(surface);
66 m_surfaceList.append(surface);
67 connectSurface(surface);
67 connectSurface(surface);
68 endInsertRows();
68 endInsertRows();
69− Q_EMIT countChanged();
69+ Q_EMIT countChanged(m_surfaceList.count());
70 if (m_surfaceList.count() == 1) {
70 if (m_surfaceList.count() == 1) {
71 Q_EMIT firstChanged();
71 Q_EMIT firstChanged();
72 }
72 }
73}
73}
74
74
75+void MirSurfaceListModel::prependSurface(MirSurface *surface)
76+{
77+ beginInsertRows(QModelIndex(), 0, 0);
78+ m_surfaceList.prepend(surface);
79+ connectSurface(surface);
80+ endInsertRows();
81+ Q_EMIT countChanged(m_surfaceList.count());
82+ Q_EMIT firstChanged();
83+}
84+
75void MirSurfaceListModel::connectSurface(MirSurface *surface)
85void MirSurfaceListModel::connectSurface(MirSurface *surface)
76{
86{
77 connect(surface, &MirSurface::raiseRequested, this, [this, surface](){ this->raise(surface); });
87 connect(surface, &MirSurface::raiseRequested, this, [this, surface](){ this->raise(surface); });
@@ -85,7 +95,7 @@ void MirSurfaceListModel::removeSurface(MirSurface *surface)
85 beginRemoveRows(QModelIndex(), i, i);
95 beginRemoveRows(QModelIndex(), i, i);
86 m_surfaceList.removeAt(i);
96 m_surfaceList.removeAt(i);
87 endRemoveRows();
97 endRemoveRows();
88− Q_EMIT countChanged();
98+ Q_EMIT countChanged(m_surfaceList.count());
89 if (m_surfaceList.count() == 0 || i == 0) {
99 if (m_surfaceList.count() == 0 || i == 0) {
90 Q_EMIT firstChanged();
100 Q_EMIT firstChanged();
91 }
101 }
@@ -143,7 +153,7 @@ MirSurfaceInterface *MirSurfaceListModel::createSurface()
143 Mir::RestoredState,
153 Mir::RestoredState,
144 screenshotUrl);
154 screenshotUrl);
145
155
146− appendSurface(surface);
156+ prependSurface(surface);
147
157
148 return surface;
158 return surface;
149}
159}
tests/mocks/Unity/Application/MirSurfaceListModel.h+1
@@ -49,6 +49,7 @@ public:
49 Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
49 Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
50
50
51private:
51private:
52+ void prependSurface(MirSurface *surface);
52 void raise(MirSurface *surface);
53 void raise(MirSurface *surface);
53 void moveSurface(int from, int to);
54 void moveSurface(int from, int to);
54 void connectSurface(MirSurface *surface);
55 void connectSurface(MirSurface *surface);
tests/mocks/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=9)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+14
@@ -38,6 +38,7 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
38 m_countVisible(false),
38 m_countVisible(false),
39 m_focused(false),
39 m_focused(false),
40 m_alerting(false),
40 m_alerting(false),
41+ m_surfaceCount(0),
41 m_quickList(new MockQuickListModel(this))
42 m_quickList(new MockQuickListModel(this))
42{
43{
43}
44}
@@ -182,6 +183,19 @@ void MockLauncherItem::setAlerting(bool alerting)
182 }
183 }
183}
184}
184
185
186+int MockLauncherItem::surfaceCount() const
187+{
188+ return m_surfaceCount;
189+}
190+
191+void MockLauncherItem::setSurfaceCount(int surfaceCount)
192+{
193+ if (m_surfaceCount != surfaceCount) {
194+ m_surfaceCount = surfaceCount;
195+ Q_EMIT surfaceCountChanged(surfaceCount);
196+ }
197+}
198+
185unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
199unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
186{
200{
187 return m_quickList;
201 return m_quickList;
tests/mocks/Unity/Launcher/MockLauncherItem.h+3
@@ -46,6 +46,7 @@ public:
46 bool countVisible() const override;
46 bool countVisible() const override;
47 bool focused() const override;
47 bool focused() const override;
48 bool alerting() const override;
48 bool alerting() const override;
49+ int surfaceCount() const override;
49
50
50 unity::shell::launcher::QuickListModelInterface *quickList() const override;
51 unity::shell::launcher::QuickListModelInterface *quickList() const override;
51
52
@@ -58,6 +59,7 @@ private:
58 void setCountVisible(bool countVisible);
59 void setCountVisible(bool countVisible);
59 void setFocused(bool focused);
60 void setFocused(bool focused);
60 void setAlerting(bool alerting);
61 void setAlerting(bool alerting);
62+ void setSurfaceCount(int surfaceCount);
61
63
62 QString m_appId;
64 QString m_appId;
63 QString m_desktopFile;
65 QString m_desktopFile;
@@ -71,6 +73,7 @@ private:
71 bool m_countVisible;
73 bool m_countVisible;
72 bool m_focused;
74 bool m_focused;
73 bool m_alerting;
75 bool m_alerting;
76+ int m_surfaceCount;
74 MockQuickListModel *m_quickList;
77 MockQuickListModel *m_quickList;
75
78
76 friend class MockLauncherModel;
79 friend class MockLauncherModel;
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+5
@@ -25,6 +25,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
25 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
25 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
26 item->setProgress(0);
26 item->setProgress(0);
27 item->setPinned(true);
27 item->setPinned(true);
28+ item->setSurfaceCount(1);
28 item->setRunning(true);
29 item->setRunning(true);
29 item->setFocused(true);
30 item->setFocused(true);
30 m_list.append(item);
31 m_list.append(item);
@@ -36,6 +37,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
36 item->setProgress(50);
37 item->setProgress(50);
37 item->setCountVisible(true);
38 item->setCountVisible(true);
38 item->setRunning(true);
39 item->setRunning(true);
40+ item->setSurfaceCount(2);
39 item->setAlerting(false);
41 item->setAlerting(false);
40 m_list.append(item);
42 m_list.append(item);
41 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
43 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
@@ -44,6 +46,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
44 item->setProgress(150);
46 item->setProgress(150);
45 m_list.append(item);
47 m_list.append(item);
46 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
48 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
49+ item->setSurfaceCount(5);
47 item->setCount(1);
50 item->setCount(1);
48 item->setCountVisible(true);
51 item->setCountVisible(true);
49 item->setRunning(true);
52 item->setRunning(true);
@@ -119,6 +122,8 @@ QVariant MockLauncherModel::data(const QModelIndex& index, int role) const
119 return item->focused();
122 return item->focused();
120 case RoleAlerting:
123 case RoleAlerting:
121 return item->alerting();
124 return item->alerting();
125+ case RoleSurfaceCount:
126+ return item->surfaceCount();
122 }
127 }
123
128
124 return QVariant();
129 return QVariant();
tests/mocks/Unity/Launcher/MockLauncherModel.h+1−1
@@ -38,7 +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 void setAlerting(const QString &appId, bool alerting); // Only for testing
42 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
42 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
43 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
43 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
44 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
44 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
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=7)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=8)
3
3
4include_directories(
4include_directories(
5 ${CMAKE_CURRENT_SOURCE_DIR}
5 ${CMAKE_CURRENT_SOURCE_DIR}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+22−2
@@ -66,14 +66,17 @@ public:
66 void setExemptFromLifecycle(bool) override {}
66 void setExemptFromLifecycle(bool) override {}
67 QSize initialSurfaceSize() const override { return QSize(); }
67 QSize initialSurfaceSize() const override { return QSize(); }
68 void setInitialSurfaceSize(const QSize &) override {}
68 void setInitialSurfaceSize(const QSize &) override {}
69− MirSurfaceListInterface* surfaceList() override { return nullptr; }
69+ MirSurfaceListInterface* surfaceList() const override { return nullptr; }
70− MirSurfaceListInterface* promptSurfaceList() override { return nullptr; }
70+ MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
71+ int surfaceCount() const override { return m_surfaceCount; }
72+ void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
71
73
72 // Methods used for mocking (not in the interface)
74 // Methods used for mocking (not in the interface)
73 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
75 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
74private:
76private:
75 QString m_appId;
77 QString m_appId;
76 bool m_focused;
78 bool m_focused;
79+ int m_surfaceCount = 0;
77};
80};
78
81
79// This is a mock, specifically to test the LauncherModel
82// This is a mock, specifically to test the LauncherModel
@@ -488,6 +491,10 @@ private Q_SLOTS:
488
491
489 // Finally check, that the change to "count" implicitly also set the alerting-state to true
492 // Finally check, that the change to "count" implicitly also set the alerting-state to true
490 QVERIFY(launcherModel->get(index)->alerting() == true);
493 QVERIFY(launcherModel->get(index)->alerting() == true);
494+
495+ // Focus the app, make sure the alert gets cleared
496+ appManager->focusApplication("abs-icon");
497+ QVERIFY(launcherModel->get(index)->alerting() == false);
491 }
498 }
492
499
493 void testCountEmblemAddsRemovesItem_data() {
500 void testCountEmblemAddsRemovesItem_data() {
@@ -559,6 +566,10 @@ private Q_SLOTS:
559
566
560 // Check that the alerting-status is now true
567 // Check that the alerting-status is now true
561 QVERIFY(launcherModel->get(index)->alerting() == true);
568 QVERIFY(launcherModel->get(index)->alerting() == true);
569+
570+ // Focus the app, make sure the alert gets cleared
571+ appManager->focusApplication("abs-icon");
572+ QVERIFY(launcherModel->get(index)->alerting() == false);
562 }
573 }
563
574
564 void testRefreshAfterDeletedDesktopFiles_data() {
575 void testRefreshAfterDeletedDesktopFiles_data() {
@@ -692,6 +703,15 @@ private Q_SLOTS:
692 QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), true);
703 QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), true);
693 QCOMPARE(getASConfig().at(index).value("count").toInt(), 55);
704 QCOMPARE(getASConfig().at(index).value("count").toInt(), 55);
694 }
705 }
706+
707+ void testSurfaceCountUpdates() {
708+ QString appId = launcherModel->get(0)->appId();
709+
710+ QCOMPARE(launcherModel->get(0)->surfaceCount(), 0);
711+ MockApp *app = qobject_cast<MockApp*>(appManager->findApplication(appId));
712+ app->setSurfaceCount(1);
713+ QCOMPARE(launcherModel->get(0)->surfaceCount(), 1);
714+ }
695};
715};
696
716
697QTEST_GUILESS_MAIN(LauncherModelTest)
717QTEST_GUILESS_MAIN(LauncherModelTest)
tests/qmltests/Launcher/tst_Launcher.qml+25−9
@@ -486,25 +486,26 @@ Rectangle {
486 function test_progressOverlays() {
486 function test_progressOverlays() {
487 dragLauncherIntoView();
487 dragLauncherIntoView();
488 var launcherListView = findChild(launcher, "launcherListView");
488 var launcherListView = findChild(launcher, "launcherListView");
489+ var moveAnimation = findInvisibleChild(launcherListView, "moveAnimation")
489 for (var i = 0; i < launcherListView.count; ++i) {
490 for (var i = 0; i < launcherListView.count; ++i) {
490− var delegate = findChild(launcherListView, "launcherDelegate" + i)
491+ launcherListView.moveToIndex(i);
491− compare(findChild(delegate, "progressOverlay").visible, LauncherModel.get(i).progress >= 0)
492+ waitForRendering(launcherListView);
492− }
493+ tryCompare(moveAnimation, "running", false);
493− }
494
494
495− function test_runningHighlight() {
496− dragLauncherIntoView();
497− var launcherListView = findChild(launcher, "launcherListView");
498− for (var i = 0; i < launcherListView.count; ++i) {
499 var delegate = findChild(launcherListView, "launcherDelegate" + i)
495 var delegate = findChild(launcherListView, "launcherDelegate" + i)
500− compare(findChild(delegate, "runningHighlight0").visible, LauncherModel.get(i).running)
496+ compare(findChild(delegate, "progressOverlay").visible, LauncherModel.get(i).progress >= 0)
501 }
497 }
502 }
498 }
503
499
504 function test_focusedHighlight() {
500 function test_focusedHighlight() {
505 dragLauncherIntoView();
501 dragLauncherIntoView();
506 var launcherListView = findChild(launcher, "launcherListView");
502 var launcherListView = findChild(launcher, "launcherListView");
503+ var moveAnimation = findInvisibleChild(launcherListView, "moveAnimation")
504+
507 for (var i = 0; i < launcherListView.count; ++i) {
505 for (var i = 0; i < launcherListView.count; ++i) {
506+ launcherListView.moveToIndex(i);
507+ waitForRendering(launcherListView);
508+ tryCompare(moveAnimation, "running", false);
508 var delegate = findChild(launcherListView, "launcherDelegate" + i)
509 var delegate = findChild(launcherListView, "launcherDelegate" + i)
509 compare(findChild(delegate, "focusedHighlight").visible, LauncherModel.get(i).focused)
510 compare(findChild(delegate, "focusedHighlight").visible, LauncherModel.get(i).focused)
510 }
511 }
@@ -1295,5 +1296,20 @@ Rectangle {
1295
1296
1296 assertFocusOnIndex(-2);
1297 assertFocusOnIndex(-2);
1297 }
1298 }
1299+
1300+ function test_surfaceCountPips() {
1301+ var launcherListView = findChild(launcher, "launcherListView")
1302+ var moveAnimation = findInvisibleChild(launcherListView, "moveAnimation")
1303+
1304+ for (var i = 0; i < launcherListView.count; i++) {
1305+ launcherListView.moveToIndex(i);
1306+ waitForRendering(launcherListView);
1307+ tryCompare(moveAnimation, "running", false);
1308+
1309+ var delegate = findChild(launcher, "launcherDelegate" + i);
1310+ var surfacePipRepeater = findInvisibleChild(delegate, "surfacePipRepeater");
1311+ compare(surfacePipRepeater.model, Math.min(3, LauncherModel.get(i).surfaceCount))
1312+ }
1313+ }
1298 }
1314 }
1299}
1315}
tests/qmltests/Stages/ApplicationCheckBox.qml+1−1
@@ -156,7 +156,7 @@ Column {
156 width: height
156 width: height
157 height: promptsLabel.height * 0.7
157 height: promptsLabel.height * 0.7
158 anchors.verticalCenter: parent.verticalCenter
158 anchors.verticalCenter: parent.verticalCenter
159− onClicked: d.application.promptSurfaceList.get(d.application.promptSurfaceList.count - 1).close()
159+ onClicked: d.application.promptSurfaceList.get(0).close()
160 enabled: d.application && d.application.promptSurfaceList.count > 0
160 enabled: d.application && d.application.promptSurfaceList.count > 0
161 Label { text: "➖"; anchors.centerIn: parent; enabled: parent.enabled }
161 Label { text: "➖"; anchors.centerIn: parent; enabled: parent.enabled }
162 }
162 }
tests/qmltests/Stages/tst_ApplicationWindow.qml+39−9
@@ -106,7 +106,7 @@ Rectangle {
106 enabled: parent.promptSurfaceList !== null && parent.promptSurfaceList.count > 0
106 enabled: parent.promptSurfaceList !== null && parent.promptSurfaceList.count > 0
107 activeFocusOnPress: false
107 activeFocusOnPress: false
108 text: "Remove"
108 text: "Remove"
109− onClicked: { parent.promptSurfaceList.get(parent.promptSurfaceList.count - 1).close(); }
109+ onClicked: { parent.promptSurfaceList.get(0).close(); }
110 }
110 }
111
111
112 Button {
112 Button {
@@ -449,20 +449,21 @@ Rectangle {
449 for (i = 0; i < 3; i++) {
449 for (i = 0; i < 3; i++) {
450 promptSurfaceList.createSurface();
450 promptSurfaceList.createSurface();
451 compare(promptSurfaces.count, i+1);
451 compare(promptSurfaces.count, i+1);
452− waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(promptSurfaces.count - 1));
452+ waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
453 }
453 }
454
454
455− for (i = 2; i >= 0; --i) {
455+ for (i = 3; i > 0; --i) {
456− var promptSurface = promptSurfaceList.get(i);
456+ var promptSurface = promptSurfaceList.get(0);
457 compare(promptSurface.activeFocus, true);
457 compare(promptSurface.activeFocus, true);
458
458
459 promptSurface.close();
459 promptSurface.close();
460+ promptSurface = null;
460− tryCompareFunction(function() { return promptSurfaces.count; }, i);
461+ tryCompareFunction(function() { return promptSurfaces.count; }, i-1);
461
462
462− if (i > 0) {
463+ if (promptSurfaces.count > 0) {
463− // active focus should have gone to the yongest remaining sibling
464+ // active focus should have gone to the new head of the list
464− var previousPromptSurface = promptSurfaceList.get(i-1);
465+ promptSurface = promptSurfaceList.get(0);
465− tryCompare(previousPromptSurface, "activeFocus", true);
466+ tryCompare(promptSurface, "activeFocus", true);
466 } else {
467 } else {
467 // active focus should have gone to the application surface
468 // active focus should have gone to the application surface
468 tryCompare(applicationWindow.surface, "activeFocus", true);
469 tryCompare(applicationWindow.surface, "activeFocus", true);
@@ -498,5 +499,34 @@ Rectangle {
498 delegate.surface.close();
499 delegate.surface.close();
499 tryCompare(promptSurfaces, "count", 0);
500 tryCompare(promptSurfaces, "count", 0);
500 }
501 }
502+
503+ // Check that the z value of SurfaceContainers for prompt surfaces go from highest
504+ // for index 0 to lowest for the last index in the prompt surface list.
505+ // Regression test for https://bugs.launchpad.net/bugs/1586219
506+ function test_promptSurfacesZOrdering() {
507+ var promptSurfaceList = root.fakeApplication.promptSurfaceList;
508+ var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
509+
510+ promptSurfaceList.createSurface();
511+
512+ for (var i = 2; i <= 3; i++) {
513+ promptSurfaceList.createSurface();
514+ tryCompare(promptSurfaces, "count", i);
515+ waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
516+
517+ for (var j = 1; j < promptSurfaces.count; j++) {
518+ var delegate = promptSurfaces.itemAt(j);
519+ var previousDelegate = promptSurfaces.itemAt(j-1);
520+ verify(previousDelegate.z > delegate.z);
521+ }
522+ }
523+
524+ // clean up
525+ while (promptSurfaceList.count > 0) {
526+ var currentCount = promptSurfaceList.count;
527+ promptSurfaceList.get(0).close();
528+ tryCompare(promptSurfaceList, "count", currentCount - 1);
529+ }
530+ }
501 }
531 }
502}
532}