on 1f28596 help sign in
unity8/tests/mocks/Unity/Application/ApplicationManager.cpp
571 lines 20 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#include "ApplicationManager.h"
18#include "ApplicationInfo.h"
20#include <paths.h>
22#include <QDir>
23#include <QGuiApplication>
24#include <QQuickItem>
25#include <QQuickView>
26#include <QQmlComponent>
27#include <QTimer>
28#include <QDateTime>
29#include <QDebug>
31ApplicationManager::ApplicationManager(QObject *parent)
32 : ApplicationManagerInterface(parent)
33 , m_suspended(false)
34 , m_mainStageComponent(0)
35 , m_mainStage(0)
36 , m_sideStageComponent(0)
37 , m_sideStage(0)
39 buildListOfAvailableApplications();
42ApplicationManager::~ApplicationManager()
46int ApplicationManager::rowCount(const QModelIndex& parent) const {
47 return !parent.isValid() ? m_runningApplications.size() : 0;
50QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
51 if (index.row() < 0 || index.row() >= m_runningApplications.size())
52 return QVariant();
54 auto app = m_runningApplications.at(index.row());
55 switch(role) {
56 case RoleAppId:
57 return app->appId();
58 case RoleName:
59 return app->name();
60 case RoleComment:
61 return app->comment();
62 case RoleIcon:
63 return app->icon();
64 case RoleStage:
65 return app->stage();
66 case RoleState:
67 return app->state();
68 case RoleFocused:
69 return app->focused();
70 case RoleScreenshot:
71 return app->screenshot();
72 default:
73 return QVariant();
74 }
77ApplicationInfo *ApplicationManager::get(int row) const {
78 if (row < 0 || row >= m_runningApplications.size())
79 return nullptr;
80 return m_runningApplications.at(row);
83ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const {
84 for (ApplicationInfo *app : m_runningApplications) {
85 if (app->appId() == appId) {
86 return app;
87 }
88 }
89 return nullptr;
92void ApplicationManager::add(ApplicationInfo *application) {
93 if (!application) {
94 return;
95 }
96 qDebug() << "add() " << application->appId();
98 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
99 m_runningApplications.append(application);
100 endInsertRows();
101 Q_EMIT applicationAdded(application->appId());
102 Q_EMIT countChanged();
103 Q_EMIT focusRequested(application->appId());
106void ApplicationManager::remove(ApplicationInfo *application) {
107 int i = m_runningApplications.indexOf(application);
108 if (i != -1) {
109 beginRemoveRows(QModelIndex(), i, i);
110 m_runningApplications.removeAt(i);
111 endRemoveRows();
112 Q_EMIT applicationRemoved(application->appId());
113 Q_EMIT countChanged();
114 }
117void ApplicationManager::move(int from, int to) {
118 if (from == to) return;
120 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
121 QModelIndex parent;
122 /* When moving an item down, the destination index needs to be incremented
123 * by one, as explained in the documentation:
124 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
125 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
126 m_runningApplications.move(from, to);
127 endMoveRows();
128 }
131int ApplicationManager::keyboardHeight() const
133 return 0;
136bool ApplicationManager::keyboardVisible() const
138 return false;
141int ApplicationManager::sideStageWidth() const
143 return 0;
146ApplicationManager::StageHint ApplicationManager::stageHint() const
148 return MainStage;
151ApplicationManager::FormFactorHint ApplicationManager::formFactorHint() const
153 return PhoneFormFactor;
156ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
157 const QStringList &arguments)
159 return startApplication(appId, NoFlag, arguments);
162ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
163 ExecFlags flags,
164 const QStringList &arguments)
166 Q_UNUSED(arguments)
167 ApplicationInfo *application = 0;
169 for (ApplicationInfo *availableApp : m_availableApplications) {
170 if (availableApp->appId() == appId) {
171 application = availableApp;
172 break;
173 }
174 }
176 if (!application)
177 return 0;
179 if (flags.testFlag(ApplicationManager::ForceMainStage)
180 && application->stage() == ApplicationInfo::SideStage) {
181 application->setStage(ApplicationInfo::MainStage);
182 }
183 add(application);
185 QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
187 return application;
190bool ApplicationManager::stopApplication(const QString &appId)
192 ApplicationInfo *application = findApplication(appId);
193 if (application == nullptr)
194 return false;
196 if (application->appId() == focusedApplicationId()) {
197 unfocusCurrentApplication();
198 }
199 remove(application);
200 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
201 return true;
204void ApplicationManager::updateScreenshot(const QString &appId)
206 qDebug() << "updating screenshot for" << appId;
207 int idx = -1;
208 ApplicationInfo *application = nullptr;
209 for (int i = 0; i < m_availableApplications.count(); ++i) {
210 application = m_availableApplications.at(i);
211 if (application->appId() == appId) {
212 idx = i;
213 break;
214 }
215 }
217 if (idx == -1) {
218 return;
219 }
221 application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
222 QModelIndex appIndex = index(idx);
223 qDebug() << "emitting dataChanged for RoleScreenshot on" << appId;
224 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
227QString ApplicationManager::focusedApplicationId() const {
228 for (ApplicationInfo *app : m_runningApplications) {
229 if (app->focused()) {
230 return app->appId();
231 }
232 }
233 return QString();
236bool ApplicationManager::suspended() const
238 return m_suspended;
241void ApplicationManager::setSuspended(bool suspended)
243 ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
244 if (focusedApp) {
245 if (suspended) {
246 focusedApp->setState(ApplicationInfo::Suspended);
247 } else {
248 focusedApp->setState(ApplicationInfo::Running);
249 }
250 }
251 m_suspended = suspended;
252 Q_EMIT suspendedChanged();
255bool ApplicationManager::focusApplication(const QString &appId)
257 ApplicationInfo *application = findApplication(appId);
258 if (application == nullptr)
259 return false;
261 if (application->stage() == ApplicationInfo::MainStage) {
262 // unfocus currently focused mainstage app
263 for (ApplicationInfo *app : m_runningApplications) {
264 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
265 app->setFocused(false);
266 app->hideWindow();
267 }
268 }
270 // focus this app
271 application->setFocused(true);
272 if (!m_mainStage)
273 createMainStage();
274 application->showWindow(m_mainStage);
275 m_mainStage->setZ(-1000);
276 if (m_sideStage)
277 m_sideStage->setZ(-2000);
278 } else if (application->stage() == ApplicationInfo::SideStage) {
279 // unfocus currently focused sidestage app
280 for (ApplicationInfo *app : m_runningApplications) {
281 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
282 app->setFocused(false);
283 app->hideWindow();
284 }
285 }
287 // focus this app
288 application->setFocused(true);
289 if (!m_sideStage)
290 createSideStage();
291 application->showWindow(m_sideStage);
292 m_sideStage->setZ(-1000);
293 if (m_mainStage)
294 m_mainStage->setZ(-2000);
295 }
297 // move app to top of stack
298 move(m_runningApplications.indexOf(application), 0);
299 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
300 Q_EMIT focusedApplicationIdChanged();
301 return true;
304void ApplicationManager::activateApplication(const QString &appId)
306 if (appId != focusedApplicationId()) {
307 QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
308 }
311void ApplicationManager::unfocusCurrentApplication()
313 for (ApplicationInfo *app : m_runningApplications) {
314 if (app->focused()) {
315 app->hideWindow();
316 app->setFocused(false);
317 }
318 }
319 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
320 Q_EMIT focusedApplicationIdChanged();
323void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
325 // TODO: Is there a better way of solving this fullscreen vs. regular
326 // application height?
327 QString topMargin;
328 if (application->fullscreen()) {
329 topMargin.append("0");
330 } else {
331 // Taken from Panel.panelHeight
332 topMargin.append("units.gu(3) + units.dp(2)");
333 }
335 QString windowQml = QString(
336 "import QtQuick 2.0\n"
337 "Image {\n"
338 " anchors.fill: parent\n"
339 " anchors.topMargin: %1\n"
340 " source: \"file://%2/Dash/graphics/phone/screenshots/%3.png\"\n"
341 " smooth: true\n"
342 " fillMode: Image.PreserveAspectCrop\n"
343 "}").arg(topMargin)
344 .arg(qmlDirectory())
345 .arg(application->icon().toString());
346 application->setWindowQml(windowQml);
348 QString imageQml = QString(
349 "import QtQuick 2.0\n"
350 "Image {\n"
351 " anchors.fill: parent\n"
352 " source: \"file://%1/Dash/graphics/phone/screenshots/%2.png\"\n"
353 " smooth: true\n"
354 " fillMode: Image.PreserveAspectCrop\n"
355 "}").arg(qmlDirectory())
356 .arg(application->icon().toString());
357 application->setImageQml(imageQml);
360void ApplicationManager::buildListOfAvailableApplications()
362 ApplicationInfo *application;
364 application = new ApplicationInfo(this);
365 application->setAppId("phone-app");
366 application->setName("Phone");
367 application->setIcon(QUrl("phone"));
368 application->setStage(ApplicationInfo::SideStage);
369 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
370 generateQmlStrings(application);
371 m_availableApplications.append(application);
373 application = new ApplicationInfo(this);
374 application->setAppId("camera-app");
375 application->setName("Camera");
376 application->setIcon(QUrl("camera"));
377 application->setFullscreen(true);
378 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
379 generateQmlStrings(application);
380 m_availableApplications.append(application);
382 application = new ApplicationInfo(this);
383 application->setAppId("gallery-app");
384 application->setName("Gallery");
385 application->setIcon(QUrl("gallery"));
386 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
387 generateQmlStrings(application);
388 m_availableApplications.append(application);
390 application = new ApplicationInfo(this);
391 application->setAppId("facebook-webapp");
392 application->setName("Facebook");
393 application->setIcon(QUrl("facebook"));
394 application->setStage(ApplicationInfo::SideStage);
395 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
396 generateQmlStrings(application);
397 m_availableApplications.append(application);
399 application = new ApplicationInfo(this);
400 application->setAppId("webbrowser-app");
401 application->setFullscreen(true);
402 application->setName("Browser");
403 application->setIcon(QUrl("browser"));
404 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
405 generateQmlStrings(application);
406 m_availableApplications.append(application);
408 application = new ApplicationInfo(this);
409 application->setAppId("twitter-webapp");
410 application->setName("Twitter");
411 application->setIcon(QUrl("twitter"));
412 application->setStage(ApplicationInfo::SideStage);
413 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
414 generateQmlStrings(application);
415 m_availableApplications.append(application);
417 application = new ApplicationInfo(this);
418 application->setAppId("gmail-webapp");
419 application->setName("GMail");
420 application->setIcon(QUrl("gmail"));
421 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
422 m_availableApplications.append(application);
424 application = new ApplicationInfo(this);
425 application->setAppId("ubuntu-weather-app");
426 application->setName("Weather");
427 application->setIcon(QUrl("weather"));
428 application->setStage(ApplicationInfo::SideStage);
429 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
430 generateQmlStrings(application);
431 m_availableApplications.append(application);
433 application = new ApplicationInfo(this);
434 application->setAppId("notes-app");
435 application->setName("Notepad");
436 application->setIcon(QUrl("notepad"));
437 application->setStage(ApplicationInfo::SideStage);
438 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
439 m_availableApplications.append(application);
441 application = new ApplicationInfo(this);
442 application->setAppId("calendar-app");
443 application->setName("Calendar");
444 application->setIcon(QUrl("calendar"));
445 application->setStage(ApplicationInfo::SideStage);
446 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
447 m_availableApplications.append(application);
449 application = new ApplicationInfo(this);
450 application->setAppId("mediaplayer-app");
451 application->setName("Media Player");
452 application->setIcon(QUrl("mediaplayer-app"));
453 application->setFullscreen(true);
454 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
455 m_availableApplications.append(application);
457 application = new ApplicationInfo(this);
458 application->setAppId("evernote");
459 application->setName("Evernote");
460 application->setIcon(QUrl("evernote"));
461 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
462 m_availableApplications.append(application);
464 application = new ApplicationInfo(this);
465 application->setAppId("map");
466 application->setName("Map");
467 application->setIcon(QUrl("map"));
468 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
469 generateQmlStrings(application);
470 m_availableApplications.append(application);
472 application = new ApplicationInfo(this);
473 application->setAppId("pinterest");
474 application->setName("Pinterest");
475 application->setIcon(QUrl("pinterest"));
476 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
477 m_availableApplications.append(application);
479 application = new ApplicationInfo(this);
480 application->setAppId("soundcloud");
481 application->setName("SoundCloud");
482 application->setIcon(QUrl("soundcloud"));
483 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
484 m_availableApplications.append(application);
486 application = new ApplicationInfo(this);
487 application->setAppId("wikipedia");
488 application->setName("Wikipedia");
489 application->setIcon(QUrl("wikipedia"));
490 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
491 m_availableApplications.append(application);
493 application = new ApplicationInfo(this);
494 application->setAppId("youtube");
495 application->setName("YouTube");
496 application->setIcon(QUrl("youtube"));
497 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
498 m_availableApplications.append(application);
501void ApplicationManager::createMainStageComponent()
503 // The assumptions I make here really should hold.
504 QQuickView *quickView =
505 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
507 QQmlEngine *engine = quickView->engine();
509 m_mainStageComponent = new QQmlComponent(engine, this);
510 QString mainStageQml =
511 "import QtQuick 2.0\n"
512 "Rectangle {\n"
513 " anchors.fill: parent\n"
514 " color: 'black'\n"
515 " z: -2000\n"
516 "}\n";
517 m_mainStageComponent->setData(mainStageQml.toLatin1(), QUrl());
520void ApplicationManager::createMainStage()
522 if (!m_mainStageComponent)
523 createMainStageComponent();
525 // The assumptions I make here really should hold.
526 QQuickView *quickView =
527 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
529 QQuickItem *shell = quickView->rootObject();
531 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
532 m_mainStage->setParentItem(shell);
535void ApplicationManager::createSideStageComponent()
537 // The assumptions I make here really should hold.
538 QQuickView *quickView =
539 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
541 QQmlEngine *engine = quickView->engine();
543 m_sideStageComponent = new QQmlComponent(engine, this);
544 QString sideStageQml =
545 "import QtQuick 2.0\n"
546 "import Ubuntu.Components 0.1\n"
547 "Item {\n"
548 " width: units.gu(40)\n" // from SideStage in Shell.qml
549 " anchors.top: parent.top\n"
550 " anchors.bottom: parent.bottom\n"
551 " anchors.right: parent.right\n"
552 " z: -1000\n"
553 "}\n";
554 m_sideStageComponent->setData(sideStageQml.toLatin1(), QUrl());
557void ApplicationManager::createSideStage()
559 if (!m_sideStageComponent)
560 createSideStageComponent();
562 // The assumptions I make here really should hold.
563 QQuickView *quickView =
564 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
566 QQuickItem *shell = quickView->rootObject();
568 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
569 m_sideStage->setParentItem(shell);
570 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);