aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel d'Andrada <daniel.dandrada@luxoft.com>2017-10-26 16:53:00 +0200
committerDaniel d'Andrada <daniel.dandrada@luxoft.com>2018-02-28 15:58:02 +0100
commitade3b97523d28db476a56a86a861ea14080749b7 (patch)
treee4163f10c8ecbec50547468eafeebcf4797ae8d2
parent15846e28541a7849d3603a76cd1d8e17bab3541c (diff)
Proper model implementation and dependency injection instead of singletons
- Have a proper implementation of the applications model used in triton - Also use a dependency injection approach, where components get the needed models as properties instead of importing singletons themselves. That makes testing easier (no need to come up with fake implementations of qml modules exporting fake singletons) as you can just directly assign a mock to the model property and also makes the dependencies absolutely clear in the code (you can see the models being passed down to the children that use them).
-rw-r--r--imports/system/models/application/ApplicationManagerModel.qml160
-rw-r--r--imports/system/models/application/qmldir2
-rw-r--r--src/ApplicationInfo.cpp84
-rw-r--r--src/ApplicationInfo.h (renamed from imports/system/models/application/ApplicationInfo.qml)74
-rw-r--r--src/ApplicationModel.cpp150
-rw-r--r--src/ApplicationModel.h58
-rw-r--r--src/WidgetListModel.cpp172
-rw-r--r--src/WidgetListModel.h59
-rw-r--r--src/main.cpp8
-rw-r--r--src/src.pro3
-rwxr-xr-xsysui/display/Display.qml12
-rw-r--r--sysui/display/HomePage.qml8
-rw-r--r--sysui/display/HomeWidgetsList.qml14
-rw-r--r--sysui/display/WindowStack.qml17
-rwxr-xr-xsysui/launcher/EditableGridView.qml9
-rwxr-xr-xsysui/launcher/Launcher.qml13
-rw-r--r--tests/qmltests/tst_HomePage.qml7
17 files changed, 627 insertions, 223 deletions
diff --git a/imports/system/models/application/ApplicationManagerModel.qml b/imports/system/models/application/ApplicationManagerModel.qml
deleted file mode 100644
index a87b8bb9..00000000
--- a/imports/system/models/application/ApplicationManagerModel.qml
+++ /dev/null
@@ -1,160 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 Pelagicore AG
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Triton IVI UI.
-**
-** $QT_BEGIN_LICENSE:GPL-QTAS$
-** Commercial License Usage
-** Licensees holding valid commercial Qt Automotive Suite licenses may use
-** this file in accordance with the commercial license agreement provided
-** with the Software or, alternatively, in accordance with the terms
-** contained in a written agreement between you and The Qt Company. For
-** licensing terms and conditions see https://www.qt.io/terms-conditions.
-** For further information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 or (at your option) any later version
-** approved by the KDE Free Qt Foundation. The licenses are as published by
-** the Free Software Foundation and appearing in the file LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-** SPDX-License-Identifier: GPL-3.0
-**
-****************************************************************************/
-
-pragma Singleton
-import QtQuick 2.8
-import QtApplicationManager 1.0
-import utils 1.0
-
-QtObject {
- id: root
-
- property string activeAppId
- onActiveAppIdChanged: console.log(logCategory, "activeAppId = " + activeAppId)
-
- signal applicationSurfaceReady(ApplicationInfo appInfo, Item item)
-
- property var appInfoMap: new Object
-
- property var loggingCategory: LoggingCategory {
- id: logCategory
- name: "triton.applicationmanagermodel"
- }
-
- property var _appInfoComponent: Component {
- id: appInfoComponent
- ApplicationInfo {}
- }
-
- function application(appId) {
- if (!appInfoMap[appId]) {
- var appInfo = appInfoComponent.createObject();
- appInfo.application = ApplicationManager.application(appId);
- appInfoMap[appId] = appInfo;
- return appInfo;
- } else {
- return appInfoMap[appId];
- }
- }
-
- function appIdFromWindow(item) {
- return WindowManager.get(WindowManager.indexOfWindow(item)).applicationId
- }
-
- function goHome() {
- var appInfo = appInfoMap[root.activeAppId]
- if (appInfo) {
- appInfo.active = false;
- }
- root.activeAppId = ""
- }
-
- Component.onCompleted: {
- loadAppInfoMap();
-
- ApplicationManager.applicationWasActivated.connect(applicationActivatedHandler)
- WindowManager.windowReady.connect(windowReadyHandler)
-
- // TODO do something about it
- //WindowManager.windowClosing.connect(windowClosingHandler)
-
- WindowManager.windowLost.connect(windowLostHandler)
- WindowManager.windowPropertyChanged.connect(windowPropertyChangedHandler)
- }
-
- function loadAppInfoMap() {
- // TODO Get it from some file or database
- var appInfo = application("com.pelagicore.calendar");
- appInfo.asWidget = true;
- appInfo.heightRows = 2;
-
- appInfo = application("com.pelagicore.maps");
- appInfo.asWidget = true;
- appInfo.heightRows = 2;
-
- appInfo = application("com.pelagicore.music");
- appInfo.asWidget = true;
- }
-
- function applicationActivatedHandler(appId, appAliasId) {
- console.log(logCategory, "applicationActivatedHandler: appId:" + appId + ", appAliasId:" + appAliasId)
-
- if (appId === root.activeAppId)
- return;
-
- var appInfo = application(appId)
- if (!appInfo.canBeActive)
- return;
-
- appInfo.active = true;
-
- {
- var oldAppInfo = appInfoMap[root.activeAppId]
- if (oldAppInfo)
- oldAppInfo.active = false;
- }
-
- root.activeAppId = appId
-
- for (var i = 0; i < WindowManager.count; i++) {
- if (!WindowManager.get(i).isClosing && WindowManager.get(i).applicationId === appId) {
- var item = WindowManager.get(i).windowItem
- root.applicationSurfaceReady(appInfo, item)
- break
- }
- }
- }
-
- function windowReadyHandler(index, item) {
- console.log(logCategory, "windowReadyHandler: index:" + index + ", item:" + item)
-
- var appID = WindowManager.get(index).applicationId;
-
- var appInfo = application(appID);
- appInfo.window = item;
-
- root.applicationSurfaceReady(appInfo, item)
- }
-
- function windowLostHandler(index, item) {
- // TODO care about animating before releasing
- WindowManager.releaseWindow(item)
- }
-
-
- function windowPropertyChangedHandler(window, name, value) {
- if (name === "activationCount") {
- var appId = WindowManager.get(WindowManager.indexOfWindow(window)).applicationId;
- ApplicationManager.application(appId).activated();
- root.applicationActivatedHandler(appId, appId);
- }
- }
-}
diff --git a/imports/system/models/application/qmldir b/imports/system/models/application/qmldir
deleted file mode 100644
index d568a333..00000000
--- a/imports/system/models/application/qmldir
+++ /dev/null
@@ -1,2 +0,0 @@
-singleton ApplicationManagerModel 1.0 ApplicationManagerModel.qml
-ApplicationInfo 1.0 ApplicationInfo.qml
diff --git a/src/ApplicationInfo.cpp b/src/ApplicationInfo.cpp
new file mode 100644
index 00000000..af439dbe
--- /dev/null
+++ b/src/ApplicationInfo.cpp
@@ -0,0 +1,84 @@
+#include "ApplicationInfo.h"
+
+// QtAM
+#include <applicationmanager.h>
+
+ApplicationInfo::ApplicationInfo(const QtAM::Application* application, QObject *parent)
+ : QObject(parent), m_application(application)
+{
+}
+
+void ApplicationInfo::start()
+{
+ QtAM::ApplicationManager::instance()->startApplication(m_application->id());
+}
+
+void ApplicationInfo::setAsWidget(bool value)
+{
+ if (value != m_asWidget) {
+ m_asWidget = value;
+ emit asWidgetChanged();
+ }
+}
+
+bool ApplicationInfo::asWidget() const
+{
+ return m_asWidget;
+}
+
+void ApplicationInfo::setWindow(QQuickItem * window)
+{
+ if (window != m_window) {
+ m_window = window;
+ emit windowChanged();
+ }
+}
+
+QQuickItem *ApplicationInfo::window() const
+{
+ return m_window;
+}
+
+const QtAM::Application *ApplicationInfo::application() const
+{
+ return m_application;
+}
+
+int ApplicationInfo::heightRows() const
+{
+ return m_heightRows;
+}
+
+void ApplicationInfo::setHeightRows(int value)
+{
+ if (value != m_heightRows) {
+ m_heightRows = value;
+ emit heightRowsChanged();
+ }
+}
+
+bool ApplicationInfo::active() const
+{
+ return m_active;
+}
+
+void ApplicationInfo::setActive(bool value)
+{
+ if (m_active != value) {
+ m_active = value;
+ emit activeChanged();
+ }
+}
+
+bool ApplicationInfo::canBeActive() const
+{
+ return m_canBeActive;
+}
+
+void ApplicationInfo::setCanBeActive(bool value)
+{
+ if (value != m_canBeActive) {
+ m_canBeActive = value;
+ emit canBeActiveChanged();
+ }
+}
diff --git a/imports/system/models/application/ApplicationInfo.qml b/src/ApplicationInfo.h
index 4a81c0a9..611db2d9 100644
--- a/imports/system/models/application/ApplicationInfo.qml
+++ b/src/ApplicationInfo.h
@@ -28,9 +28,13 @@
** SPDX-License-Identifier: GPL-3.0
**
****************************************************************************/
+#pragma once
-import QtQuick 2.7
-import QtApplicationManager 1.0
+#include <QObject>
+#include <QQuickItem>
+
+// QtAM
+#include <application.h>
/*
A wrapper for AppMan Application that adds some more goodies
@@ -38,30 +42,68 @@ import QtApplicationManager 1.0
Maybe some of those extra bits could be put into Application itself to reduce or even
eliminate the need for this wrapper.
*/
-QtObject {
- // the AppMan Application object
- property var application
+class ApplicationInfo : public QObject {
+ Q_OBJECT
// whether the application is active (on foreground / fullscreen)
// false means it's either invisible, minimized, reduced to a widget geometry
// or might not even be running at all
- property bool active: false
+ Q_PROPERTY(bool active READ active NOTIFY activeChanged)
// If false, Application.activated signals won't cause the application to be the active one
- property bool canBeActive: true
+ // TODO: try to get rid of this (ie, find a better solution)
+ Q_PROPERTY(bool canBeActive READ canBeActive WRITE setCanBeActive NOTIFY canBeActiveChanged)
// the main window of this application, if any
- // TODO: try to get rid of this (ie, find a better solution)
- property var window
+ Q_PROPERTY(QQuickItem* window READ window() NOTIFY windowChanged)
// Whether the application window should be shown as a widget
- property bool asWidget: false
+ Q_PROPERTY(bool asWidget READ asWidget WRITE setAsWidget NOTIFY asWidgetChanged)
// Widget geometry. Ignored if asWidget === false
- property int heightRows: 1
- property int minHeightRows: 1
+ Q_PROPERTY(int heightRows READ heightRows WRITE setHeightRows NOTIFY heightRowsChanged)
+ Q_PROPERTY(int minHeightRows MEMBER m_minHeightRows NOTIFY minHeightRowsChanged)
+
+ // FIXME: make it constant
+ Q_PROPERTY(const QtAM::Application* application READ application CONSTANT)
+public:
+ ApplicationInfo(const QtAM::Application* application, QObject *parent = nullptr);
+
+ // starts the application. Same as ApplicatioManager.startApplication() but in a object oriented fashion
+ Q_INVOKABLE void start();
+
+ void setAsWidget(bool);
+ bool asWidget() const;
+
+ void setWindow(QQuickItem *);
+ QQuickItem *window() const;
+
+ const QtAM::Application *application() const;
+
+ int heightRows() const;
+ void setHeightRows(int);
+
+ bool active() const;
+ void setActive(bool);
+
+ bool canBeActive() const;
+ void setCanBeActive(bool);
+
+signals:
+ void activeChanged();
+ void canBeActiveChanged();
+ void windowChanged();
+ void asWidgetChanged();
+ void heightRowsChanged();
+ void minHeightRowsChanged();
+private:
+ bool m_active{false};
+ bool m_canBeActive{true};
+ QQuickItem *m_window{nullptr};
+ bool m_asWidget{false};
+ int m_heightRows{1};
+ int m_minHeightRows{1};
+ const QtAM::Application *m_application{nullptr};
+};
- function start() {
- ApplicationManager.startApplication(application.id);
- }
-}
+Q_DECLARE_METATYPE(ApplicationInfo*)
diff --git a/src/ApplicationModel.cpp b/src/ApplicationModel.cpp
new file mode 100644
index 00000000..46501219
--- /dev/null
+++ b/src/ApplicationModel.cpp
@@ -0,0 +1,150 @@
+#include "ApplicationModel.h"
+#include "ApplicationInfo.h"
+
+// QtAM
+#include <application.h>
+#include <applicationmanager.h>
+#include <QtAppManWindow>
+
+#include <QStringList>
+
+#include <QDebug>
+
+using QtAM::WindowManager;
+using QtAM::ApplicationManager;
+
+ApplicationModel::ApplicationModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ auto appMan = ApplicationManager::instance();
+ connect(appMan, &ApplicationManager::applicationWasActivated, this, &ApplicationModel::onApplicationActivated);
+
+
+ for (int i = 0; i < appMan->count(); ++i) {
+ const QtAM::Application *application = appMan->application(i);
+ ApplicationInfo *appInfo = new ApplicationInfo(application);
+ m_appInfoList.append(appInfo);
+ }
+
+ // TODO: Load the widget configuration from some database or file
+ configureApps();
+
+ // TODO: Monitor appMan for Application additions and removals.
+
+ auto windowManager = WindowManager::instance();
+ connect(windowManager, &WindowManager::windowReady, this, &ApplicationModel::onWindowReady);
+
+ connect(windowManager, &WindowManager::windowLost, this, [windowManager](int index, QQuickItem *window) {
+ Q_UNUSED(index)
+ // TODO care about animating before releasing
+ windowManager->releaseWindow(window);
+ });
+}
+
+ApplicationModel::~ApplicationModel()
+{
+ qDeleteAll(m_appInfoList);
+}
+
+void ApplicationModel::configureApps()
+{
+ auto *appInfo = application("com.pelagicore.calendar");
+ appInfo->setAsWidget(true);
+ appInfo->setHeightRows(2);
+
+ appInfo = application("com.pelagicore.maps");
+ appInfo->setAsWidget(true);
+ appInfo->setHeightRows(2);
+
+ appInfo = application("com.pelagicore.music");
+ appInfo->setAsWidget(true);
+}
+
+int ApplicationModel::rowCount(const QModelIndex &) const
+{
+ return m_appInfoList.count();
+}
+
+QVariant ApplicationModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() >= 0 && index.row() < m_appInfoList.count()) {
+ ApplicationInfo *appInfo = m_appInfoList.at(index.row());
+ if (role == RoleAppInfo) {
+ return QVariant::fromValue(appInfo);
+ } else if (role == RoleIcon) {
+ return QVariant::fromValue(appInfo->application()->icon());
+ } else if (role == RoleAppId) {
+ return QVariant::fromValue(appInfo->application()->id());
+ } else if (role == RoleName) {
+ // FIXME: use locale
+ return QVariant::fromValue(appInfo->application()->name(QStringLiteral("en")));
+ }
+ }
+ return QVariant();
+}
+
+void ApplicationModel::onWindowReady(int index, QQuickItem *window)
+{
+ auto windowManager = WindowManager::instance();
+
+ QString appID = windowManager->get(index)["applicationId"].toString();
+
+ ApplicationInfo *appInfo = application(appID);
+ appInfo->setWindow(window);
+
+ emit applicationSurfaceReady(appInfo, window);
+}
+
+ApplicationInfo *ApplicationModel::application(const QString &appId)
+{
+ for (auto *appInfo : m_appInfoList) {
+ if (appInfo->application()->id() == appId) {
+ return appInfo;
+ }
+ }
+ return nullptr;
+}
+
+void ApplicationModel::goHome()
+{
+ if (!m_activeAppId.isEmpty()) {
+ auto *appInfo = application(m_activeAppId);
+ Q_ASSERT(appInfo);
+
+ appInfo->setActive(false);
+
+ m_activeAppId.clear();
+ emit activeAppIdChanged();
+ }
+}
+
+void ApplicationModel::onApplicationActivated(const QString &appId, const QString &/*aliasId*/)
+{
+ if (appId == m_activeAppId)
+ return;
+
+ auto *appInfo = application(appId);
+ if (!appInfo->canBeActive())
+ return;
+
+ appInfo->setActive(true);
+
+ {
+ auto *oldAppInfo = application(m_activeAppId);
+ if (oldAppInfo)
+ oldAppInfo->setActive(false);
+ }
+
+ m_activeAppId = appId;
+ emit activeAppIdChanged();
+
+ auto windowManager = WindowManager::instance();
+ for (int i = 0; i < windowManager->count(); ++i) {
+ auto wmItem = windowManager->get(i);
+ if (!wmItem["isClosing"].toBool() && wmItem["applicationId"].toString() == appId) {
+ auto *item = wmItem["windowItem"].value<QQuickItem*>();
+ emit applicationSurfaceReady(appInfo, item);
+ break;
+ }
+ }
+}
diff --git a/src/ApplicationModel.h b/src/ApplicationModel.h
new file mode 100644
index 00000000..63ed5eb6
--- /dev/null
+++ b/src/ApplicationModel.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <QList>
+
+class QQuickItem;
+
+class ApplicationInfo;
+
+class ApplicationModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(QString activeAppId READ activeAppId NOTIFY activeAppIdChanged)
+public:
+ ApplicationModel(QObject *parent = nullptr);
+ virtual ~ApplicationModel();
+
+ enum Roles {
+ RoleAppInfo = Qt::UserRole,
+ RoleIcon = Qt::UserRole + 1,
+ RoleAppId = Qt::UserRole + 2,
+ RoleName = Qt::UserRole + 3,
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ QHash<int, QByteArray> roleNames() const override
+ {
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(RoleAppInfo, "appInfo");
+ roleNames.insert(RoleIcon, "icon");
+ roleNames.insert(RoleAppId, "applicationId");
+ roleNames.insert(RoleName, "name");
+ return roleNames;
+ }
+
+ Q_INVOKABLE ApplicationInfo *application(const QString &appId);
+
+ Q_INVOKABLE void goHome();
+
+ QString activeAppId() const { return m_activeAppId; }
+
+signals:
+ void applicationSurfaceReady(ApplicationInfo *appInfo, QQuickItem *item);
+ void activeAppIdChanged();
+
+private slots:
+ void onWindowReady(int index, QQuickItem *window);
+ void onApplicationActivated(const QString &appId, const QString &aliasId);
+
+private:
+ void configureApps();
+
+ QList<ApplicationInfo*> m_appInfoList;
+ QString m_activeAppId;
+};
+
diff --git a/src/WidgetListModel.cpp b/src/WidgetListModel.cpp
new file mode 100644
index 00000000..b486166a
--- /dev/null
+++ b/src/WidgetListModel.cpp
@@ -0,0 +1,172 @@
+#include "WidgetListModel.h"
+
+#include <QDebug>
+
+int WidgetListModel::rowCount(const QModelIndex &/*parent*/) const
+{
+ return m_list.count();
+}
+
+QVariant WidgetListModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() >= 0 && index.row() < m_list.count()) {
+ if (role == RoleAppInfo) {
+ ApplicationInfo *appInfo = m_list.at(index.row());
+ return QVariant::fromValue(appInfo);
+ }
+ }
+ return QVariant();
+}
+
+QAbstractItemModel *WidgetListModel::applicationModel() const
+{
+ return m_applicationModel;
+}
+
+void WidgetListModel::setApplicationModel(QAbstractItemModel *appModel)
+{
+ if (appModel == m_applicationModel) {
+ return;
+ }
+
+ disconnect(appModel, 0, this, 0);
+ m_list.clear();
+
+ m_applicationModel = appModel;
+ emit applicationModelChanged();
+
+ if (!appModel) {
+ return;
+ }
+
+ fetchAppInfoRoleIndex();
+
+ if (appModel->rowCount() > 0) {
+
+ trackRowsFromApplicationModel(0, appModel->rowCount() - 1);
+ }
+
+ connect(appModel, &QAbstractItemModel::rowsInserted, this,
+ [this](const QModelIndex & /*parent*/, int first, int last)
+ {
+ this->trackRowsFromApplicationModel(first, last);
+ });
+
+ connect(appModel, &QAbstractItemModel::rowsAboutToBeRemoved, this,
+ [this](const QModelIndex & /*parent*/, int first, int last)
+ {
+ for (int i = first; i <= last; ++i) {
+ auto *appInfo = getApplicationInfoFromModelAt(i);
+ removeApplicationInfo(appInfo);
+ disconnect(appInfo, 0, this, 0);
+ }
+ });
+
+ connect(appModel, &QObject::destroyed, this,
+ [this]()
+ {
+ this->setApplicationModel(nullptr);
+ });
+}
+
+void WidgetListModel::fetchAppInfoRoleIndex()
+{
+ QHash<int, QByteArray> hash = m_applicationModel->roleNames();
+
+ m_appInfoRoleIndex = -1;
+ for (auto i = hash.begin(); i != hash.end() && m_appInfoRoleIndex == -1; ++i) {
+ if (i.value() == QByteArray("appInfo"))
+ m_appInfoRoleIndex = i.key();
+ }
+
+ Q_ASSERT(m_appInfoRoleIndex != -1);
+}
+
+ApplicationInfo *WidgetListModel::application(int rowIndex)
+{
+ QVariant variant = data(index(rowIndex, 0), m_appInfoRoleIndex);
+ return variant.value<ApplicationInfo*>();
+}
+
+void WidgetListModel::move(int from, int to)
+{
+ qDebug().nospace() << "WidgetListModel::move(from="<<from<<", to="<<to<<")";
+
+ if (from == to) return;
+
+ if (from >= 0 && from < m_list.size() && to >= 0 && to < m_list.size()) {
+ QModelIndex parent;
+ /* When moving an item down, the destination index needs to be incremented
+ by one, as explained in the documentation:
+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
+
+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
+ m_list.move(from, to);
+ endMoveRows();
+ }
+}
+
+void WidgetListModel::trackRowsFromApplicationModel(int first, int last)
+{
+ QList<ApplicationInfo*> newRows;
+
+ for (int i = first; i <= last; ++i) {
+ auto *appInfo = getApplicationInfoFromModelAt(i);
+ if (appInfo->asWidget()) {
+ newRows.append(appInfo);
+ }
+ }
+
+ if (newRows.isEmpty()) {
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), rowCount()/*first*/, rowCount() + newRows.count() - 1/*last*/);
+ m_list.append(newRows);
+ for (auto *appInfo : newRows) {
+ connect(appInfo, &ApplicationInfo::asWidgetChanged, this,
+ [this, appInfo]()
+ {
+ if (appInfo->asWidget()) {
+ appendApplicationInfo(appInfo);
+ } else {
+ removeApplicationInfo(appInfo);
+ }
+ });
+ }
+ endInsertRows();
+ emit countChanged();
+}
+
+void WidgetListModel::appendApplicationInfo(ApplicationInfo *appInfo)
+{
+ beginInsertRows(QModelIndex(), rowCount()/*first*/, rowCount()/*last*/);
+ m_list.append(appInfo);
+ endInsertRows();
+}
+
+void WidgetListModel::removeApplicationInfo(ApplicationInfo *appInfo)
+{
+ int index = m_list.indexOf(appInfo);
+ Q_ASSERT(index != -1);
+ if (index == -1) {
+ return;
+ }
+
+ beginRemoveRows(QModelIndex(), index, index);
+ m_list.removeAt(index);
+ endRemoveRows();
+}
+
+ApplicationInfo *WidgetListModel::getApplicationInfoFromModelAt(int index)
+{
+ auto rowIndex = m_applicationModel->index(index, 0, QModelIndex());
+ QVariant variant = m_applicationModel->data(rowIndex, m_appInfoRoleIndex);
+ auto appInfo = variant.value<ApplicationInfo*>();
+
+ if (!appInfo) {
+ qFatal("WidgetListModel: Invalid source model");
+ }
+
+ return appInfo;
+}
diff --git a/src/WidgetListModel.h b/src/WidgetListModel.h
new file mode 100644
index 00000000..9e93e248
--- /dev/null
+++ b/src/WidgetListModel.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "ApplicationInfo.h"
+
+/*
+ Filters ApplicationModel to only show the applications that have asWidget==true and also
+ keeps its own order of applications, independently of ApplicationModel
+
+ Similar to QSortFilterProxyModel but instead of sorting it keeps track of its own ordering,
+ modified via its public move() function.
+ */
+class WidgetListModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel* applicationModel READ applicationModel WRITE setApplicationModel NOTIFY applicationModelChanged)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+public:
+ enum Roles {
+ RoleAppInfo = Qt::UserRole,
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override
+ {
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(RoleAppInfo, "appInfo");
+ return roleNames;
+ }
+
+ QAbstractItemModel *applicationModel() const;
+ void setApplicationModel(QAbstractItemModel *);
+
+ Q_INVOKABLE ApplicationInfo *application(int index);
+
+ Q_INVOKABLE void move(int fromIndex, int toIndex);
+
+ int count() const { return rowCount(); }
+
+
+signals:
+ void applicationModelChanged();
+ void countChanged();
+
+private:
+ void fetchAppInfoRoleIndex();
+
+ void trackRowsFromApplicationModel(int first, int last);
+ void appendApplicationInfo(ApplicationInfo *appInfo);
+ void removeApplicationInfo(ApplicationInfo *appInfo);
+
+ ApplicationInfo *getApplicationInfoFromModelAt(int index);
+
+ int m_appInfoRoleIndex{-1};
+ QAbstractItemModel *m_applicationModel{nullptr};
+ QList<ApplicationInfo*> m_list;
+};
diff --git a/src/main.cpp b/src/main.cpp
index 9fbd3ec8..73d97308 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -44,6 +44,9 @@
# include "MouseTouchAdaptor.h"
#endif
+#include "ApplicationInfo.h"
+#include "ApplicationModel.h"
+#include "WidgetListModel.h"
QT_USE_NAMESPACE_AM
@@ -96,6 +99,11 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QIcon::setThemeSearchPaths(searchPaths);
}
+ qmlRegisterUncreatableType<ApplicationInfo>("models.application", 1, 0, "ApplicationInfo", "You canot create ApplicationInfo instances from QML");
+ qRegisterMetaType<ApplicationInfo*>();
+ qmlRegisterType<ApplicationModel>("models.application", 1, 0, "ApplicationModel");
+ qmlRegisterType<WidgetListModel>("models.application", 1, 0, "WidgetListModel");
+
DefaultConfiguration cfg(QStringList(qSL("am-config.yaml")), QString());
cfg.parse();
a.setup(&cfg);
diff --git a/src/src.pro b/src/src.pro
index cc349372..e262889b 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -10,7 +10,8 @@ QT *= appman_main-private testlib gui-private
DEFINES *= TRITON_VERSION=\\\"$$VERSION\\\"
-SOURCES = main.cpp
+SOURCES = main.cpp ApplicationInfo.cpp ApplicationModel.cpp WidgetListModel.cpp
+HEADERS = ApplicationInfo.h ApplicationModel.h WidgetListModel.h
unix:!macos:system($$pkgConfigExecutable() --libs x11 xi xcb) {
PKGCONFIG *= xcb x11 xi
diff --git a/sysui/display/Display.qml b/sysui/display/Display.qml
index 435ff01e..66f49efa 100755
--- a/sysui/display/Display.qml
+++ b/sysui/display/Display.qml
@@ -49,6 +49,14 @@ Control {
source: Style.gfx2(Style.displayBackground)
}
+ ApplicationModel {
+ id: applicationModel
+ }
+ WidgetListModel {
+ id: widgetListModel
+ applicationModel: applicationModel
+ }
+
// Content Elements
StageLoader {
@@ -72,6 +80,7 @@ Control {
anchors.horizontalCenter: parent.horizontalCenter
active: StagedStartupModel.loadDisplay
source: "../launcher/Launcher.qml"
+ Binding { target: launcherLoader.item; property: "applicationModel"; value: applicationModel }
}
Item {
@@ -95,6 +104,9 @@ Control {
anchors.fill: parent
active: StagedStartupModel.loadRest
source: "WindowStack.qml"
+ Binding { target: windowStackLoader.item; property: "applicationModel"; value: applicationModel }
+ Binding { target: windowStackLoader.item; property: "widgetListModel"; value: widgetListModel }
+
}
WidgetDrawer {
diff --git a/sysui/display/HomePage.qml b/sysui/display/HomePage.qml
index 20365fd1..8bea7ce1 100644
--- a/sysui/display/HomePage.qml
+++ b/sysui/display/HomePage.qml
@@ -39,7 +39,7 @@ import animations 1.0
Item {
id: root
- property var widgetsList: HomeWidgetsList {}
+ property var widgetsList
property Item bottomApplicationWidget
readonly property real widgetWidth: widgetGrid.width
@@ -94,7 +94,7 @@ Item {
var i = 0;
var accumulatedHeight = 0;
while (true) {
- var appInfo = root.widgetsList.get(i).appInfo;
+ var appInfo = root.widgetsList.application(i);
accumulatedHeight += appInfo.heightRows * rowHeight;
if (accumulatedHeight >= pos.y) {
widgetIndexAboveHandle = i;
@@ -191,7 +191,7 @@ Item {
var remainingDelta = delta;
var usableDeltaAbove = 0
while (remainingDelta !== 0 && i >= 0) {
- var appInfo = root.widgetsList.get(i).appInfo;
+ var appInfo = root.widgetsList.application(i);
var minHeight = appInfo.minHeightRows * rowHeight;
var targetHeight = (appInfo.heightRows * rowHeight) + remainingDelta;
@@ -217,7 +217,7 @@ Item {
remainingDelta = usableDeltaAbove;
var usableDelta = 0;
while (remainingDelta !== 0 && i < root.widgetsList.count) {
- var appInfo = root.widgetsList.get(i).appInfo;
+ var appInfo = root.widgetsList.application(i);
var minHeight = appInfo.minHeightRows * rowHeight;
var targetHeight = (appInfo.heightRows * rowHeight) - remainingDelta;
diff --git a/sysui/display/HomeWidgetsList.qml b/sysui/display/HomeWidgetsList.qml
deleted file mode 100644
index ea532f4d..00000000
--- a/sysui/display/HomeWidgetsList.qml
+++ /dev/null
@@ -1,14 +0,0 @@
-import QtQuick 2.6
-
-import models.application 1.0
-
-// TODO: Make this just a filter of all AppInfo objects from ApplicationManagerModel,
-// filtering on asWidget === true
-ListModel {
- id: root
- Component.onCompleted: {
- root.append({"appInfo":ApplicationManagerModel.application("com.pelagicore.maps")});
- root.append({"appInfo":ApplicationManagerModel.application("com.pelagicore.calendar")});
- root.append({"appInfo":ApplicationManagerModel.application("com.pelagicore.music")});
- }
-}
diff --git a/sysui/display/WindowStack.qml b/sysui/display/WindowStack.qml
index abf9f83c..123014dc 100644
--- a/sysui/display/WindowStack.qml
+++ b/sysui/display/WindowStack.qml
@@ -35,18 +35,18 @@ import QtGraphicalEffects 1.0
import QtQuick.Controls 2.1
import controls 1.0
import utils 1.0
-import models.application 1.0
import models.settings 1.0
import models.system 1.0
-import QtApplicationManager 1.0
-
/*
A window stack with the home page at the bottom and at most an application window on top of it
*/
StackView {
id: root
+ property var applicationModel
+ property alias widgetListModel: homePage.widgetsList
+
property alias homePageBottomApplicationWidget: homePage.bottomApplicationWidget
property alias homePageWidgetWidth: homePage.widgetWidth
property alias homePageRowHeight: homePage.rowHeight
@@ -103,10 +103,10 @@ StackView {
}
Connections {
- target: ApplicationManagerModel
+ target: root.applicationModel
onActiveAppIdChanged: {
- if (ApplicationManagerModel.activeAppId === "" && root.depth > 1) {
+ if (root.applicationModel.activeAppId === "" && root.depth > 1) {
// go back to the home screen
root.pop();
}
@@ -132,15 +132,12 @@ StackView {
}
}
+ /*
onReleaseApplicationSurface: {
if (root.currentItem === item) {
root.pop()
}
}
-
- onUnhandledSurfaceReceived: {
- item.visible = false
- item.parent = dummyitem
- }
+ */
}
}
diff --git a/sysui/launcher/EditableGridView.qml b/sysui/launcher/EditableGridView.qml
index 4c810aa1..9d96ca3e 100755
--- a/sysui/launcher/EditableGridView.qml
+++ b/sysui/launcher/EditableGridView.qml
@@ -36,8 +36,6 @@ import QtQuick.Layouts 1.0
import controls 1.0
import utils 1.0
-import models.application 1.0
-
Item {
id: root
@@ -84,11 +82,10 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
- property var appInfo: ApplicationManagerModel.application(model.applicationId)
Connections {
- target: appButton.appInfo
+ target: model.appInfo
onActiveChanged: {
- if (appButton.appInfo.active) {
+ if (model.appInfo.active) {
appButton.checked = true;
} else {
appButton.checked = false;
@@ -152,6 +149,7 @@ Item {
onClicked: {
if (!grid.editMode) {
root.appButtonClicked(model.applicationId);
+ model.appInfo.start();
}
}
@@ -196,6 +194,7 @@ Item {
width: root.width
height: Style.vspan(1)
opacity: grid.editMode ? 1.0 : 0.0
+ visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 200
diff --git a/sysui/launcher/Launcher.qml b/sysui/launcher/Launcher.qml
index 1915c427..1d5b73cf 100755
--- a/sysui/launcher/Launcher.qml
+++ b/sysui/launcher/Launcher.qml
@@ -33,13 +33,10 @@ import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQml.Models 2.1
import QtQuick.Layouts 1.0
-import QtApplicationManager 1.0
import animations 1.0
import controls 1.0
import utils 1.0
-import models.application 1.0
-
Item {
id: root
@@ -49,10 +46,7 @@ Item {
readonly property real expandedHeight: Style.vspan(10)
readonly property bool open: gridButton.checked
- function goHome() {
- homeButton.checked = true;
- homeButton.clicked();
- }
+ property var applicationModel
Behavior on height { DefaultSmoothedAnimation {} }
@@ -81,7 +75,7 @@ Item {
checked: true
onClicked: {
gridButton.checked = false;
- ApplicationManagerModel.goHome()
+ root.applicationModel.goHome();
}
// // TODO: Replace this with the correct visualization
@@ -112,14 +106,13 @@ Item {
anchors.top: parent.top
gridOpen: root.open
- model: ApplicationManager
+ model: root.applicationModel
onButtonCreated: buttonGroup.addButton(button)
onButtonRemoved: buttonGroup.removeButton(button)
onAppButtonClicked: {
homeButton.checked = false;
gridButton.checked = false;
- ApplicationManager.startApplication(applicationId);
}
}
diff --git a/tests/qmltests/tst_HomePage.qml b/tests/qmltests/tst_HomePage.qml
index acc59bc3..d837b5e0 100644
--- a/tests/qmltests/tst_HomePage.qml
+++ b/tests/qmltests/tst_HomePage.qml
@@ -90,7 +90,12 @@ Item {
HomePage {
id: homePage
anchors.fill: parent
- widgetsList: ListModel { id: listModel }
+ widgetsList: ListModel {
+ id: listModel
+ function application(index) {
+ return get(index).appInfo;
+ }
+ }
Component.onCompleted: {
listModel.append({"appInfo":redApp})
listModel.append({"appInfo":greenApp})