From: Michael Schuster Date: Wed, 15 Jan 2020 15:42:06 +0000 (+0100) Subject: Add App list menu to Tray UI X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~222^2^2~413^2~34 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=b4b6366ba8aa6eaa19aa37c2e1f00fb2c5ff0178;p=nextcloud-desktop.git Add App list menu to Tray UI - 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 --- diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index b817054d1..6becf1662 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -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() diff --git a/src/gui/systray.h b/src/gui/systray.h index 7125c1090..b491b749f 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -70,7 +70,7 @@ signals: Q_INVOKABLE void showWindow(); public slots: - void slotChangeActivityModel(); + void slotNewUserSelected(); private: static Systray *_instance; diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 2d2da8ec6..f367a8e56 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -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 UserAppsModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "appName"; + roles[UrlRole] = "appUrl"; + return roles; +} + } diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index f7d5092f8..14b2b0c23 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -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 roleNames() const; + +private: + static UserAppsModel *_instance; + UserAppsModel(QObject *parent = 0); + + AccountAppList _apps; +}; + } #endif // USERMODEL_H diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 92f2a848a..1cf9576ef 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -380,8 +380,47 @@ Window { hoverEnabled: true onClicked: { + /* + // The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10) + // so we handle this with userModelBackend.openCurrentAccountServer() + // + // See UserModel::openCurrentAccountServer() to disable this workaround + // in the future for Qt >= 5.10 + + if(appsMenu.count() > 0) { + appsMenu.popup(); + } else { + userModelBackend.openCurrentAccountServer(); + } + */ + + appsMenu.open(); userModelBackend.openCurrentAccountServer(); } + + Menu { + id: appsMenu + x: (trayWindowAppsButton.x + 2) + y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) + width: (trayWindowAppsButton.width - 2) + closePolicy: "CloseOnPressOutside" + + background: Rectangle { + border.color: "#0082c9" + radius: 2 + } + + Instantiator { + id: appsMenuInstantiator + model: appsMenuModelBackend + onObjectAdded: appsMenu.insertItem(index, object) + onObjectRemoved: appsMenu.removeItem(object) + delegate: MenuItem { + text: appName + onTriggered: appsMenuModelBackend.openAppUrl(appUrl) + } + } + } } background: