on 3856e15 help sign in
unity8/tests/mocks/Unity/Application/ApplicationManager.cpp
599 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)
38 , m_rightMargin(0)
40 buildListOfAvailableApplications();
43ApplicationManager::~ApplicationManager()
47int ApplicationManager::rowCount(const QModelIndex& parent) const {
48 return !parent.isValid() ? m_runningApplications.size() : 0;
51QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
52 if (index.row() < 0 || index.row() >= m_runningApplications.size())
53 return QVariant();
55 auto app = m_runningApplications.at(index.row());
56 switch(role) {
57 case RoleAppId:
58 return app->appId();
59 case RoleName:
60 return app->name();
61 case RoleComment:
62 return app->comment();
63 case RoleIcon:
64 return app->icon();
65 case RoleStage:
66 return app->stage();
67 case RoleState:
68 return app->state();
69 case RoleFocused:
70 return app->focused();
71 case RoleScreenshot:
72 return app->screenshot();
73 default:
74 return QVariant();
75 }
78ApplicationInfo *ApplicationManager::get(int row) const {
79 if (row < 0 || row >= m_runningApplications.size())
80 return nullptr;
81 return m_runningApplications.at(row);
84ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const {
85 for (ApplicationInfo *app : m_runningApplications) {
86 if (app->appId() == appId) {
87 return app;
88 }
89 }
90 return nullptr;
93void ApplicationManager::add(ApplicationInfo *application) {
94 if (!application) {
95 return;
96 }
97 qDebug() << "add() " << application->appId();
99 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
100 m_runningApplications.append(application);
101 endInsertRows();
102 Q_EMIT applicationAdded(application->appId());
103 Q_EMIT countChanged();
104 Q_EMIT focusRequested(application->appId());
107void ApplicationManager::remove(ApplicationInfo *application) {
108 int i = m_runningApplications.indexOf(application);
109 if (i != -1) {
110 beginRemoveRows(QModelIndex(), i, i);
111 m_runningApplications.removeAt(i);
112 endRemoveRows();
113 Q_EMIT applicationRemoved(application->appId());
114 Q_EMIT countChanged();
115 }
118void ApplicationManager::move(int from, int to) {
119 if (from == to) return;
121 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
122 QModelIndex parent;
123 /* When moving an item down, the destination index needs to be incremented
124 * by one, as explained in the documentation:
125 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
126 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
127 m_runningApplications.move(from, to);
128 endMoveRows();
129 }
132int ApplicationManager::keyboardHeight() const
134 return 0;
137bool ApplicationManager::keyboardVisible() const
139 return false;
142int ApplicationManager::sideStageWidth() const
144 return 0;
147ApplicationManager::StageHint ApplicationManager::stageHint() const
149 return MainStage;
152ApplicationManager::FormFactorHint ApplicationManager::formFactorHint() const
154 return PhoneFormFactor;
157ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
158 const QStringList &arguments)
160 return startApplication(appId, NoFlag, arguments);
163ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
164 ExecFlags flags,
165 const QStringList &arguments)
167 Q_UNUSED(arguments)
168 ApplicationInfo *application = 0;
170 for (ApplicationInfo *availableApp : m_availableApplications) {
171 if (availableApp->appId() == appId) {
172 application = availableApp;
173 break;
174 }
175 }
177 if (!application)
178 return 0;
180 if (flags.testFlag(ApplicationManager::ForceMainStage)
181 && application->stage() == ApplicationInfo::SideStage) {
182 application->setStage(ApplicationInfo::MainStage);
183 }
184 add(application);
186 QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
188 return application;
191bool ApplicationManager::stopApplication(const QString &appId)
193 ApplicationInfo *application = findApplication(appId);
194 if (application == nullptr)
195 return false;
197 if (application->appId() == focusedApplicationId()) {
198 unfocusCurrentApplication();
199 }
200 remove(application);
201 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
202 return true;
205bool ApplicationManager::updateScreenshot(const QString &appId)
207 qDebug() << "updating screenshot for" << appId;
208 int idx = -1;
209 ApplicationInfo *application = nullptr;
210 for (int i = 0; i < m_availableApplications.count(); ++i) {
211 application = m_availableApplications.at(i);
212 if (application->appId() == appId) {
213 idx = i;
214 break;
215 }
216 }
218 if (idx == -1) {
219 return false;
220 }
222 application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
223 QModelIndex appIndex = index(idx);
224 qDebug() << "emitting dataChanged for RoleScreenshot on" << appId;
225 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
226 return true;
229QString ApplicationManager::focusedApplicationId() const {
230 for (ApplicationInfo *app : m_runningApplications) {
231 if (app->focused()) {
232 return app->appId();
233 }
234 }
235 return QString();
238bool ApplicationManager::suspended() const
240 return m_suspended;
243void ApplicationManager::setSuspended(bool suspended)
245 ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
246 if (focusedApp) {
247 if (suspended) {
248 focusedApp->setState(ApplicationInfo::Suspended);
249 } else {
250 focusedApp->setState(ApplicationInfo::Running);
251 }
252 }
253 m_suspended = suspended;
254 Q_EMIT suspendedChanged();
257bool ApplicationManager::focusApplication(const QString &appId)
259 ApplicationInfo *application = findApplication(appId);
260 if (application == nullptr)
261 return false;
263 if (application->stage() == ApplicationInfo::MainStage) {
264 // unfocus currently focused mainstage app
265 for (ApplicationInfo *app : m_runningApplications) {
266 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
267 app->setFocused(false);
268 app->hideWindow();
269 }
270 }
272 // focus this app
273 application->setFocused(true);
274 if (!m_mainStage)
275 createMainStage();
276 application->showWindow(m_mainStage);
277 m_mainStage->setZ(-1000);
278 if (m_sideStage)
279 m_sideStage->setZ(-2000);
280 } else if (application->stage() == ApplicationInfo::SideStage) {
281 // unfocus currently focused sidestage app
282 for (ApplicationInfo *app : m_runningApplications) {
283 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
284 app->setFocused(false);
285 app->hideWindow();
286 }
287 }
289 // focus this app
290 application->setFocused(true);
291 if (!m_sideStage)
292 createSideStage();
293 application->showWindow(m_sideStage);
294 m_sideStage->setZ(-1000);
295 if (m_mainStage)
296 m_mainStage->setZ(-2000);
297 }
299 // move app to top of stack
300 move(m_runningApplications.indexOf(application), 0);
301 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
302 Q_EMIT focusedApplicationIdChanged();
303 return true;
306bool ApplicationManager::requestFocusApplication(const QString &appId)
308 if (appId != focusedApplicationId()) {
309 QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
310 return true;
311 }
312 return false;
315void ApplicationManager::unfocusCurrentApplication()
317 for (ApplicationInfo *app : m_runningApplications) {
318 if (app->focused()) {
319 app->hideWindow();
320 app->setFocused(false);
321 }
322 }
323 qDebug() << Q_FUNC_INFO << "emitting focusedAppChanged" << focusedApplicationId();
324 Q_EMIT focusedApplicationIdChanged();
327void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
329 // TODO: Is there a better way of solving this fullscreen vs. regular
330 // application height?
331 QString topMargin;
332 if (application->fullscreen()) {
333 topMargin.append("0");
334 } else {
335 // Taken from Panel.panelHeight
336 topMargin.append("units.gu(3) + units.dp(2)");
337 }
339 QString windowQml = QString(
340 "import QtQuick 2.0\n"
341 "Image {\n"
342 " anchors.fill: parent\n"
343 " anchors.topMargin: %1\n"
344 " anchors.rightMargin: %2\n"
345 " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
346 " smooth: true\n"
347 " fillMode: Image.PreserveAspectCrop\n"
348 "}").arg(topMargin)
349 .arg(m_rightMargin)
350 .arg(qmlDirectory())
351 .arg(application->icon().toString());
352 application->setWindowQml(windowQml);
354 QString imageQml = QString(
355 "import QtQuick 2.0\n"
356 "Image {\n"
357 " anchors.fill: parent\n"
358 " source: \"file://%1/Dash/graphics/phone/screenshots/%2.png\"\n"
359 " smooth: true\n"
360 " fillMode: Image.PreserveAspectCrop\n"
361 "}").arg(qmlDirectory())
362 .arg(application->icon().toString());
363 application->setImageQml(imageQml);
366void ApplicationManager::buildListOfAvailableApplications()
368 ApplicationInfo *application;
370 application = new ApplicationInfo(this);
371 application->setAppId("phone-app");
372 application->setName("Phone");
373 application->setIcon(QUrl("phone"));
374 application->setStage(ApplicationInfo::SideStage);
375 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
376 generateQmlStrings(application);
377 m_availableApplications.append(application);
379 application = new ApplicationInfo(this);
380 application->setAppId("camera-app");
381 application->setName("Camera");
382 application->setIcon(QUrl("camera"));
383 application->setFullscreen(true);
384 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
385 generateQmlStrings(application);
386 m_availableApplications.append(application);
388 application = new ApplicationInfo(this);
389 application->setAppId("gallery-app");
390 application->setName("Gallery");
391 application->setIcon(QUrl("gallery"));
392 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
393 generateQmlStrings(application);
394 m_availableApplications.append(application);
396 application = new ApplicationInfo(this);
397 application->setAppId("facebook-webapp");
398 application->setName("Facebook");
399 application->setIcon(QUrl("facebook"));
400 application->setStage(ApplicationInfo::SideStage);
401 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
402 generateQmlStrings(application);
403 m_availableApplications.append(application);
405 application = new ApplicationInfo(this);
406 application->setAppId("webbrowser-app");
407 application->setFullscreen(true);
408 application->setName("Browser");
409 application->setIcon(QUrl("browser"));
410 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
411 generateQmlStrings(application);
412 m_availableApplications.append(application);
414 application = new ApplicationInfo(this);
415 application->setAppId("twitter-webapp");
416 application->setName("Twitter");
417 application->setIcon(QUrl("twitter"));
418 application->setStage(ApplicationInfo::SideStage);
419 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
420 generateQmlStrings(application);
421 m_availableApplications.append(application);
423 application = new ApplicationInfo(this);
424 application->setAppId("gmail-webapp");
425 application->setName("GMail");
426 application->setIcon(QUrl("gmail"));
427 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
428 m_availableApplications.append(application);
430 application = new ApplicationInfo(this);
431 application->setAppId("ubuntu-weather-app");
432 application->setName("Weather");
433 application->setIcon(QUrl("weather"));
434 application->setStage(ApplicationInfo::SideStage);
435 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
436 generateQmlStrings(application);
437 m_availableApplications.append(application);
439 application = new ApplicationInfo(this);
440 application->setAppId("notes-app");
441 application->setName("Notepad");
442 application->setIcon(QUrl("notepad"));
443 application->setStage(ApplicationInfo::SideStage);
444 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
445 m_availableApplications.append(application);
447 application = new ApplicationInfo(this);
448 application->setAppId("calendar-app");
449 application->setName("Calendar");
450 application->setIcon(QUrl("calendar"));
451 application->setStage(ApplicationInfo::SideStage);
452 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
453 m_availableApplications.append(application);
455 application = new ApplicationInfo(this);
456 application->setAppId("mediaplayer-app");
457 application->setName("Media Player");
458 application->setIcon(QUrl("mediaplayer-app"));
459 application->setFullscreen(true);
460 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
461 m_availableApplications.append(application);
463 application = new ApplicationInfo(this);
464 application->setAppId("evernote");
465 application->setName("Evernote");
466 application->setIcon(QUrl("evernote"));
467 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
468 m_availableApplications.append(application);
470 application = new ApplicationInfo(this);
471 application->setAppId("map");
472 application->setName("Map");
473 application->setIcon(QUrl("map"));
474 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
475 generateQmlStrings(application);
476 m_availableApplications.append(application);
478 application = new ApplicationInfo(this);
479 application->setAppId("pinterest");
480 application->setName("Pinterest");
481 application->setIcon(QUrl("pinterest"));
482 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
483 m_availableApplications.append(application);
485 application = new ApplicationInfo(this);
486 application->setAppId("soundcloud");
487 application->setName("SoundCloud");
488 application->setIcon(QUrl("soundcloud"));
489 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
490 m_availableApplications.append(application);
492 application = new ApplicationInfo(this);
493 application->setAppId("wikipedia");
494 application->setName("Wikipedia");
495 application->setIcon(QUrl("wikipedia"));
496 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
497 m_availableApplications.append(application);
499 application = new ApplicationInfo(this);
500 application->setAppId("youtube");
501 application->setName("YouTube");
502 application->setIcon(QUrl("youtube"));
503 application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
504 m_availableApplications.append(application);
507void ApplicationManager::createMainStageComponent()
509 // The assumptions I make here really should hold.
510 QQuickView *quickView =
511 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
513 QQmlEngine *engine = quickView->engine();
515 m_mainStageComponent = new QQmlComponent(engine, this);
516 QString mainStageQml =
517 "import QtQuick 2.0\n"
518 "Rectangle {\n"
519 " anchors.fill: parent\n"
520 " color: 'black'\n"
521 " z: -2000\n"
522 "}\n";
523 m_mainStageComponent->setData(mainStageQml.toLatin1(), QUrl());
526void ApplicationManager::createMainStage()
528 if (!m_mainStageComponent)
529 createMainStageComponent();
531 // The assumptions I make here really should hold.
532 QQuickView *quickView =
533 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
535 QQuickItem *shell = quickView->rootObject();
537 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
538 m_mainStage->setParentItem(shell);
541void ApplicationManager::createSideStageComponent()
543 // The assumptions I make here really should hold.
544 QQuickView *quickView =
545 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
547 QQmlEngine *engine = quickView->engine();
549 m_sideStageComponent = new QQmlComponent(engine, this);
550 QString sideStageQml =
551 "import QtQuick 2.0\n"
552 "import Ubuntu.Components 0.1\n"
553 "Item {\n"
554 " width: units.gu(40)\n" // from SideStage in Shell.qml
555 " anchors.top: parent.top\n"
556 " anchors.bottom: parent.bottom\n"
557 " anchors.right: parent.right\n"
558 " z: -1000\n"
559 "}\n";
560 m_sideStageComponent->setData(sideStageQml.toLatin1(), QUrl());
563void ApplicationManager::createSideStage()
565 if (!m_sideStageComponent)
566 createSideStageComponent();
568 // The assumptions I make here really should hold.
569 QQuickView *quickView =
570 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
572 QQuickItem *shell = quickView->rootObject();
574 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
575 m_sideStage->setParentItem(shell);
576 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
579QStringList ApplicationManager::availableApplications()
581 QStringList appIds;
582 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
583 appIds << app->appId();
584 }
585 return appIds;
588int ApplicationManager::rightMargin() const
590 return m_rightMargin;
593void ApplicationManager::setRightMargin(int rightMargin)
595 m_rightMargin = rightMargin;
596 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
597 generateQmlStrings(app);
598 }