Add App list menu to Tray UI
authorMichael Schuster <michael@schuster.ms>
Wed, 15 Jan 2020 15:42:06 +0000 (16:42 +0100)
committerMichael Schuster <michael@schuster.ms>
Wed, 15 Jan 2020 15:42:06 +0000 (16:42 +0100)
- Modify Window.qml and Systray to show the App menu upon clicking the App button
  on the top-right. Fall back to opening the general URL in case no apps are found.

- Introduce a new UserAppsModel in UserModel.cpp to access the fetched server Apps
  from AccountState (propagated down from the User class).

Signed-off-by: Michael Schuster <michael@schuster.ms>
src/gui/systray.cpp
src/gui/systray.h
src/gui/tray/UserModel.cpp
src/gui/tray/UserModel.h
src/gui/tray/Window.qml

index b817054d133a4197adf2317e724a201580f66308..6becf1662c4ccaf7534a9c19a4e75191eb56cc12 100644 (file)
@@ -57,12 +57,13 @@ Systray::Systray()
     _trayEngine = new QQmlEngine;
     _trayEngine->addImageProvider("avatars", new ImageProvider);
     _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance());
+    _trayEngine->rootContext()->setContextProperty("appsMenuModelBackend", UserAppsModel::instance());
     _trayEngine->rootContext()->setContextProperty("systrayBackend", this);
 
     _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml")));
 
     connect(UserModel::instance(), &UserModel::newUserSelected,
-        this, &Systray::slotChangeActivityModel);
+        this, &Systray::slotNewUserSelected);
 
     connect(AccountManager::instance(), &AccountManager::accountAdded,
         this, &Systray::showWindow);
@@ -77,9 +78,13 @@ void Systray::create()
     }
 }
 
-void Systray::slotChangeActivityModel()
+void Systray::slotNewUserSelected()
 {
+    // Change ActivityModel
     _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
+
+    // Rebuild App list
+    UserAppsModel::instance()->buildAppList();
 }
 
 bool Systray::isOpen()
index 7125c1090216a11666ce278adeaf35539ca42d2e..b491b749fc28c76709a7a6cae9ab47a2aad2736f 100644 (file)
@@ -70,7 +70,7 @@ signals:
     Q_INVOKABLE void showWindow();
 
 public slots:
-    void slotChangeActivityModel();
+    void slotNewUserSelected();
 
 private:
     static Systray *_instance;
index 2d2da8ec61bbf2602773da52b762904d1e2d3da2..f367a8e56b603198b7e0d553498442ded7cb5670 100644 (file)
@@ -40,6 +40,8 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
 
     connect(_account.data(), &AccountState::stateChanged,
             [=]() { if (isConnected()) {slotRefresh();} });
+    connect(_account.data(), &AccountState::hasFetchedNavigationApps,
+        this, &User::slotRebuildNavigationAppList);
 }
 
 void User::slotBuildNotificationDisplay(const ActivityList &list)
@@ -138,6 +140,12 @@ void User::slotRefreshNotifications()
     }
 }
 
+void User::slotRebuildNavigationAppList()
+{
+    // Rebuild App list
+    UserAppsModel::instance()->buildAppList();
+}
+
 void User::slotNotificationRequestFinished(int statusCode)
 {
     int row = sender()->property("activityRow").toInt();
@@ -445,6 +453,11 @@ bool User::hasActivities() const
     return _account->account()->capabilities().hasActivities();
 }
 
+AccountAppList User::appList() const
+{
+    return _account->appList();
+}
+
 bool User::isCurrentUser() const
 {
     return _isCurrentUser;
@@ -603,6 +616,10 @@ Q_INVOKABLE void UserModel::openCurrentAccountTalk()
 
 Q_INVOKABLE void UserModel::openCurrentAccountServer()
 {
+    // Don't open this URL when the QML appMenu pops up on click (see Window.qml)
+    if(appList().count() > 0)
+        return;
+
     QString url = _users[_currentUserId]->server(false);
     if (!(url.contains("http://") || url.contains("https://"))) {
         url = "https://" + _users[_currentUserId]->server(false);
@@ -717,6 +734,15 @@ void UserModel::fetchCurrentActivityModel()
     _users[currentUserId()]->slotRefresh();
 }
 
+AccountAppList UserModel::appList() const
+{
+    if (_users.count() >= 1) {
+        return _users[_currentUserId]->appList();
+    } else {
+        return AccountAppList();
+    }
+}
+
 /*-------------------------------------------------------------------------------------*/
 
 ImageProvider::ImageProvider()
@@ -734,4 +760,69 @@ QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &
     }
 }
 
