on xenial help sign in
TopLevelWindowModel: don't put hidden windows in the model

From the UI point of view, it's as if they didn't exist.

commitb020446e9155efd01badedfcea41b3f280c14d51 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateThu 16 Mar 2017, 12:30 (10 years ago) parent2a9c520
12 files changed+487−39
plugins/WindowManager/CMakeLists.txt+2 plugins/WindowManager/TopLevelWindowModel.cpp+64−20 plugins/WindowManager/TopLevelWindowModel.h+6−2 plugins/WindowManager/Window.cpp+6 plugins/WindowManager/Window.h+4−1 plugins/WindowManager/WindowManagerGlobal.h+23 tests/mocks/Unity/Application/MirSurfaceListModel.cpp+7−7 tests/mocks/Unity/Application/MirSurfaceListModel.h+7−9 tests/plugins/CMakeLists.txt+1 tests/plugins/WindowManager/CMakeLists.txt+34 tests/plugins/WindowManager/UnityApplicationMocks.h+203 tests/plugins/WindowManager/tst_TopLevelWindowModel.cpp+130
plugins/WindowManager/CMakeLists.txt+2
@@ -9,6 +9,8 @@ set(WINDOWMANAGER_SRC
99 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
1010 )
1111
12+add_definitions(-DWINDOWMANAGERQML_LIBRARY)
13+
1214add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
1315
1416qt5_use_modules(windowmanager-qml Qml Quick Gui)
plugins/WindowManager/TopLevelWindowModel.cpp+64−20
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2016 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -130,7 +130,7 @@ void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *
130130 int i = 0;
131131 while (i < m_windowModel.count()) {
132132 if (m_windowModel.at(i).application == application) {
133− removeAt(i);
133+ deleteAt(i);
134134 } else {
135135 ++i;
136136 }
@@ -148,12 +148,13 @@ void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface,
148148{
149149 Q_ASSERT(surface != nullptr);
150150
151+ connectSurface(surface);
152+
151153 bool filledPlaceholder = false;
152154 for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
153155 ModelEntry &entry = m_windowModel[i];
154156 if (entry.application == application && entry.window->surface() == nullptr) {
155157 entry.window->setSurface(surface);
156− connectSurface(surface);
157158 INFO_MSG << " appId=" << application->appId() << " surface=" << surface
158159 << ", filling out placeholder. after: " << toString();
159160 filledPlaceholder = true;
@@ -167,6 +168,34 @@ void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface,
167168}
168169
169170void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
171+{
172+
173+ Window *window = createWindow(surface);
174+
175+ connect(window, &Window::stateChanged, this, [=](Mir::State newState) {
176+ if (newState == Mir::HiddenState) {
177+ // Comply, removing it from our model. Just as if it didn't exist anymore.
178+ removeAt(indexForId(window->id()));
179+ } else {
180+ if (indexForId(window->id()) == -1) {
181+ // was probably hidden before. put it back on the list
182+ auto *application = m_applicationManager->findApplicationWithSurface(window->surface());
183+ Q_ASSERT(application);
184+ prependWindow(window, application);
185+ }
186+ }
187+ });
188+
189+ prependWindow(window, application);
190+
191+ if (!surface) {
192+ activateEmptyWindow(window);
193+ }
194+
195+ INFO_MSG << " after " << toString();
196+}
197+
198+void TopLevelWindowModel::prependWindow(Window *window, unityapi::ApplicationInfoInterface *application)
170199{
171200 if (m_modelState == IdleState) {
172201 m_modelState = InsertingState;
@@ -176,12 +205,7 @@ void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *su
176205 // No point in signaling anything if we're resetting the whole model
177206 }
178207
179− Window *window = createWindow(surface);
180−
181208 m_windowModel.prepend(ModelEntry(window, application));
182− if (surface) {
183− connectSurface(surface);
184− }
185209
186210 if (m_modelState == InsertingState) {
187211 endInsertRows();
@@ -189,12 +213,6 @@ void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *su
189213 Q_EMIT listChanged();
190214 m_modelState = IdleState;
191215 }
192−
193− if (!surface) {
194− activateEmptyWindow(window);
195− }
196−
197− INFO_MSG << " after " << toString();
198216}
199217
200218void TopLevelWindowModel::connectWindow(Window *window)
@@ -243,6 +261,13 @@ void TopLevelWindowModel::connectWindow(Window *window)
243261 connect(window, &Window::emptyWindowActivated, this, [this, window]() {
244262 activateEmptyWindow(window);
245263 });
264+
265+ connect(window, &Window::liveChanged, this, [this, window](bool isAlive) {
266+ if (!isAlive && window->state() == Mir::HiddenState) {
267+ // Hidden windows are not in the model. So just delete it right away.
268+ delete window;
269+ }
270+ });
246271}
247272
248273void TopLevelWindowModel::activateEmptyWindow(Window *window)
@@ -308,7 +333,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
308333 }
309334
310335 if (m_windowModel[i].removeOnceSurfaceDestroyed) {
311− removeAt(i);
336+ deleteAt(i);
312337 } else {
313338 auto window = m_windowModel[i].window;
314339 window->setSurface(nullptr);
@@ -346,7 +371,16 @@ void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surfac
346371 } else {
347372 auto *application = m_applicationManager->findApplicationWithSurface(surface);
348373 if (application) {
349− prependSurface(surface, application);
374+ if (surface->state() == Mir::HiddenState) {
375+ // Ignore it until it's finally shown
376+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
377+ Q_ASSERT(newState != Mir::HiddenState);
378+ disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
379+ prependSurface(surface, application);
380+ });
381+ } else {
382+ prependSurface(surface, application);
383+ }
350384 } else {
351385 // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
352386 // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
@@ -361,6 +395,17 @@ void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surfac
361395 }
362396}
363397
398+void TopLevelWindowModel::deleteAt(int index)
399+{
400+ auto window = m_windowModel[index].window;
401+
402+ removeAt(index);
403+
404+ window->setSurface(nullptr);
405+
406+ delete window;
407+}
408+
364409void TopLevelWindowModel::removeAt(int index)
365410{
366411 if (m_modelState == IdleState) {
@@ -373,8 +418,9 @@ void TopLevelWindowModel::removeAt(int index)
373418
374419 auto window = m_windowModel[index].window;
375420
376− window->setSurface(nullptr);
377− window->setFocused(false);
421+ if (!window->surface()) {
422+ window->setFocused(false);
423+ }
378424
379425 m_windowModel.removeAt(index);
380426
@@ -385,11 +431,9 @@ void TopLevelWindowModel::removeAt(int index)
385431 m_modelState = IdleState;
386432 }
387433
388− disconnect(window, 0, this, 0);
389434 if (m_focusedWindow == window) {
390435 setFocusedWindow(nullptr);
391436 }
392− delete window;
393437
394438 INFO_MSG << " after " << toString();
395439}
plugins/WindowManager/TopLevelWindowModel.h+6−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2016 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -20,6 +20,8 @@
2020#include <QAbstractListModel>
2121#include <QLoggingCategory>
2222
23+#include "WindowManagerGlobal.h"
24+
2325Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
2426
2527class Window;
@@ -47,7 +49,7 @@ namespace unity {
4749 * As applications can have multiple surfaces and you can also have entries without surfaces at all,
4850 * the only way to unambiguously refer to an entry in this model is through its id.
4951 */
50−class TopLevelWindowModel : public QAbstractListModel
52+class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
5153{
5254 Q_OBJECT
5355
@@ -197,6 +199,7 @@ private:
197199 void setFocusedWindow(Window *window);
198200 void removeInputMethodWindow();
199201 int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
202+ void deleteAt(int index);
200203 void removeAt(int index);
201204
202205 void addApplication(unity::shell::application::ApplicationInfoInterface *application);
@@ -207,6 +210,7 @@ private:
207210 unity::shell::application::ApplicationInfoInterface *application);
208211 void prependSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
209212 unity::shell::application::ApplicationInfoInterface *application);
213+ void prependWindow(Window *window, unity::shell::application::ApplicationInfoInterface *application);
210214
211215 void connectWindow(Window *window);
212216 void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
plugins/WindowManager/Window.cpp+6
@@ -178,6 +178,12 @@ void Window::setSurface(unityapi::MirSurfaceInterface *surface)
178178 }
179179 });
180180
181+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, &Window::liveChanged);
182+
183+ connect(surface, &QObject::destroyed, this, [this]() {
184+ setSurface(nullptr);
185+ });
186+
181187 // bring it up to speed
182188 if (m_positionRequested) {
183189 m_surface->setRequestedPosition(m_requestedPosition);
plugins/WindowManager/Window.h+4−1
@@ -24,6 +24,8 @@
2424// Unity API
2525#include <unity/shell/application/Mir.h>
2626
27+#include "WindowManagerGlobal.h"
28+
2729namespace unity {
2830 namespace shell {
2931 namespace application {
@@ -42,7 +44,7 @@ Q_DECLARE_LOGGING_CATEGORY(UNITY_WINDOW)
4244 was killed to free up memory, as it should still remain in the window list since the user
4345 did not explicitly close it).
4446 */
45−class Window : public QObject
47+class WINDOWMANAGERQML_EXPORT Window : public QObject
4648{
4749 Q_OBJECT
4850
@@ -144,6 +146,7 @@ Q_SIGNALS:
144146 void confinesMousePointerChanged(bool value);
145147 void surfaceChanged(unity::shell::application::MirSurfaceInterface *surface);
146148 void allowClientResizeChanged(bool value);
149+ void liveChanged(bool value);
147150
148151 /**
149152 * @brief Emitted when focus for this window is requested by an external party
plugins/WindowManager/WindowManagerGlobal.hnew file+23
@@ -0,0 +1,23 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QtCore/QtGlobal>
18+
19+#if defined(WINDOWMANAGERQML_LIBRARY)
20+# define WINDOWMANAGERQML_EXPORT Q_DECL_EXPORT
21+#else
22+# define WINDOWMANAGERQML_EXPORT Q_DECL_IMPORT
23+#endif
tests/mocks/Unity/Application/MirSurfaceListModel.cpp+7−7
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2016 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
1717#include "MirSurfaceListModel.h"
1818#include "ApplicationInfo.h"
1919
20−#include "MirSurface.h"
20+#include <unity/shell/application/MirSurfaceInterface.h>
2121
2222#define MIRSURFACELISTMODEL_DEBUG 0
2323
@@ -46,14 +46,14 @@ QVariant MirSurfaceListModel::data(const QModelIndex& index, int role) const
4646 return QVariant();
4747
4848 if (role == SurfaceRole) {
49− MirSurface *surface = m_surfaceList.at(index.row());
49+ MirSurfaceInterface *surface = m_surfaceList.at(index.row());
5050 return QVariant::fromValue(static_cast<unity::shell::application::MirSurfaceInterface*>(surface));
5151 } else {
5252 return QVariant();
5353 }
5454}
5555
56−void MirSurfaceListModel::raise(MirSurface *surface)
56+void MirSurfaceListModel::raise(MirSurfaceInterface *surface)
5757{
5858 DEBUG_MSG("(" << surface << ")");
5959 int i = m_surfaceList.indexOf(surface);
@@ -62,7 +62,7 @@ void MirSurfaceListModel::raise(MirSurface *surface)
6262 }
6363}
6464
65−void MirSurfaceListModel::addSurface(MirSurface *surface)
65+void MirSurfaceListModel::addSurface(MirSurfaceInterface *surface)
6666{
6767 DEBUG_MSG("(" << surface << ")");
6868 beginInsertRows(QModelIndex(), 0, 0);
@@ -73,7 +73,7 @@ void MirSurfaceListModel::addSurface(MirSurface *surface)
7373 Q_EMIT firstChanged();
7474}
7575
76−void MirSurfaceListModel::connectSurface(MirSurface *surface)
76+void MirSurfaceListModel::connectSurface(MirSurfaceInterface *surface)
7777{
7878 connect(surface, &QObject::destroyed, this, [this, surface](){ this->removeSurface(surface); });
7979 connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool surfaceFocused){
@@ -83,7 +83,7 @@ void MirSurfaceListModel::connectSurface(MirSurface *surface)
8383 });
8484}
8585
86−void MirSurfaceListModel::removeSurface(MirSurface *surface)
86+void MirSurfaceListModel::removeSurface(MirSurfaceInterface *surface)
8787{
8888 int i = m_surfaceList.indexOf(surface);
8989 if (i != -1) {
tests/mocks/Unity/Application/MirSurfaceListModel.h+7−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2016 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@
2323#include <QAbstractListModel>
2424#include <QList>
2525
26−class MirSurface;
27−
2826class MirSurfaceListModel : public unity::shell::application::MirSurfaceListInterface
2927{
3028 Q_OBJECT
@@ -38,17 +36,17 @@ public:
3836 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
3937 QVariant data(const QModelIndex& index, int role) const override;
4038
41− void addSurface(MirSurface *surface);
42− void removeSurface(MirSurface *surface);
39+ void addSurface(unity::shell::application::MirSurfaceInterface *surface);
40+ void removeSurface(unity::shell::application::MirSurfaceInterface *surface);
4341
44− bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
42+ bool contains(unity::shell::application::MirSurfaceInterface *surface) const { return m_surfaceList.contains(surface); }
4543
4644private:
47− void raise(MirSurface *surface);
45+ void raise(unity::shell::application::MirSurfaceInterface *surface);
4846 void moveSurface(int from, int to);
49− void connectSurface(MirSurface *surface);
47+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
5048
51− QList<MirSurface*> m_surfaceList;
49+ QList<unity::shell::application::MirSurfaceInterface*> m_surfaceList;
5250};
5351
5452Q_DECLARE_METATYPE(MirSurfaceListModel*)
tests/plugins/CMakeLists.txt+1
@@ -9,4 +9,5 @@ add_subdirectory(SessionBroadcast)
99add_subdirectory(Ubuntu)
1010add_subdirectory(Unity)
1111add_subdirectory(Utils)
12+add_subdirectory(WindowManager)
1213add_subdirectory(Wizard)
tests/plugins/WindowManager/CMakeLists.txtnew file+34
@@ -0,0 +1,34 @@
1+include_directories(
2+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+ ${CMAKE_SOURCE_DIR}/tests/mocks
5+ )
6+
7+add_executable(TopLevelWindowModelTestExec
8+ tst_TopLevelWindowModel.cpp
9+ UnityApplicationMocks.h
10+
11+ ${CMAKE_SOURCE_DIR}/tests/mocks/Unity/Application/MirSurfaceListModel.cpp
12+
13+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
14+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
15+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
16+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
17+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
18+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
19+ )
20+qt5_use_modules(TopLevelWindowModelTestExec Test Core Gui Qml Quick)
21+
22+target_link_libraries(TopLevelWindowModelTestExec windowmanager-qml)
23+
24+install(TARGETS TopLevelWindowModelTestExec
25+ DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/WindowManager"
26+)
27+
28+# To find libwindowmanager-qml.so
29+set_target_properties(TopLevelWindowModelTestExec PROPERTIES
30+ INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
31+
32+add_unity8_unittest(TopLevelWindowModel TopLevelWindowModelTestExec
33+ ENVIRONMENT LD_LIBRARY_PATH=${UNITY_PLUGINPATH}/WindowManager
34+)
tests/plugins/WindowManager/UnityApplicationMocks.hnew file+203
@@ -0,0 +1,203 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITYAPPLICATIONMOCKS_H
18+#define UNITYAPPLICATIONMOCKS_H
19+
20+#include <unity/shell/application/ApplicationInfoInterface.h>
21+#include <unity/shell/application/ApplicationManagerInterface.h>
22+#include <unity/shell/application/MirSurfaceInterface.h>
23+#include <unity/shell/application/SurfaceManagerInterface.h>
24+
25+// from tests/mocks
26+#include <Unity/Application/MirSurfaceListModel.h>
27+
28+using namespace unity::shell::application;
29+
30+class MirSurface : public MirSurfaceInterface
31+{
32+ Q_OBJECT
33+public:
34+ Mir::Type type() const override { return m_type; }
35+ QString name() const override { return QString("foo"); }
36+ QString persistentId() const override { return QString("a-b-c-my-id"); }
37+ QString appId() const override { return QString(); }
38+ QPoint position() const override { return QPoint(); }
39+ QSize size() const override { return QSize(); }
40+ void resize(int, int) override {}
41+ void resize(const QSize &) override {}
42+ Mir::State state() const override { return m_state; }
43+ bool live() const override { return m_live; }
44+ bool visible() const override { return true; }
45+ Mir::OrientationAngle orientationAngle() const override { return Mir::Angle0; }
46+ void setOrientationAngle(Mir::OrientationAngle) override {}
47+
48+ int minimumWidth() const override { return 0; }
49+ int minimumHeight() const override { return 0; }
50+ int maximumWidth() const override { return 0; }
51+ int maximumHeight() const override { return 0; }
52+ int widthIncrement() const override { return 0; }
53+ int heightIncrement() const override { return 0; }
54+
55+ void setKeymap(const QString &) override {}
56+ QString keymap() const override { return QString(); }
57+ Mir::ShellChrome shellChrome() const override { return Mir::NormalChrome; }
58+ bool focused() const override { return true; }
59+ QRect inputBounds() const override { return QRect(); }
60+ bool confinesMousePointer() const override { return false; }
61+ bool allowClientResize() const override { return true; }
62+ void setAllowClientResize(bool) override {}
63+ QPoint requestedPosition() const override { return QPoint(); }
64+ void setRequestedPosition(const QPoint &) override {}
65+ MirSurfaceInterface* parentSurface() const override { return nullptr; }
66+ unity::shell::application::MirSurfaceListInterface* childSurfaceList() const override { return nullptr; }
67+ void close() override {}
68+ void activate() override {}
69+
70+public Q_SLOTS:
71+ void requestState(Mir::State value) override
72+ {
73+ if (m_state != value) {
74+ m_state = value;
75+ Q_EMIT stateChanged(m_state);
76+ }
77+ }
78+
79+public:
80+ Mir::Type m_type { Mir::NormalType };
81+ Mir::State m_state { Mir::RestoredState };
82+ bool m_live { true };
83+};
84+
85+class SurfaceManager : public SurfaceManagerInterface
86+{
87+ Q_OBJECT
88+
89+public:
90+ void raise(MirSurfaceInterface *) override {}
91+ void activate(MirSurfaceInterface *) override {}
92+};
93+
94+class Application : public ApplicationInfoInterface
95+{
96+ Q_OBJECT
97+public:
98+ Application(QString appId) : ApplicationInfoInterface(appId, nullptr), m_appId(std::move(appId)) {}
99+
100+ void close() override {}
101+ QString appId() const override { return m_appId;}
102+ QString name() const override { return "foo"; }
103+ QString comment() const override { return "bar"; }
104+ QUrl icon() const override { return QUrl(); }
105+ State state() const override { return m_state; }
106+ RequestedState requestedState() const override { return m_requestedState; }
107+ void setRequestedState(RequestedState value) override
108+ {
109+ if (value != m_requestedState) {
110+ m_requestedState = value;
111+ Q_EMIT requestedStateChanged(value);
112+ }
113+ }
114+ bool focused() const override { return false; }
115+ QString splashTitle() const override { return QString(); }
116+ QUrl splashImage() const override { return QUrl(); }
117+ bool splashShowHeader() const override { return false; }
118+ QColor splashColor() const override { return QColor(); }
119+ QColor splashColorHeader() const override { return QColor(); }
120+ QColor splashColorFooter() const override { return QColor(); }
121+ Qt::ScreenOrientations supportedOrientations() const override { return Qt::LandscapeOrientation; }
122+ bool rotatesWindowContents() const override { return false; }
123+ bool isTouchApp() const override { return false; }
124+ bool exemptFromLifecycle() const override { return false; }
125+ void setExemptFromLifecycle(bool) override {}
126+ QSize initialSurfaceSize() const override { return QSize(); }
127+ void setInitialSurfaceSize(const QSize &) override {}
128+ MirSurfaceListInterface* surfaceList() const override { return &m_surfaceList; }
129+ MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
130+ int surfaceCount() const override { return 0; }
131+
132+ QString m_appId;
133+ State m_state;
134+ RequestedState m_requestedState;
135+ mutable MirSurfaceListModel m_surfaceList;
136+};
137+
138+class ApplicationManager : public ApplicationManagerInterface
139+{
140+ Q_OBJECT
141+
142+public:
143+
144+ int rowCount(const QModelIndex &) const override
145+ {
146+ return m_applications.count();
147+ }
148+
149+ QVariant data(const QModelIndex &/*index*/, int /*role*/) const override
150+ {
151+ return QVariant();
152+ }
153+
154+ QString focusedApplicationId() const override {return QString();}
155+
156+ ApplicationInfoInterface *get(int index) const override
157+ {
158+ return m_applications[index];
159+ }
160+
161+ ApplicationInfoInterface *findApplication(const QString &appId) const override
162+ {
163+ Q_UNUSED(appId);
164+ return nullptr;
165+ }
166+
167+ ApplicationInfoInterface *findApplicationWithSurface(MirSurfaceInterface* surface) const override
168+ {
169+ for (int i = 0; i < m_applications.count(); ++i) {
170+ if (m_applications[i]->m_surfaceList.contains(surface)) {
171+ return m_applications[i];
172+ }
173+ }
174+ return nullptr;
175+ }
176+
177+ bool requestFocusApplication(const QString &appId) override
178+ {
179+ Q_UNUSED(appId);
180+ return false;
181+ }
182+
183+ ApplicationInfoInterface *startApplication(const QString &appId, const QStringList &/*arguments*/) override
184+ {
185+ Application *application = new Application(appId);
186+ prepend(application);
187+ return application;
188+ }
189+
190+ bool stopApplication(const QString &) override { return true; }
191+
192+private:
193+ void prepend(Application *application)
194+ {
195+ beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
196+ m_applications.append(application);
197+ endInsertRows();
198+ }
199+
200+ QList<Application*> m_applications;
201+};
202+
203+#endif // UNITYAPPLICATIONMOCKS_H
tests/plugins/WindowManager/tst_TopLevelWindowModel.cppnew file+130
@@ -0,0 +1,130 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QtTest/QtTest>
18+
19+// WindowManager plugin
20+#include <TopLevelWindowModel.h>
21+#include <Window.h>
22+
23+#include "UnityApplicationMocks.h"
24+
25+class tst_TopLevelWindowModel : public QObject
26+{
27+ Q_OBJECT
28+
29+private Q_SLOTS:
30+ void init(); // called right before each and every test function is executed
31+ void cleanup(); // called right after each and every test function is executed
32+
33+ void singleSurfaceStartsHidden();
34+ void secondSurfaceIsHidden();
35+
36+private:
37+ ApplicationManager *applicationManager{nullptr};
38+ SurfaceManager *surfaceManager{nullptr};
39+ TopLevelWindowModel *topLevelWindowModel{nullptr};
40+};
41+
42+void tst_TopLevelWindowModel::init()
43+{
44+ applicationManager = new ApplicationManager;
45+ surfaceManager = new SurfaceManager;
46+
47+ topLevelWindowModel = new TopLevelWindowModel;
48+ topLevelWindowModel->setApplicationManager(applicationManager);
49+ topLevelWindowModel->setSurfaceManager(surfaceManager);
50+}
51+
52+void tst_TopLevelWindowModel::cleanup()
53+{
54+ delete topLevelWindowModel;
55+ topLevelWindowModel = nullptr;
56+
57+ delete surfaceManager;
58+ surfaceManager = nullptr;
59+
60+ delete applicationManager;
61+ applicationManager = nullptr;
62+}
63+
64+void tst_TopLevelWindowModel::singleSurfaceStartsHidden()
65+{
66+ QCOMPARE(topLevelWindowModel->rowCount(), 0);
67+
68+ auto application = static_cast<Application*>(applicationManager->startApplication(QString("hello-world"), QStringList()));
69+
70+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
71+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)nullptr);
72+
73+ auto surface = new MirSurface;
74+ surface->m_state = Mir::HiddenState;
75+ application->m_surfaceList.addSurface(surface);
76+ Q_EMIT surfaceManager->surfaceCreated(surface);
77+
78+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
79+ // not showing the surface as it's still hidden
80+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)nullptr);
81+
82+ surface->requestState(Mir::RestoredState);
83+
84+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
85+ // Now that the surface is no longer hidden, TopLevelWindowModel should expose it.
86+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)surface);
87+}
88+
89+void tst_TopLevelWindowModel::secondSurfaceIsHidden()
90+{
91+ QCOMPARE(topLevelWindowModel->rowCount(), 0);
92+
93+ auto application = static_cast<Application*>(applicationManager->startApplication(QString("hello-world"), QStringList()));
94+
95+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
96+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)nullptr);
97+
98+ auto firstSurface = new MirSurface;
99+ application->m_surfaceList.addSurface(firstSurface);
100+ Q_EMIT surfaceManager->surfaceCreated(firstSurface);
101+
102+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
103+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
104+
105+ auto secondSurface = new MirSurface;
106+ secondSurface->m_state = Mir::HiddenState;
107+ application->m_surfaceList.addSurface(secondSurface);
108+ Q_EMIT surfaceManager->surfaceCreated(secondSurface);
109+
110+ // still only the first surface is exposed by TopLevelWindowModel
111+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
112+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
113+
114+ secondSurface->requestState(Mir::RestoredState);
115+
116+ // now the second surface finally shows up
117+ QCOMPARE(topLevelWindowModel->rowCount(), 2);
118+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)secondSurface);
119+ QCOMPARE((void*)topLevelWindowModel->windowAt(1)->surface(), (void*)firstSurface);
120+
121+ secondSurface->requestState(Mir::HiddenState);
122+
123+ // and it's gone again
124+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
125+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
126+}
127+
128+QTEST_MAIN(tst_TopLevelWindowModel)
129+
130+#include "tst_TopLevelWindowModel.moc"