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,141 @@ +/* + * 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.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import org.kde.kirigami 2.4 as Kirigami +import org.kde.kcm 1.2 + +import org.kde.kaccounts 1.2 as KAccounts + +SimpleKCM { + id: component; + + title: i18n("Account Details") + + property alias model: servicesList.model + + RemoveAccountDialog { + id: accountRemover + parent: component + accountId: servicesList.model.accountId + displayName: servicesList.model.accountDisplayName + providerName: servicesList.model.accountProviderName + onAccountRemoved: kcm.pop() + } + + RenameAccountDialog { + id: accountRenamer + parent: component + accountId: servicesList.model.accountId + currentDisplayName: servicesList.model.accountDisplayName + } + + Component { + id: serviceToggleJob + KAccounts.AccountServiceToggleJob { } + } + + 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: accountRenamer.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: accountRemover.open(); + } + } + + Kirigami.FormLayout { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + Controls.Label { + visible: servicesList.count === 0 + Layout.fillWidth: true + height: component.height / 3 + enabled: false + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: i18nc("A text shown when an account has no configurable services", "There are no configurable services available for this account. You can still change its display name by clicking the edit icon above.") + } + 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 @@ -18,105 +18,83 @@ * 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 QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 import org.kde.kirigami 2.7 as Kirigami - -import org.kde.kaccounts 1.0 import org.kde.kcm 1.2 -import Ubuntu.OnlineAccounts 0.1 as OA +import org.kde.kaccounts 1.2 as KAccounts 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.displayName = model.displayName; - accountRemovalDlg.providerName = model.providerName; - accountRemovalDlg.open(); + accountRemover.accountId = model.id; + accountRemover.displayName = model.displayName; + accountRemover.providerName = model.providerName; + accountRemover.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 + enabled: false 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.") + text: i18nc("A text shown when a user has not yet added any accounts", "You have not added any accounts yet.\n\nClick on \"Add New Account...\" below to do so.") } } - MessageBoxSheet { - id: accountRemovalDlg + + RemoveAccountDialog { + id: accountRemover parent: kaccountsRoot - property QtObject account - property string displayName - property string providerName - 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: { accountRemovalDlg.account.remove(); } - } - ] } 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 @@ -17,36 +18,75 @@ * 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 QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + import org.kde.kirigami 2.4 as Kirigami -import org.kde.kaccounts 1.0 import org.kde.kcm 1.2 -import Ubuntu.OnlineAccounts 0.1 as OA + +import org.kde.kaccounts 1.2 as KAccounts 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/kcm/package/contents/ui/MessageBoxSheet.qml b/src/kcm/package/contents/ui/MessageBoxSheet.qml --- a/src/kcm/package/contents/ui/MessageBoxSheet.qml +++ b/src/kcm/package/contents/ui/MessageBoxSheet.qml @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import QtQuick 2.7 -import QtQuick.Controls 2.11 as QtControls -import QtQuick.Layouts 1.11 as QtLayouts +import QtQuick 2.12 +import QtQuick.Controls 2.12 as QtControls +import QtQuick.Layouts 1.12 as QtLayouts import org.kde.kirigami 2.7 as Kirigami diff --git a/src/kcm/package/contents/ui/RemoveAccountDialog.qml b/src/kcm/package/contents/ui/RemoveAccountDialog.qml new file mode 100644 --- /dev/null +++ b/src/kcm/package/contents/ui/RemoveAccountDialog.qml @@ -0,0 +1,58 @@ +/* + * 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.12 +import QtQuick.Layouts 1.12 + +import org.kde.kirigami 2.7 as Kirigami + +import org.kde.kaccounts 1.2 as KAccounts + +MessageBoxSheet { + id: component + property int accountId + property string displayName + property string providerName + signal accountRemoved() + title: i18nc("The title for a dialog which lets you remove an account", "Remove Account?") + text: { + if (displayName.length > 0 && 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\"?", providerName, displayName) + } else if (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\"?", 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?", 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": component.accountId }); + job.start(); + } + Component { + id: accountRemovalJob + KAccounts.RemoveAccountJob { + onFinished: component.accountRemoved() + } + } + } + ] +} diff --git a/src/kcm/package/contents/ui/RenameAccountDialog.qml b/src/kcm/package/contents/ui/RenameAccountDialog.qml new file mode 100644 --- /dev/null +++ b/src/kcm/package/contents/ui/RenameAccountDialog.qml @@ -0,0 +1,63 @@ +/* + * 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.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import org.kde.kirigami 2.7 as Kirigami + +import org.kde.kaccounts 1.2 as KAccounts + +MessageBoxSheet { + id: component + title: i18nc("The title for a dialog which lets you set the human-readable name of an account", "Rename Account") + property int accountId + property string currentDisplayName + signal accountRenamed() + onSheetOpenChanged: { + if (sheetOpen === true) { + newAccountDisplayName.text = currentDisplayName; + } + } + 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 && newAccountDisplayName.text !== component.currentDisplayName + 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": component.accountId, "displayName": newAccountDisplayName.text }) + job.start(); + } + Component { + id: accountDisplayNameJob + KAccounts.ChangeAccountDisplayNameJob { + onFinished: component.accountRenamed() + } + } + } + ] +} 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,116 @@ +/************************************************************************************* + * 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 "core.h" + +#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/removeaccountjob.cpp b/src/lib/removeaccountjob.cpp new file mode 100644 --- /dev/null +++ b/src/lib/removeaccountjob.cpp @@ -0,0 +1,81 @@ +/************************************************************************************* + * 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 "removeaccountjob.h" + +#include +#include "core.h" +#include +#include + +class RemoveAccountJob::Private { +public: + Private() {} + QString accountId; +}; + +RemoveAccountJob::RemoveAccountJob(QObject* parent) + : KJob(parent) + , d(new Private) +{ } + +RemoveAccountJob::~RemoveAccountJob() +{ + delete d; +} + +QString RemoveAccountJob::accountId() const +{ + return d->accountId; +} + +void RemoveAccountJob::setAccountId(const QString& accountId) +{ + d->accountId = accountId; + Q_EMIT accountIdChanged(); +} + +void RemoveAccountJob::start() +{ + Accounts::Manager* accountsManager = KAccounts::accountsManager(); + if (accountsManager) { + Accounts::Account *account = accountsManager->account(d->accountId.toInt()); + if (account) { + // We can't depend on Accounts::Account::synced, as that doesn't necessarily get fired when + // asking for the account to be removed... + connect(accountsManager, &Accounts::Manager::accountRemoved, this, [this](Accounts::AccountId id) { + if (id == d->accountId.toUInt()) { + 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"); +}