on c4e54da help sign in
unity8/tests/mocks/Unity/Application/ApplicationManager.cpp
603 lines 19 kB history raw
1/*
2 * Copyright (C) 2013-2014 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"
19#include "MirSurfaceItem.h"
21#include <paths.h>
23#include <QDir>
24#include <QGuiApplication>
25#include <QQuickItem>
26#include <QQuickView>
27#include <QQmlComponent>
28#include <QTimer>
29#include <QDateTime>
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)
38 , m_rightMargin(0)
40 m_roleNames.insert(RoleSurface, "surface");
41 m_roleNames.insert(RoleFullscreen, "fullscreen");
43 buildListOfAvailableApplications();
46ApplicationManager::~ApplicationManager()
50int ApplicationManager::rowCount(const QModelIndex& parent) const {
51 return !parent.isValid() ? m_runningApplications.size() : 0;
54QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
55 if (index.row() < 0 || index.row() >= m_runningApplications.size())
56 return QVariant();
58 auto app = m_runningApplications.at(index.row());
59 switch(role) {
60 case RoleAppId:
61 return app->appId();
62 case RoleName:
63 return app->name();
64 case RoleComment:
65 return app->comment();
66 case RoleIcon:
67 return app->icon();
68 case RoleStage:
69 return app->stage();
70 case RoleState:
71 return app->state();
72 case RoleFocused:
73 return app->focused();
74 case RoleScreenshot:
75 return app->screenshot();
76 case RoleSurface:
77 return QVariant::fromValue(app->surface());
78 case RoleFullscreen:
79 return app->fullscreen();
80 default:
81 return QVariant();
82 }
85ApplicationInfo *ApplicationManager::get(int row) const {
86 if (row < 0 || row >= m_runningApplications.size())
87 return nullptr;
88 return m_runningApplications.at(row);
91ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const {
92 for (ApplicationInfo *app : m_runningApplications) {
93 if (app->appId() == appId) {
94 return app;
95 }
96 }
97 return nullptr;
100QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
102 for (int i = 0; i < m_runningApplications.size(); ++i) {
103 if (m_runningApplications.at(i) == application) {
104 return index(i);
105 }
106 }
108 return QModelIndex();
111void ApplicationManager::add(ApplicationInfo *application) {
112 if (!application) {
113 return;
114 }
116 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
117 m_runningApplications.append(application);
118 endInsertRows();
119 Q_EMIT applicationAdded(application->appId());
120 Q_EMIT countChanged();
121 Q_EMIT focusRequested(application->appId());
123 connect(application, &ApplicationInfo::surfaceChanged, this, [application, this]() {
124 QModelIndex appIndex = findIndex(application);
125 if (!appIndex.isValid()) return;
126 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
127 });
130void ApplicationManager::remove(ApplicationInfo *application) {
131 int i = m_runningApplications.indexOf(application);
132 if (i != -1) {
133 beginRemoveRows(QModelIndex(), i, i);
134 m_runningApplications.removeAt(i);
135 endRemoveRows();
136 Q_EMIT applicationRemoved(application->appId());
137 Q_EMIT countChanged();
138 }
139 disconnect(application, &ApplicationInfo::surfaceChanged, this, 0);
142void ApplicationManager::move(int from, int to) {
143 if (from == to) return;
145 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
146 QModelIndex parent;
147 /* When moving an item down, the destination index needs to be incremented
148 * by one, as explained in the documentation:
149 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
150 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
151 m_runningApplications.move(from, to);
152 endMoveRows();
153 }
156int ApplicationManager::keyboardHeight() const
158 return 0;
161bool ApplicationManager::keyboardVisible() const
163 return false;
166int ApplicationManager::sideStageWidth() const
168 return 0;
171ApplicationManager::StageHint ApplicationManager::stageHint() const
173 return MainStage;
176ApplicationManager::FormFactorHint ApplicationManager::formFactorHint() const
178 return PhoneFormFactor;
181ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
182 const QStringList &arguments)
184 return startApplication(appId, NoFlag, arguments);
187ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
188 ExecFlags flags,
189 const QStringList &arguments)
191 Q_UNUSED(arguments)
192 ApplicationInfo *application = 0;
194 for (ApplicationInfo *availableApp : m_availableApplications) {
195 if (availableApp->appId() == appId) {
196 application = availableApp;
197 break;
198 }
199 }
201 if (!application)
202 return 0;
204 if (flags.testFlag(ApplicationManager::ForceMainStage)
205 && application->stage() == ApplicationInfo::SideStage) {
206 application->setStage(ApplicationInfo::MainStage);
207 }
208 add(application);
209 application->setState(ApplicationInfo::Running);
211 return application;
214bool ApplicationManager::stopApplication(const QString &appId)
216 ApplicationInfo *application = findApplication(appId);
217 if (application == nullptr)
218 return false;
220 if (application->appId() == focusedApplicationId()) {
221 unfocusCurrentApplication();
222 }
223 application->setState(ApplicationInfo::Stopped);
224 remove(application);
225 return true;
228bool ApplicationManager::updateScreenshot(const QString &appId)
230 int idx = -1;
231 ApplicationInfo *application = nullptr;
232 for (int i = 0; i < m_availableApplications.count(); ++i) {
233 application = m_availableApplications.at(i);
234 if (application->appId() == appId) {
235 idx = i;
236 break;
237 }
238 }
240 if (idx == -1) {
241 return false;
242 }
244 QModelIndex appIndex = index(idx);
245 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
246 return true;
249QString ApplicationManager::focusedApplicationId() const {
250 for (ApplicationInfo *app : m_runningApplications) {
251 if (app->focused()) {
252 return app->appId();
253 }
254 }
255 return QString();
258bool ApplicationManager::suspended() const
260 return m_suspended;
263void ApplicationManager::setSuspended(bool suspended)
265 ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
266 if (focusedApp) {
267 if (suspended) {
268 focusedApp->setState(ApplicationInfo::Suspended);
269 } else {
270 focusedApp->setState(ApplicationInfo::Running);
271 }
272 }
273 m_suspended = suspended;
274 Q_EMIT suspendedChanged();
277bool ApplicationManager::focusApplication(const QString &appId)
279 ApplicationInfo *application = findApplication(appId);
280 if (application == nullptr)
281 return false;
283 if (application->stage() == ApplicationInfo::MainStage) {
284 // unfocus currently focused mainstage app
285 for (ApplicationInfo *app : m_runningApplications) {
286 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
287 app->setFocused(false);
288 app->hideWindow();
289 }
290 }
292 // focus this app
293 application->setFocused(true);
294 if (!m_mainStage)
295 createMainStage();
296 application->showWindow(m_mainStage);
297 m_mainStage->setZ(-1000);
298 if (m_sideStage)
299 m_sideStage->setZ(-2000);
300 } else if (application->stage() == ApplicationInfo::SideStage) {
301 // unfocus currently focused sidestage app
302 for (ApplicationInfo *app : m_runningApplications) {
303 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
304 app->setFocused(false);
305 app->hideWindow();
306 }
307 }
309 // focus this app
310 application->setFocused(true);
311 if (!m_sideStage)
312 createSideStage();
313 application->showWindow(m_sideStage);
314 m_sideStage->setZ(-1000);
315 if (m_mainStage)
316 m_mainStage->setZ(-2000);
317 }
319 // move app to top of stack
320 move(m_runningApplications.indexOf(application), 0);
321 Q_EMIT focusedApplicationIdChanged();
322 return true;
325bool ApplicationManager::requestFocusApplication(const QString &appId)
327 if (appId != focusedApplicationId()) {
328 QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
329 return true;
330 }
331 return false;
334void ApplicationManager::unfocusCurrentApplication()
336 for (ApplicationInfo *app : m_runningApplications) {
337 if (app->focused()) {
338 app->hideWindow();
339 app->setFocused(false);
340 }
341 }
342 Q_EMIT focusedApplicationIdChanged();
345void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
347 // TODO: Is there a better way of solving this fullscreen vs. regular
348 // application height?
349 QString topMargin;
350 if (application->fullscreen()) {
351 topMargin.append("0");
352 } else {
353 // Taken from Panel.panelHeight
354 topMargin.append("units.gu(3) + units.dp(2)");
355 }
357 QString windowQml = QString(
358 "import QtQuick 2.0\n"
359 "Image {\n"
360 " anchors.fill: parent\n"
361 " anchors.topMargin: %1\n"
362 " anchors.rightMargin: %2\n"
363 " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
364 " smooth: true\n"
365 " fillMode: Image.PreserveAspectCrop\n"
366 "}").arg(topMargin)
367 .arg(m_rightMargin)
368 .arg(qmlDirectory())
369 .arg(application->icon().toString());
370 application->setWindowQml(windowQml);
372 QString imageQml = QString(
373 "import QtQuick 2.0\n"
374 "Image {\n"
375 " anchors.fill: parent\n"
376 " source: \"file://%1/Dash/graphics/phone/screenshots/%2.png\"\n"
377 " smooth: true\n"
378 " fillMode: Image.PreserveAspectCrop\n"
379 "}").arg(qmlDirectory())
380 .arg(application->icon().toString());
381 application->setImageQml(imageQml);
383 application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
384 .arg(application->icon().toString()));
387void ApplicationManager::buildListOfAvailableApplications()
389 ApplicationInfo *application;
391 application = new ApplicationInfo(this);
392 application->setAppId("dialer-app");
393 application->setName("Dialer");
394 application->setIcon(QUrl("dialer"));
395 application->setStage(ApplicationInfo::SideStage);
396 generateQmlStrings(application);
397 m_availableApplications.append(application);
399 application = new ApplicationInfo(this);
400 application->setAppId("camera-app");
401 application->setName("Camera");
402 application->setIcon(QUrl("camera"));
403 application->setFullscreen(true);
404 generateQmlStrings(application);
405 m_availableApplications.append(application);
407 application = new ApplicationInfo(this);
408 application->setAppId("gallery-app");
409 application->setName("Gallery");
410 application->setIcon(QUrl("gallery"));
411 generateQmlStrings(application);
412 m_availableApplications.append(application);
414 application = new ApplicationInfo(this);
415 application->setAppId("facebook-webapp");
416 application->setName("Facebook");
417 application->setIcon(QUrl("facebook"));
418 application->setStage(ApplicationInfo::SideStage);
419 generateQmlStrings(application);
420 m_availableApplications.append(application);
422 application = new ApplicationInfo(this);
423 application->setAppId("webbrowser-app");
424 application->setFullscreen(true);
425 application->setName("Browser");
426 application->setIcon(QUrl("browser"));
427 generateQmlStrings(application);
428 m_availableApplications.append(application);
430 application = new ApplicationInfo(this);
431 application->setAppId("twitter-webapp");
432 application->setName("Twitter");
433 application->setIcon(QUrl("twitter"));
434 application->setStage(ApplicationInfo::SideStage);
435 generateQmlStrings(application);
436 m_availableApplications.append(application);
438 application = new ApplicationInfo(this);
439 application->setAppId("gmail-webapp");
440 application->setName("GMail");
441 application->setIcon(QUrl("gmail"));
442 m_availableApplications.append(application);
444 application = new ApplicationInfo(this);
445 application->setAppId("ubuntu-weather-app");
446 application->setName("Weather");
447 application->setIcon(QUrl("weather"));
448 application->setStage(ApplicationInfo::SideStage);
449 generateQmlStrings(application);
450 m_availableApplications.append(application);
452 application = new ApplicationInfo(this);
453 application->setAppId("notes-app");
454 application->setName("Notepad");
455 application->setIcon(QUrl("notepad"));
456 application->setStage(ApplicationInfo::SideStage);
457 m_availableApplications.append(application);
459 application = new ApplicationInfo(this);
460 application->setAppId("calendar-app");
461 application->setName("Calendar");
462 application->setIcon(QUrl("calendar"));
463 application->setStage(ApplicationInfo::SideStage);
464 m_availableApplications.append(application);
466 application = new ApplicationInfo(this);
467 application->setAppId("mediaplayer-app");
468 application->setName("Media Player");
469 application->setIcon(QUrl("mediaplayer-app"));
470 application->setFullscreen(true);
471 m_availableApplications.append(application);
473 application = new ApplicationInfo(this);
474 application->setAppId("evernote");
475 application->setName("Evernote");
476 application->setIcon(QUrl("evernote"));
477 m_availableApplications.append(application);
479 application = new ApplicationInfo(this);
480 application->setAppId("map");
481 application->setName("Map");
482 application->setIcon(QUrl("map"));
483 generateQmlStrings(application);
484 m_availableApplications.append(application);
486 application = new ApplicationInfo(this);
487 application->setAppId("pinterest");
488 application->setName("Pinterest");
489 application->setIcon(QUrl("pinterest"));
490 m_availableApplications.append(application);
492 application = new ApplicationInfo(this);
493 application->setAppId("soundcloud");
494 application->setName("SoundCloud");
495 application->setIcon(QUrl("soundcloud"));
496 m_availableApplications.append(application);
498 application = new ApplicationInfo(this);
499 application->setAppId("wikipedia");
500 application->setName("Wikipedia");
501 application->setIcon(QUrl("wikipedia"));
502 m_availableApplications.append(application);
504 application = new ApplicationInfo(this);
505 application->setAppId("youtube");
506 application->setName("YouTube");
507 application->setIcon(QUrl("youtube"));
508 m_availableApplications.append(application);
511void ApplicationManager::createMainStageComponent()
513 // The assumptions I make here really should hold.
514 QQuickView *quickView =
515 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
517 QQmlEngine *engine = quickView->engine();
519 m_mainStageComponent = new QQmlComponent(engine, this);
520 QString mainStageQml =
521 "import QtQuick 2.0\n"
522 "Rectangle {\n"
523 " anchors.fill: parent\n"
524 " color: 'black'\n"
525 " z: -2000\n"
526 "}\n";
527 m_mainStageComponent->setData(mainStageQml.toLatin1(), QUrl());
530void ApplicationManager::createMainStage()
532 if (!m_mainStageComponent)
533 createMainStageComponent();
535 // The assumptions I make here really should hold.
536 QQuickView *quickView =
537 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
539 QQuickItem *shell = quickView->rootObject();
541 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
542 m_mainStage->setParentItem(shell);
545void ApplicationManager::createSideStageComponent()
547 // The assumptions I make here really should hold.
548 QQuickView *quickView =
549 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
551 QQmlEngine *engine = quickView->engine();
553 m_sideStageComponent = new QQmlComponent(engine, this);
554 QString sideStageQml =
555 "import QtQuick 2.0\n"
556 "import Ubuntu.Components 0.1\n"
557 "Item {\n"
558 " width: units.gu(40)\n" // from SideStage in Shell.qml
559 " anchors.top: parent.top\n"
560 " anchors.bottom: parent.bottom\n"
561 " anchors.right: parent.right\n"
562 " z: -1000\n"
563 "}\n";
564 m_sideStageComponent->setData(sideStageQml.toLatin1(), QUrl());
567void ApplicationManager::createSideStage()
569 if (!m_sideStageComponent)
570 createSideStageComponent();
572 // The assumptions I make here really should hold.
573 QQuickView *quickView =
574 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
576 QQuickItem *shell = quickView->rootObject();
578 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
579 m_sideStage->setParentItem(shell);
580 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
583QStringList ApplicationManager::availableApplications()
585 QStringList appIds;
586 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
587 appIds << app->appId();
588 }
589 return appIds;
592int ApplicationManager::rightMargin() const
594 return m_rightMargin;
597void ApplicationManager::setRightMargin(int rightMargin)
599 m_rightMargin = rightMargin;
600 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
601 generateQmlStrings(app);
602 }