diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# KAccounts Integration + +Integration library and QML module for Accounts-SSO and SignOn-SSO + +# Introduction + +KAccounts Integration provides a way to share accounts data such as login tokens and general +user information (like usernames and such) between various applications. + +The KAccounts library is a KDE Frameworks style abstraction layer on top of the Accounts-SSO +and SignOnD libraries, which uses a combination of models and jobs to expose the functionality +of those. + +The kaccounts QML plugin exposes that functionality directly to Qt Quick based applications, +and using the classes only requires importing the module like so: + +``` +import org.kde.kaccounts 1.2 as KAccounts +``` + +The main functionality in the library can be accessed through the various classes below, and +the accounts manager can be accessed directly through ```KAccounts::accountsManager()```. The +other central classes are: + +## Models + +* AccountsModel +* ServicesModel +* ProvidersModel + +## Jobs + +* AccountServiceToggleJob +* ChangeAccountDisplayNameJob +* CreateAccountJob +* RemoveAccountJob + +# KDE Control Module + +The Online Accounts KCM is the main user-visible point for KAccounts, and can be accessed +either through System Settings, or directly from any system menu which allows launching of +KCMs directly (including KRunner). It is built using the Qt Quick module mentioned above, +and uses Kirigami as its base. + +# Provider and Service files + +If you plan on creating new providers and services, you will need to register those with +the accounts manager. Two cmake macros are provided to assist you in the creation and +installation of these files, and further assists in translation integration for them: + +* kaccounts_add_provider +* kaccounts_add_service diff --git a/src/declarative/kaccountsdeclarativeplugin.cpp b/src/declarative/kaccountsdeclarativeplugin.cpp --- a/src/declarative/kaccountsdeclarativeplugin.cpp +++ b/src/declarative/kaccountsdeclarativeplugin.cpp @@ -1,5 +1,6 @@ /************************************************************************************* * Copyright (C) 2015 by Aleix Pol * + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * @@ -17,12 +18,35 @@ *************************************************************************************/ #include "kaccountsdeclarativeplugin.h" -#include "createaccountjob.h" + +#include "accountsmodel.h" +#include "servicesmodel.h" +#include "providersmodel.h" + #include "accountservicetogglejob.h" +#include "changeaccountdisplaynamejob.h" +#include "createaccountjob.h" +#include "removeaccountjob.h" + #include void KAccountsDeclarativePlugin::registerTypes(const char* uri) { - qmlRegisterType(uri, 1, 0, "CreateAccountJob"); - qmlRegisterType(uri, 1, 1, "AccountServiceToggleJob"); + // Version 1.0 + // Consider this registration deprecated - use the one named ...Job below instead + qmlRegisterType( uri, 1, 0, "CreateAccount"); + + // Version 1.1 + // Consider this registration deprecated - use the one named ...Job below instead + qmlRegisterType( uri, 1, 1, "AccountServiceToggle"); + + // Version 1.2 + qmlRegisterType( uri, 1, 2, "AccountsModel"); + qmlRegisterType( uri, 1, 2, "ProvidersModel"); + qmlRegisterType( uri, 1, 2, "ServicesModel"); + + qmlRegisterType( uri, 1, 2, "AccountServiceToggleJob"); + qmlRegisterType( uri, 1, 2, "ChangeAccountDisplayNameJob"); + qmlRegisterType( uri, 1, 2, "CreateAccountJob"); + qmlRegisterType( uri, 1, 2, "RemoveAccountJob"); } diff --git a/src/kcm/accounts.cpp b/src/kcm/accounts.cpp --- a/src/kcm/accounts.cpp +++ b/src/kcm/accounts.cpp @@ -32,7 +32,9 @@ KAboutData* about = new KAboutData(QStringLiteral("kcm_kaccounts"), i18n("Accounts"), QStringLiteral("1.0"), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("Sebastian Kügler"), QString(), QStringLiteral("sebas@kde.org")); + about->addAuthor(i18n("Dan Leinir Turthra Jensen"), QString(), QStringLiteral("admin@leinir.dk"), QString(), QStringLiteral("leinir")); setAboutData(about); + setButtons(KQuickAddons::ConfigModule::NoAdditionalButton); } #include "accounts.moc" diff --git a/src/kcm/package/contents/ui/AccountDetails.qml b/src/kcm/package/contents/ui/AccountDetails.qml new file mode 100644 --- /dev/null +++ b/src/kcm/package/contents/ui/AccountDetails.qml @@ -0,0 +1,200 @@ +/* + * Copyright 2019 Nicolas Fella + * Copyright 2020 Dan Leinir Turthra Jensen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.0 as Controls +import org.kde.kirigami 2.4 as Kirigami +import org.kde.kaccounts 1.2 as KAccounts +import org.kde.kcm 1.2 + +SimpleKCM { + id: component; + + title: i18n("Account Details") + + property alias model: servicesList.model + + MessageBoxSheet { + id: accountRemovalDlg + parent: component + property string displayName: servicesList.model.accountDisplayName + property string providerName: servicesList.model.accountProviderName + title: i18nc("The title for a dialog which lets you remove an account", "Remove Account?") + text: { + if (accountRemovalDlg.displayName.length > 0 && accountRemovalDlg.providerName.length > 0) { + return i18nc("The text for a dialog which lets you remove an account when both provider name and account name are available", "Are you sure you wish to remove the \"%1\" account \"%2\"?", accountRemovalDlg.providerName, accountRemovalDlg.displayName) + } else if (accountRemovalDlg.displayName.length > 0) { + return i18nc("The text for a dialog which lets you remove an account when only the account name is available", "Are you sure you wish to remove the account \"%1\"?", accountRemovalDlg.displayName) + } else { + return i18nc("The text for a dialog which lets you remove an account when only the provider name is available", "Are you sure you wish to remove this \"%1\" account?", accountRemovalDlg.providerName) + } + } + actions: [ + Kirigami.Action { + text: i18nc("The label for a button which will cause the removal of a specified account", "Remove Account") + onTriggered: { + var job = accountRemovalJob.createObject(component, { "accountId": servicesList.model.accountId }); + job.start(); + } + } + ] + } + + MessageBoxSheet { + id: renameAccountDlg + parent: component + title: i18nc("The title for a dialog which lets you set the human-readable name of an account", "Rename Account") + onSheetOpenChanged: { + if (sheetOpen === true) { + newAccountDisplayName.text = servicesList.model.accountDisplayName; + } + } + contentItem: Kirigami.FormLayout { + Layout.preferredWidth: Kirigami.Units.gridUnit * 10 + Layout.margins: Kirigami.Units.largeSpacing + Controls.TextField { + id: newAccountDisplayName + Kirigami.FormData.label: i18nc("Label for the text field used to enter a new human-readable name for an account", "Enter the new name of the account") + } + } + actions: [ + Kirigami.Action { + enabled: newAccountDisplayName.text.length > 0 + text: i18nc("Text of a button which will cause the human-readable name of an account to be set to a text specified by the user", "Set Account Name") + onTriggered: { + var job = accountDisplayNameJob.createObject(component, { "accountId": servicesList.model.accountId, "displayName": newAccountDisplayName.text }) + job.start(); + } + } + ] + } + + Component { + id: accountDisplayNameJob + KAccounts.ChangeAccountDisplayNameJob { } + } + + Component { + id: serviceToggleJob + KAccounts.AccountServiceToggleJob { } + } + + Component { + id: accountRemovalJob + KAccounts.RemoveAccountJob { + onFinished: { + kcm.pop(); + } + } + } + + header: RowLayout { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.smallSpacing + spacing: Kirigami.Units.smallSpacing + Kirigami.Icon { + source: model.accountIconName + Layout.preferredWidth: Kirigami.Units.iconSizes.large + Layout.preferredHeight: Kirigami.Units.iconSizes.large + } + Controls.Label { + Layout.fillWidth: true + text: { + if (model.accountDisplayName.length > 0 && model.accountProviderName.length > 0) { + return i18n("%1 (%2)", model.accountDisplayName, model.accountProviderName) + } else if (model.accountDisplayName.length > 0) { + return model.accountDisplayName + } else { + return i18n("%1 account", model.accountProviderName) + } + } + } + Controls.ToolButton { + icon.name: "edit-entry" + display: Controls.AbstractButton.IconOnly + Layout.preferredHeight: Kirigami.Units.iconSizes.large + Layout.preferredWidth: Kirigami.Units.iconSizes.large + onClicked: renameAccountDlg.open(); + Controls.ToolTip { + visible: parent.hovered && !parent.pressed + text: i18nc("Button which spawns a dialog allowing the user to change the displayed account's human-readable name", "Change Account Display Name") + delay: Kirigami.Units.toolTipDelay + timeout: 5000 + y: parent.height + } + } + } + + footer: RowLayout { + Controls.Button { + Layout.alignment: Qt.AlignRight + text: i18n("Remove This Account") + icon.name: "edit-delete-remove" + onClicked: accountRemovalDlg.open(); + } + } + + Kirigami.FormLayout { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + Item { + visible: servicesList.count === 0 + Layout.fillWidth: true + height: Kirigami.Units.largeSpacing + } + Kirigami.AbstractCard { + Layout.fillWidth: true + visible: servicesList.count === 0 + header: Kirigami.Heading { + text: i18nc("Heading for a box informing the user there are no configuration points in this account", "No Services") + } + contentItem: Controls.Label { + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: i18nc("A text shown when an account has no configurable services", "This account has no services available for configuration") + } + } + Kirigami.Separator { + visible: servicesList.count > 0 + Kirigami.FormData.label: i18nc("Heading for a list of services available with this account", "Use This Account For") + Kirigami.FormData.isSection: true + } + Repeater { + id: servicesList + delegate: Controls.CheckBox { + id: serviceCheck + Kirigami.FormData.label: model.description + checked: model.enabled + text: model.displayName + Binding { + target: serviceCheck + property: "checked" + value: model.enabled + } + onClicked: { + var job = serviceToggleJob.createObject(component, { "accountId": servicesList.model.accountId, "serviceId": model.name, "serviceEnabled": !model.enabled }) + job.start() + } + } + } + } +} diff --git a/src/kcm/package/contents/ui/Accounts.qml b/src/kcm/package/contents/ui/Accounts.qml --- a/src/kcm/package/contents/ui/Accounts.qml +++ b/src/kcm/package/contents/ui/Accounts.qml @@ -26,73 +26,73 @@ import org.kde.kirigami 2.7 as Kirigami -import org.kde.kaccounts 1.0 +import org.kde.kaccounts 1.2 as KAccounts import org.kde.kcm 1.2 -import Ubuntu.OnlineAccounts 0.1 as OA - ScrollViewKCM { id: kaccountsRoot // Existing accounts view: ListView { - model: OA.AccountServiceModel { - id: accountsModel - service: "global" - includeDisabled: true - } + model: KAccounts.AccountsModel { } delegate: Kirigami.SwipeListItem { id: accountDelegate width: ListView.view.width - contentItem: Controls.Label { - text: { - if (model.displayName.length > 0 && model.providerName.length > 0) { - return i18n("%1 (%2)", model.displayName, model.providerName) - } else if (model.displayName.length > 0) { - return model.displayName - } else { - return i18n("%1 account", model.providerName) - } + contentItem: RowLayout { + implicitWidth: accountDelegate.ListView.view.width + implicitHeight: Kirigami.Units.iconSizes.large + Kirigami.Units.smallSpacing * 2 + spacing: Kirigami.Units.smallSpacing + Kirigami.Icon { + source: model.iconName + Layout.preferredWidth: Kirigami.Units.iconSizes.large + Layout.preferredHeight: Kirigami.Units.iconSizes.large } - - OA.Account { - id: account - objectHandle: model.accountHandle + Controls.Label { + Layout.fillWidth: true + text: { + if (model.displayName.length > 0 && model.providerName.length > 0) { + return i18n("%1 (%2)", model.displayName, model.providerName) + } else if (model.displayName.length > 0) { + return model.displayName + } else { + return i18n("%1 account", model.providerName) + } + } } } actions: [ Kirigami.Action { text: i18nc("Tooltip for an action which will offer the user to remove the mentioned account", "Remove %1", accountDelegate.contentItem.text) iconName: "edit-delete-remove" onTriggered: { - accountRemovalDlg.account = account; + accountRemovalDlg.accountId = model.id; accountRemovalDlg.displayName = model.displayName; accountRemovalDlg.providerName = model.providerName; accountRemovalDlg.open(); } } ] - onClicked: kcm.push("AvailableServices.qml", {accountId: model.accountId}) + onClicked: kcm.push("AccountDetails.qml", {model: model.services}) } Controls.Label { - anchors { - fill: parent - margins: Kirigami.Units.largeSpacing - } - verticalAlignment: Text.AlignVCenter + visible: view.count === 0 + anchors.fill: parent + clip: true horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap - visible: parent.count === 0 - opacity: 0.5 - text: i18nc("A text shown when a user has not yet added any accounts", "You have not added any accounts yet.\nClick the \"Add new Account\" button below to do so.") + textFormat: Text.RichText + text: i18nc("A text shown when a user has not yet added any accounts", "You have not added any accounts yet.