+/*-------------------------------------------------------------------------------------*/
+
+UserAppsModel *UserAppsModel::_instance = nullptr;
+
+UserAppsModel *UserAppsModel::instance()
+{
+    if (_instance == nullptr) {
+        _instance = new UserAppsModel();
+    }
+    return _instance;
+}
+
+UserAppsModel::UserAppsModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+}
+
+void UserAppsModel::buildAppList()
+{
+    beginRemoveRows(QModelIndex(), 0, rowCount());
+    _apps.clear();
+    endRemoveRows();
+
+    if(UserModel::instance()->appList().count() > 0) {
+        foreach(AccountApp *app, UserModel::instance()->appList()) {
+            beginInsertRows(QModelIndex(), rowCount(), rowCount());
+            _apps << app;
+            endInsertRows();
+        }
+    }
+}
+
+void UserAppsModel::openAppUrl(const QUrl &url)
+{
+    QDesktopServices::openUrl(url);
+}
+
+int UserAppsModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return _apps.count();
+}
+
+QVariant UserAppsModel::data(const QModelIndex &index, int role) const
+{
+    if (index.row() < 0 || index.row() >= _apps.count()) {
+        return QVariant();
+    }
+
+    if (role == NameRole) {
+        return _apps[index.row()]->name();
+    } else if (role == UrlRole) {
+        return _apps[index.row()]->url();
+    }
+    return QVariant();
+}
+
+QHash<int, QByteArray> UserAppsModel::roleNames() const
+{
+    QHash<int, QByteArray> roles;
+    roles[NameRole] = "appName";
+    roles[UrlRole] = "appUrl";
+    return roles;
+}
+
 }
index f7d5092f8b9d87ddfabe3704daa1a416bb776248..14b2b0c231ae46dda56aea4e2e46fd8fef80d0ae 100644 (file)
@@ -32,6 +32,7 @@ public:
     QString server(bool shortened = true) const;
     bool serverHasTalk() const;
     bool hasActivities() const;
+    AccountAppList appList() const;
     QImage avatar(bool whiteBg = false) const;
     QString id() const;
     void login() const;
@@ -55,6 +56,7 @@ public slots:
     void slotRefreshActivities();
     void slotRefresh();
     void setNotificationRefreshInterval(std::chrono::milliseconds interval);
+    void slotRebuildNavigationAppList();
 
 private:
     AccountStatePtr _account;
@@ -117,6 +119,8 @@ public:
         IdRole
     };
 
+    AccountAppList appList() const;
+
 signals:
     Q_INVOKABLE void addAccount();
     Q_INVOKABLE void refreshCurrentUserGui();
@@ -142,5 +146,36 @@ public:
     QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
 };
 
+class UserAppsModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    static UserAppsModel *instance();
+    virtual ~UserAppsModel() {};
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+    enum UserAppsRoles {
+        NameRole = Qt::UserRole + 1,
+        UrlRole
+    };
+
+    void buildAppList();
+
+public slots:
+    void openAppUrl(const QUrl &url);
+
+protected:
+    QHash<int, QByteArray> roleNames() const;
+
+private:
+    static UserAppsModel *_instance;
+    UserAppsModel(QObject *parent = 0);
+
+    AccountAppList _apps;
+};
+
 }
 #endif // USERMODEL_H
index 92f2a848a8f3ecb068feb3fe945b622e23f7a7f4..1cf9576efab52ab33b5edc1bb089a2e692da7277 100644 (file)
@@ -380,8 +380,47 @@ Window {
                         hoverEnabled: true\r
                         onClicked:\r
                         {\r
+                            /*\r
+                            // The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10)\r
+                            // so we handle this with userModelBackend.openCurrentAccountServer()\r
+                            //\r
+                            // See UserModel::openCurrentAccountServer() to disable this workaround\r
+                            // in the future for Qt >= 5.10\r
+\r
+                            if(appsMenu.count() > 0) {\r
+                                appsMenu.popup();\r
+                            } else {\r
+                                userModelBackend.openCurrentAccountServer();\r
+                            }\r
+                            */\r
+\r
+                            appsMenu.open();\r
                             userModelBackend.openCurrentAccountServer();\r
                         }\r
+\r
+                        Menu {\r
+                            id: appsMenu\r
+                            x: (trayWindowAppsButton.x + 2)\r
+                            y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2)\r
+                            width: (trayWindowAppsButton.width - 2)\r
+                            closePolicy: "CloseOnPressOutside"\r
+\r
+                            background: Rectangle {\r
+                                border.color: "#0082c9"\r
+                                radius: 2\r
+                            }\r
+\r
+                            Instantiator {\r
+                                id: appsMenuInstantiator\r
+                                model: appsMenuModelBackend\r
+                                onObjectAdded: appsMenu.insertItem(index, object)\r
+                                onObjectRemoved: appsMenu.removeItem(object)\r
+                                delegate: MenuItem {\r
+                                    text: appName\r
+                                    onTriggered: appsMenuModelBackend.openAppUrl(appUrl)\r
+                                }\r
+                            }\r
+                        }\r
                     }\r
 \r
                     background:\r