Click on \"Add New Account...\" to do so.") + onLinkActivated: kcm.push("AvailableAccounts.qml") } } + MessageBoxSheet { id: accountRemovalDlg parent: kaccountsRoot - property QtObject account + property int accountId property string displayName property string providerName title: i18nc("The title for a dialog which lets you remove an account", "Remove Account?") @@ -108,15 +108,23 @@ actions: [ Kirigami.Action { text: i18nc("The label for a button which will cause the removal of a specified account", "Remove Account") - onTriggered: { accountRemovalDlg.account.remove(); } + onTriggered: { + var job = accountRemovalJob.createObject(kaccountsRoot, { "accountId": accountRemovalDlg.accountId }); + job.start(); + } } ] } + Component { + id: accountRemovalJob + KAccounts.RemoveAccountJob { } + } + footer: RowLayout { Controls.Button { Layout.alignment: Qt.AlignRight - text: i18n("Add new Account") + text: i18n("Add New Account...") icon.name: "contact-new" onClicked: kcm.push("AvailableAccounts.qml") } diff --git a/src/kcm/package/contents/ui/AvailableAccounts.qml b/src/kcm/package/contents/ui/AvailableAccounts.qml --- a/src/kcm/package/contents/ui/AvailableAccounts.qml +++ b/src/kcm/package/contents/ui/AvailableAccounts.qml @@ -1,5 +1,6 @@ /* * Copyright 2019 Nicolas Fella + * Copyright 2020 Dan Leinir Turthra Jensen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -21,32 +22,69 @@ import QtQuick.Layouts 1.11 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.4 as Kirigami -import org.kde.kaccounts 1.0 +import org.kde.kaccounts 1.2 as KAccounts import org.kde.kcm 1.2 -import Ubuntu.OnlineAccounts 0.1 as OA ScrollViewKCM { id: root - title: i18n("Add new Account") + title: i18n("Add New Account") view: ListView { - model: OA.ProviderModel {} - - delegate: Kirigami.BasicListItem { - icon: model.iconName - label: model.displayName - width: parent.width + model: KAccounts.ProvidersModel {} + delegate: Kirigami.AbstractListItem { + id: accountDelegate + width: ListView.view.width + enabled: model.supportsMultipleAccounts === true || model.accountsCount === 0 + contentItem: RowLayout { + implicitWidth: accountDelegate.ListView.view.width + implicitHeight: Kirigami.Units.iconSizes.large + Kirigami.Units.smallSpacing * 2 + spacing: Kirigami.Units.smallSpacing + Kirigami.Icon { + source: model.iconName + Layout.preferredWidth: Kirigami.Units.iconSizes.large + Layout.preferredHeight: Kirigami.Units.iconSizes.large + Item { + visible: model.accountsCount > 0 + anchors { + bottom: parent.bottom + right: parent.right + } + height: parent.height / 3 + width: height + Rectangle { + anchors.fill: parent + radius: height / 2 + color: Kirigami.Theme.highlightColor + border { + width: 1 + color: Kirigami.Theme.highlightedTextColor + } + } + Controls.Label { + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Kirigami.Theme.highlightedTextColor + text: model.accountsCount + } + } + } + Controls.Label { + Layout.fillWidth: true + text: model.displayName + "\n" + model.description; + } + } onClicked: { - var job = jobComponent.createObject(root, { "providerName": providerId }) + var job = jobComponent.createObject(root, { "providerName": model.name }) job.start() } } } Component { id: jobComponent - CreateAccountJob { + KAccounts.CreateAccountJob { onFinished: kcm.pop() } } diff --git a/src/kcm/package/contents/ui/AvailableServices.qml b/src/kcm/package/contents/ui/AvailableServices.qml deleted file mode 100644 --- a/src/kcm/package/contents/ui/AvailableServices.qml +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019 Nicolas Fella - * Copyright 2020 Dan Leinir Turthra Jensen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.7 -import QtQuick.Layouts 1.11 -import QtQuick.Controls 2.0 as Controls -import org.kde.kirigami 2.4 as Kirigami -import org.kde.kaccounts 1.1 as KAccounts -import org.kde.kcm 1.2 -import Ubuntu.OnlineAccounts 0.1 as OA - -ScrollViewKCM { - id: component; - - title: i18n("Available Services") - - property alias accountId: servicesModel.accountId - - view: ListView { - model: OA.AccountServiceModel { - id: servicesModel - includeDisabled: true - function refreshData() { - // Because AccountServiceModel seems to not pick this up itself, we'll reset the model... like so, because there's no reset - var oldId = component.accountId; - component.accountId = ""; - component.accountId = oldId; - } - } - delegate: Kirigami.AbstractListItem { - width: parent.width - Controls.CheckBox { - id: serviceCheck - text: model.serviceName - checked: model.enabled - Binding { - target: serviceCheck - property: "checked" - value: model.enabled - } - onClicked: { - var job = jobComponent.createObject(component, { "accountId": component.accountId, "serviceId": model.serviceName, "serviceEnabled": !model.enabled }) - job.result.connect(servicesModel.refreshData); - job.start() - } - } - } - Controls.Label { - anchors.fill: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - visible: parent.count === 0 - opacity: 0.5 - text: i18nc("A text shown when an account has no configurable services", "(No services for this account)") - } - } - Component { - id: jobComponent - KAccounts.AccountServiceToggleJob { } - } -} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -6,24 +6,36 @@ PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KAccountsConfigVersion.cmake" SOVERSION ${KACCOUNTS_SOVERSION}) -set(kaccountslib_SRCS +set (kaccountslib_SRCS + accountsmodel.cpp + core.cpp + kaccountsdplugin.cpp + kaccountsuiplugin.cpp + providersmodel.cpp + servicesmodel.cpp + uipluginsmanager.cpp + accountservicetogglejob.cpp + changeaccountdisplaynamejob.cpp createaccountjob.cpp - kaccountsuiplugin.cpp - kaccountsdplugin.cpp getcredentialsjob.cpp - core.cpp - uipluginsmanager.cpp + removeaccountjob.cpp ) ecm_generate_headers(kaccountslib_HEADERS HEADER_NAMES + AccountsModel Core - GetCredentialsJob KAccountsUiPlugin KAccountsDPlugin + ProvidersModel + ServicesModel + AccountServiceToggleJob + ChangeAccountDisplayNameJob CreateAccountJob + GetCredentialsJob + RemoveAccountJob REQUIRED_HEADERS kaccountslib_HEADERS ) diff --git a/src/lib/accountservicetogglejob.h b/src/lib/accountservicetogglejob.h --- a/src/lib/accountservicetogglejob.h +++ b/src/lib/accountservicetogglejob.h @@ -25,6 +25,9 @@ #include +/** + * @brief A job used to change the enabled state of a specific service on a specific account + */ class KACCOUNTS_EXPORT AccountServiceToggleJob : public KJob { Q_OBJECT diff --git a/src/lib/accountsmodel.h b/src/lib/accountsmodel.h new file mode 100644 --- /dev/null +++ b/src/lib/accountsmodel.h @@ -0,0 +1,72 @@ +/************************************************************************************* + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef ACCOUNTS_MODEL_H +#define ACCOUNTS_MODEL_H + +#include "kaccounts_export.h" + +#include + +#include + +/** + * @brief A model representing all the accounts registered on a system + * + * # Roles + * + * The following role names are available in this model: + * + * * id: The internal ID of the account + * * services: A model which contains information about the services this account supports (see ServicesModel) + * * enabled: Whether or not this account is enabled + * * credentialsId: The internal ID for any stored credentials for this account + * * displayName: A human-readable name for this account (change this using ChangeAccountDisplayNameJob) + * * providerName: The internal name of the provider this account is registered through + * * iconName: An XDG Icon specification icon name + * * dataObject: The instance of Accounts::Account which the data for this account is fetched from + */ +class KACCOUNTS_EXPORT AccountsModel : public QAbstractListModel +{ + Q_OBJECT + + public: + enum Roles { + IdRole = Qt::UserRole + 1, + ServicesRole, + EnabledRole, + CredentialsIdRole, + DisplayNameRole, + ProviderNameRole, + IconNameRole, + DataObjectRole + }; + explicit AccountsModel(QObject* parent = nullptr); + virtual ~AccountsModel(); + + QHash< int, QByteArray > roleNames() const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + private: + class Private; + Private *d; +}; + +#endif //ACCOUNTS_MODEL_H diff --git a/src/lib/accountsmodel.cpp b/src/lib/accountsmodel.cpp new file mode 100644 --- /dev/null +++ b/src/lib/accountsmodel.cpp @@ -0,0 +1,193 @@ +/************************************************************************************* + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "accountsmodel.h" + +#include "core.h" +#include "servicesmodel.h" + +#include + +#include +#include + +#include + +#include +#include + +class AccountsModel::Private : public QObject +{ +public: + Private(AccountsModel *model) + : accountsManager(KAccounts::accountsManager()) + , q(model) + { + accountIDs = accountsManager->accountList(); + + connect(accountsManager, &Accounts::Manager::accountCreated, + q, [this](Accounts::AccountId accountId){ + int row = accountIDs.count(); + q->beginInsertRows(QModelIndex(), row, row); + accountIDs.insert(row, accountId); + q->endInsertRows(); + }); + connect(accountsManager, &Accounts::Manager::accountRemoved, + q, [this](Accounts::AccountId accountId) { + q->beginRemoveRows(QModelIndex(), accountIDs.indexOf(accountId), accountIDs.indexOf(accountId)); + removeAccount(accountId); + q->endRemoveRows(); + }); + }; + virtual ~Private() + { + qDeleteAll(accounts); + }; + + Accounts::Manager *accountsManager; + Accounts::AccountIdList accountIDs; + QHash accounts; + QHash servicesModels; + + Accounts::Account* accountById(int id); + void removeAccount(Accounts::AccountId accountId); + +private: + AccountsModel* q; +}; + +Accounts::Account* AccountsModel::Private::accountById(int id) +{ + if (accounts.contains(id)) { + return accounts.value(id); + } + + // If we don't yet have this account cached, get it and connect it up to the model + Accounts::Account* account = accountsManager->account(id); + if (!account) { + qDebug() << "\t Failed to get the account from manager"; + return nullptr; + } + + connect(account, &Accounts::Account::displayNameChanged, q, [this,account](){ + QModelIndex accountIndex = q->index(accountIDs.indexOf(account->id())); + Q_EMIT q->dataChanged(accountIndex, accountIndex, QVector() << AccountsModel::DisplayNameRole); + }); + + accounts[id] = account; + return account; +} + +void AccountsModel::Private::removeAccount(Accounts::AccountId accountId) +{ + accountIDs.removeOne(accountId); + delete accounts.take(accountId); +} + +AccountsModel::AccountsModel(QObject* parent) + : QAbstractListModel(parent) + , d(new AccountsModel::Private(this)) +{ +} + +AccountsModel::~AccountsModel() +{ + delete d; +} + +QHash AccountsModel::roleNames() const +{ + static QHash roles{ + {IdRole, "id"}, + {ServicesRole, "services"}, + {EnabledRole, "enabled"}, + {CredentialsIdRole, "credentialsId"}, + {DisplayNameRole, "displayName"}, + {ProviderNameRole, "providerName"}, + {IconNameRole, "iconName"}, + {DataObjectRole, "dataObject"} + }; + return roles; +} + +int AccountsModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->accountIDs.count(); +} + +QVariant AccountsModel::data(const QModelIndex& index, int role) const +{ + QVariant data; + if(checkIndex(index)) { + Accounts::AccountId accountId = d->accountIDs.value(index.row()); + Accounts::Account *account = d->accountById(accountId); + if (account) { + switch (role) { + case IdRole: + data.setValue(account->id()); + break; + case ServicesRole: + { + ServicesModel* servicesModel{nullptr}; + if (d->servicesModels.contains(account)) { + servicesModel = d->servicesModels.value(account); + } else { + // Not parenting to the account itself, so we can avoid it suddenly + // disappearing. Just to be on the safe side + servicesModel = new ServicesModel(d->accountsManager); + servicesModel->setAccount(account); + d->servicesModels[account] = servicesModel; + } + data.setValue(servicesModel); + break; + } + case EnabledRole: + data.setValue(account->enabled()); + break; + case CredentialsIdRole: + data.setValue(account->credentialsId()); + break; + case DisplayNameRole: + data.setValue(account->displayName()); + break; + case ProviderNameRole: + data.setValue(account->providerName()); + break; + case IconNameRole: + { + QString iconName = QString::fromLatin1("unknown"); + if (account->provider().isValid() && !account->provider().iconName().isEmpty()) { + iconName = account->provider().iconName(); + } + data.setValue(iconName); + break; + } + case DataObjectRole: + data.setValue(account); + break; + } + } + } + + return data; +} diff --git a/src/lib/accountservicetogglejob.h b/src/lib/changeaccountdisplaynamejob.h copy from src/lib/accountservicetogglejob.h copy to src/lib/changeaccountdisplaynamejob.h --- a/src/lib/accountservicetogglejob.h +++ b/src/lib/changeaccountdisplaynamejob.h @@ -16,40 +16,41 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ -#ifndef ACCOUNTSERVICETOGGLE_H -#define ACCOUNTSERVICETOGGLE_H +#ifndef CHANGEACCOUNTDISPLAYNAMEJOB_H +#define CHANGEACCOUNTDISPLAYNAMEJOB_H #include "kaccounts_export.h" #include #include -class KACCOUNTS_EXPORT AccountServiceToggleJob : public KJob +/** + * @brief A job used to change the human-readable name of a specified account + * + * This job will refuse to change the name to something empty (while it is technically + * possible to do so for an account, it is highly undesirable) + */ +class KACCOUNTS_EXPORT ChangeAccountDisplayNameJob : public KJob { Q_OBJECT Q_PROPERTY(QString accountId READ accountId WRITE setAccountId NOTIFY accountIdChanged) - Q_PROPERTY(QString serviceId READ serviceId WRITE setServiceId NOTIFY serviceIdChanged) - Q_PROPERTY(bool serviceEnabled READ serviceEnabled WRITE setServiceEnabled NOTIFY serviceEnabledChanged) + Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged) public: - explicit AccountServiceToggleJob(QObject* parent = nullptr); - virtual ~AccountServiceToggleJob(); + explicit ChangeAccountDisplayNameJob(QObject* parent = nullptr); + virtual ~ChangeAccountDisplayNameJob(); void start() override; QString accountId() const; void setAccountId(const QString& accountId); Q_SIGNAL void accountIdChanged(); - QString serviceId() const; - void setServiceId(const QString& serviceId); - Q_SIGNAL void serviceIdChanged(); - - bool serviceEnabled() const; - void setServiceEnabled(bool serviceEnabled); - Q_SIGNAL void serviceEnabledChanged(); + QString displayName() const; + void setDisplayName(const QString& displayName); + Q_SIGNAL void displayNameChanged(); private: class Private; Private* d; }; -#endif//ACCOUNTSERVICETOGGLE_H +#endif//CHANGEACCOUNTDISPLAYNAMEJOB_H diff --git a/src/lib/changeaccountdisplaynamejob.cpp b/src/lib/changeaccountdisplaynamejob.cpp new file mode 100644 --- /dev/null +++ b/src/lib/changeaccountdisplaynamejob.cpp @@ -0,0 +1,90 @@ +/************************************************************************************* + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "changeaccountdisplaynamejob.h" + +#include "core.h" +#include +#include +#include + +class ChangeAccountDisplayNameJob::Private { +public: + Private() {} + QString accountId; + QString displayName; +}; + +ChangeAccountDisplayNameJob::ChangeAccountDisplayNameJob(QObject* parent) + : KJob(parent) + , d(new Private) +{ } + +ChangeAccountDisplayNameJob::~ChangeAccountDisplayNameJob() +{ + delete d; +} + +QString ChangeAccountDisplayNameJob::accountId() const +{ + return d->accountId; +} + +void ChangeAccountDisplayNameJob::setAccountId(const QString& accountId) +{ + d->accountId = accountId; + Q_EMIT accountIdChanged(); +} + +QString ChangeAccountDisplayNameJob::displayName() const +{ + return d->displayName; +} + +void ChangeAccountDisplayNameJob::setDisplayName(const QString& displayName) +{ + d->displayName = displayName; + Q_EMIT displayNameChanged(); +} + +void ChangeAccountDisplayNameJob::start() +{ + if (!d->displayName.isEmpty()) { + Accounts::Manager* accountsManager = KAccounts::accountsManager(); + if (accountsManager) { + Accounts::Account *account = accountsManager->account(d->accountId.toInt()); + if (account) { + account->setDisplayName(d->displayName); + connect(account, &Accounts::Account::synced, this, [this](){ emitResult(); }); + account->sync(); + } else { + qWarning() << "No account found with the ID" << d->accountId; + setErrorText(i18n("No account found with the ID %1").arg(d->accountId)); + emitResult(); + } + } else { + qWarning() << "No accounts manager, this is not awesome."; + setErrorText(i18n("No accounts manager, this is not awesome.")); + emitResult(); + } + } else { + qWarning() << "Setting an account display name to empty is a terrible idea, and we refuse to do that"; + setErrorText(i18n("The display name cannot be empty")); + emitResult(); + } +} diff --git a/src/lib/createaccountjob.h b/src/lib/createaccountjob.h --- a/src/lib/createaccountjob.h +++ b/src/lib/createaccountjob.h @@ -39,6 +39,9 @@ class IdentityInfo; } +/** + * @brief Create a new account for the specified provider + */ class KACCOUNTS_EXPORT CreateAccountJob : public KJob { Q_OBJECT diff --git a/src/lib/createaccountjob.cpp b/src/lib/createaccountjob.cpp --- a/src/lib/createaccountjob.cpp +++ b/src/lib/createaccountjob.cpp @@ -17,6 +17,7 @@ *************************************************************************************/ #include "createaccountjob.h" + #include "kaccountsuiplugin.h" #include "core.h" #include "uipluginsmanager.h" diff --git a/src/lib/getcredentialsjob.h b/src/lib/getcredentialsjob.h --- a/src/lib/getcredentialsjob.h +++ b/src/lib/getcredentialsjob.h @@ -30,7 +30,7 @@ } /** - * A KJob for obtaining user's credentials for the given Accounts::AccountId + * @brief A KJob for obtaining user's credentials for the given Accounts::AccountId */ class KACCOUNTS_EXPORT GetCredentialsJob : public KJob { diff --git a/src/lib/providersmodel.h b/src/lib/providersmodel.h new file mode 100644 --- /dev/null +++ b/src/lib/providersmodel.h @@ -0,0 +1,64 @@ +/************************************************************************************* + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef PROVIDERS_MODEL_H +#define PROVIDERS_MODEL_H + +#include "kaccounts_export.h" + +#include + +/** + * @brief A model which represents the available providers + * + * # Roles + * + * The role names available in this model are: + * + * * name: The internal name identifying this provider + * * displayName: The human-readable name for this provider + * * description: A (usually single sentence) description of this provider + * * iconName: An XDG Icon specification icon name for this provider + * * supportsMultipleAccounts: Whether or not this provider supports multiple simultaneous accounts + * * accountsCount: The number of accounts which already exist on the system for this provider + */ +class KACCOUNTS_EXPORT ProvidersModel : public QAbstractListModel +{ + Q_OBJECT + public: + enum Roles { + NameRole = Qt::UserRole + 1, ///< The unique name identifier for this provider + DisplayNameRole, ///< The human-readable name for this provider + DescriptionRole, ///< A (usually single sentence) description of this provider + IconNameRole, ///< The name of the icon to be used for this provider (an XDG Icon Spec style name) + SupportsMultipleAccountsRole, ///< Whether or not this provider supports multiple simultaneous accounts + AccountsCountRole ///< The number of accounts which already exist for this provider + }; + explicit ProvidersModel(QObject* parent = nullptr); + virtual ~ProvidersModel(); + + QHash< int, QByteArray > roleNames() const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + private: + class Private; + Private *d; +}; + +#endif//PROVIDERS_MODEL_H diff --git a/src/lib/providersmodel.cpp b/src/lib/providersmodel.cpp new file mode 100644 --- /dev/null +++ b/src/lib/providersmodel.cpp @@ -0,0 +1,115 @@ +/************************************************************************************* + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "providersmodel.h" + +#include +#include +#include + +class ProvidersModel::Private { +public: + Private() {} + Accounts::Manager* accountsManager{nullptr}; + + const Accounts::ProviderList& providerList() { + if (!accountsManager) { + accountsManager = KAccounts::accountsManager(); + providers = accountsManager->providerList(); + } + return providers; + } +private: + Accounts::ProviderList providers; +}; + +ProvidersModel::ProvidersModel(QObject* parent) + : QAbstractListModel(parent) + , d(new Private) +{ +} + +ProvidersModel::~ProvidersModel() +{ + delete d; +} + +QHash ProvidersModel::roleNames() const +{ + static const QHash roleNames { + {NameRole, "name"}, + {DisplayNameRole, "displayName"}, + {DescriptionRole, "description"}, + {IconNameRole, "iconName"}, + {SupportsMultipleAccountsRole, "supportsMultipleAccounts"}, + {AccountsCountRole, "accountsCount"} + }; + return roleNames; +} + +int ProvidersModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + return d->providerList().count(); +} + +QVariant ProvidersModel::data(const QModelIndex& index, int role) const +{ + QVariant data; + if (checkIndex(index)) { + const Accounts::Provider& provider = d->providerList().value(index.row()); + if (provider.isValid()) { + switch(role) { + case NameRole: + data.setValue(provider.name()); + break; + case DisplayNameRole: + data.setValue(provider.displayName()); + break; + case DescriptionRole: + data.setValue(provider.description()); + break; + case IconNameRole: + data.setValue(provider.iconName()); + break; + case SupportsMultipleAccountsRole: + data.setValue(!provider.isSingleAccount()); + break; + case AccountsCountRole: + { + const Accounts::AccountIdList accounts = d->accountsManager->accountList(); + int i{0}; + for (const Accounts::AccountId& accountId : accounts) { + Accounts::Account* account = d->accountsManager->account(accountId); + if (account->providerName() == provider.name()) { + ++i; + } + } + data.setValue(i); + break; + } + default: + data.setValue(QString::fromLatin1("No such role: %1").arg(role)); + break; + } + } + } + return data; +} diff --git a/src/lib/accountservicetogglejob.h b/src/lib/removeaccountjob.h copy from src/lib/accountservicetogglejob.h copy to src/lib/removeaccountjob.h --- a/src/lib/accountservicetogglejob.h +++ b/src/lib/removeaccountjob.h @@ -16,40 +16,33 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ -#ifndef ACCOUNTSERVICETOGGLE_H -#define ACCOUNTSERVICETOGGLE_H +#ifndef REMOVEACCOUNT_H +#define REMOVEACCOUNT_H #include "kaccounts_export.h" #include -#include +#include -class KACCOUNTS_EXPORT AccountServiceToggleJob : public KJob +/** + * @brief A job which will attempt to remove the specified account + */ +class KACCOUNTS_EXPORT RemoveAccountJob : public KJob { Q_OBJECT Q_PROPERTY(QString accountId READ accountId WRITE setAccountId NOTIFY accountIdChanged) - Q_PROPERTY(QString serviceId READ serviceId WRITE setServiceId NOTIFY serviceIdChanged) - Q_PROPERTY(bool serviceEnabled READ serviceEnabled WRITE setServiceEnabled NOTIFY serviceEnabledChanged) public: - explicit AccountServiceToggleJob(QObject* parent = nullptr); - virtual ~AccountServiceToggleJob(); + explicit RemoveAccountJob(QObject* parent = nullptr); + virtual ~RemoveAccountJob(); void start() override; QString accountId() const; void setAccountId(const QString& accountId); Q_SIGNAL void accountIdChanged(); - - QString serviceId() const; - void setServiceId(const QString& serviceId); - Q_SIGNAL void serviceIdChanged(); - - bool serviceEnabled() const; - void setServiceEnabled(bool serviceEnabled); - Q_SIGNAL void serviceEnabledChanged(); private: class Private; Private* d; }; -#endif//ACCOUNTSERVICETOGGLE_H +#endif//REMOVEACCOUNT_H diff --git a/src/lib/accountservicetogglejob.h b/src/lib/removeaccountjob.cpp copy from src/lib/accountservicetogglejob.h copy to src/lib/removeaccountjob.cpp --- a/src/lib/accountservicetogglejob.h +++ b/src/lib/removeaccountjob.cpp @@ -16,40 +16,60 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ -#ifndef ACCOUNTSERVICETOGGLE_H -#define ACCOUNTSERVICETOGGLE_H +#include "removeaccountjob.h" -#include "kaccounts_export.h" +#include +#include "core.h" +#include +#include -#include +class RemoveAccountJob::Private { +public: + Private() {} + QString accountId; +}; -#include +RemoveAccountJob::RemoveAccountJob(QObject* parent) + : KJob(parent) + , d(new Private) +{ } -class KACCOUNTS_EXPORT AccountServiceToggleJob : public KJob +RemoveAccountJob::~RemoveAccountJob() { - Q_OBJECT - Q_PROPERTY(QString accountId READ accountId WRITE setAccountId NOTIFY accountIdChanged) - Q_PROPERTY(QString serviceId READ serviceId WRITE setServiceId NOTIFY serviceIdChanged) - Q_PROPERTY(bool serviceEnabled READ serviceEnabled WRITE setServiceEnabled NOTIFY serviceEnabledChanged) -public: - explicit AccountServiceToggleJob(QObject* parent = nullptr); - virtual ~AccountServiceToggleJob(); - - void start() override; + delete d; +} - QString accountId() const; - void setAccountId(const QString& accountId); - Q_SIGNAL void accountIdChanged(); +QString RemoveAccountJob::accountId() const +{ + return d->accountId; +} - QString serviceId() const; - void setServiceId(const QString& serviceId); - Q_SIGNAL void serviceIdChanged(); +void RemoveAccountJob::setAccountId(const QString& accountId) +{ + d->accountId = accountId; + Q_EMIT accountIdChanged(); +} - bool serviceEnabled() const; - void setServiceEnabled(bool serviceEnabled); - Q_SIGNAL void serviceEnabledChanged(); -private: - class Private; - Private* d; -}; -#endif//ACCOUNTSERVICETOGGLE_H +void RemoveAccountJob::start() +{ + Accounts::Manager* accountsManager = KAccounts::accountsManager(); + if (accountsManager) { + Accounts::Account *account = accountsManager->account(d->accountId.toInt()); + if (account) { + connect(account, &Accounts::Account::synced, this, [this](){ emitResult(); }); + SignOn::Identity *identity = SignOn::Identity::existingIdentity(account->credentialsId(), this); + if (identity) { + identity->remove(); + identity->deleteLater(); + } + account->remove(); + account->sync(); + } else { + qWarning() << "No account found with the ID" << d->accountId; + emitResult(); + } + } else { + qWarning() << "No accounts manager, this is not awesome."; + emitResult(); + } +} diff --git a/src/lib/servicesmodel.h b/src/lib/servicesmodel.h new file mode 100644 --- /dev/null +++ b/src/lib/servicesmodel.h @@ -0,0 +1,101 @@ +/************************************************************************************* + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef SERVICES_MODEL_H +#define SERVICES_MODEL_H + +#include "kaccounts_export.h" + +#include + +/** + * @brief A model which represents the services in a single account + * + * You can create this manually, but usually you would get an instance of it from the + * AccountsModel::Roles::ServicesRole data role (model.services) of an AccountsModel + * instance. + * + * # Roles + * + * The following role names are available in this model: + * + * * name: The internal name for the service, as understood by the backend + * * description: A (usually single line) description of the service + * * displayName: A human-readable name for the service (use this in the UI instead of name) + * * serviceType: A machine-readable category for the service (e.g. microblogging, e-mail, IM...) + * * providerName: The machine-readable name of the provider which this service is related to + * * iconName: An XDG Icon specification icon name representing this service + * * tags: A list of strings representing tags set on this service + * * enabled: Whether or not the service is enabled for the given account + */ +class KACCOUNTS_EXPORT ServicesModel : public QAbstractListModel +{ + Q_OBJECT + /** + * The Accounts::Account instance which this model should use for fetching the list of services + */ + Q_PROPERTY(QObject* account READ account WRITE setAccount NOTIFY accountChanged) + /** + * The internal ID for the account this model represents + */ + Q_PROPERTY(quint32 accountId READ accountId NOTIFY accountChanged) + /** + * The human-readable name of the account this model represents + */ + Q_PROPERTY(QString accountDisplayName READ accountDisplayName NOTIFY accountChanged) + /** + * The name of the provider this model's account is signed in through + */ + Q_PROPERTY(QString accountProviderName READ accountProviderName NOTIFY accountChanged) + /** + * The XDG Icon specification icon name for this model's account + */ + Q_PROPERTY(QString accountIconName READ accountIconName NOTIFY accountChanged) + public: + enum Roles { + NameRole = Qt::UserRole + 1, + DescriptionRole, + DisplayNameRole, + ServiceTypeRole, + ProviderNameRole, + IconNameRole, + TagsRole, + EnabledRole + }; + explicit ServicesModel(QObject* parent = nullptr); + virtual ~ServicesModel(); + + QHash< int, QByteArray > roleNames() const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + void setAccount(QObject* account); + QObject* account() const; + quint32 accountId() const; + QString accountDisplayName() const; + QString accountProviderName() const; + QString accountIconName() const; + Q_SIGNAL void accountChanged(); + + private: + class Private; + Private *d; +}; + +#endif//SERVICES_MODEL_H diff --git a/src/lib/servicesmodel.cpp b/src/lib/servicesmodel.cpp new file mode 100644 --- /dev/null +++ b/src/lib/servicesmodel.cpp @@ -0,0 +1,191 @@ +/************************************************************************************* + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2020 by Dan Leinir Turthra Jensen * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "servicesmodel.h" + +#include "core.h" + +#include + +#include + +#include + +#include +#include + +class ServicesModel::Private : public QObject +{ +public: + Private(ServicesModel *model) + : q(model) + { }; + virtual ~Private() + { }; + + Accounts::ServiceList services; + Accounts::Account* account{nullptr}; + +private: + ServicesModel* q; +}; + +ServicesModel::ServicesModel(QObject* parent) + : QAbstractListModel(parent) + , d(new ServicesModel::Private(this)) +{ +} + +ServicesModel::~ServicesModel() +{ + delete d; +} + +QHash ServicesModel::roleNames() const +{ + static QHash roles{ + {NameRole, "name"}, + {DescriptionRole, "description"}, + {DisplayNameRole, "displayName"}, + {ServiceTypeRole, "servieType"}, + {ProviderNameRole, "providerName"}, + {IconNameRole, "iconName"}, + {TagsRole, "tags"}, + {EnabledRole, "enabled"} + }; + return roles; +} + +int ServicesModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->services.count(); +} + +QVariant ServicesModel::data(const QModelIndex& index, int role) const +{ + QVariant data; + if(checkIndex(index)) { + const Accounts::Service& service = d->services.value(index.row()); + if (service.isValid()) { + switch (role) { + case NameRole: + data.setValue(service.name()); + break; + case DescriptionRole: + data.setValue(service.description()); + break; + case DisplayNameRole: + data.setValue(service.displayName()); + break; + case ServiceTypeRole: + data.setValue(service.serviceType()); + break; + case ProviderNameRole: + data.setValue(service.provider()); + break; + case IconNameRole: + data.setValue(service.iconName()); + break; + case TagsRole: + data.setValue(service.tags().values()); + break; + case EnabledRole: + data.setValue(d->account->enabledServices().contains(service)); + break; + } + } + } + return data; +} + +void ServicesModel::setAccount(QObject* account) +{ + if (d->account != account) { + beginResetModel(); + d->services.clear(); + if (d->account) { + disconnect(d->account, nullptr, this, nullptr); + } + d->account = qobject_cast(account); + if (d->account) { + connect(d->account, &Accounts::Account::displayNameChanged, this, &ServicesModel::accountChanged); + connect(d->account, &Accounts::Account::enabledChanged, this, [this](const QString& serviceName, bool /*enabled*/){ + int i{0}; + for (const Accounts::Service& service : d->services) { + if (service.name() == serviceName) { + break; + } + ++i; + } + dataChanged(index(i), index(i)); + }); + connect(d->account, &QObject::destroyed, this, [this](){ + beginResetModel(); + d->account = nullptr; + Q_EMIT accountChanged(); + d->services.clear(); + endResetModel(); + }); + d->services = d->account->services(); + } + endResetModel(); + Q_EMIT accountChanged(); + } +} + +QObject * ServicesModel::account() const +{ + return d->account; +} + +quint32 ServicesModel::accountId() const +{ + if (d->account) { + return d->account->id(); + } + return -1; +} + +QString ServicesModel::accountDisplayName() const +{ + if (d->account) { + return d->account->displayName(); + } + return QString{}; +} + +QString ServicesModel::accountProviderName() const +{ + if (d->account) { + return d->account->providerName(); + } + return QString{}; +} + +QString ServicesModel::accountIconName() const +{ + if (d->account && d->account->provider().isValid() && !d->account->provider().iconName().isEmpty()) { + return d->account->provider().iconName(); + } + return QString::fromLatin1("unknown"); +}