Commit eb784415 authored by Carl Schwan's avatar Carl Schwan 🚴
Browse files

Add right click menu to collection and item for address book



Signed-off-by: Carl Schwan's avatarCarl Schwan <carl@carlschwan.eu>
parent fe38cb8a
......@@ -43,6 +43,7 @@ kconfig_add_kcfg_files(kalendar_contact_plugin GENERATE_MOC contactconfig.kcfgc)
ecm_target_qml_sources(kalendar_contact_plugin SOURCES
qml/ContactChooserPage.qml
qml/ContactView.qml
qml/AddressBookCollectionHandler.qml
)
ecm_target_qml_sources(kalendar_contact_plugin
......@@ -56,6 +57,8 @@ ecm_target_qml_sources(kalendar_contact_plugin
qml/private/Header.qml
qml/private/PhoneNumberDialog.qml
qml/private/QrCodePage.qml
qml/private/AddressBookMenu.qml
qml/private/DeleteContactAction.qml
)
ecm_qt_declare_logging_category(kalendar_contact_plugin
......
......@@ -7,13 +7,20 @@
#include <Akonadi/AgentManager>
#include <Akonadi/Collection>
#include <Akonadi/CollectionColorAttribute>
#include <Akonadi/CollectionDeleteJob>
#include <Akonadi/CollectionModifyJob>
#include <Akonadi/CollectionPropertiesDialog>
#include <Akonadi/CollectionStatistics>
#include <Akonadi/CollectionUtils>
#include <Akonadi/ETMViewStateSaver>
#include <Akonadi/EntityMimeTypeFilterModel>
#include <Akonadi/ItemDeleteJob>
#include <Akonadi/ItemFetchJob>
#include <Akonadi/ItemFetchScope>
#include <Akonadi/Monitor>
#include <Akonadi/SelectionProxyModel>
#include <qsortfilterproxymodel.h>
#include <QSortFilterProxyModel>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <akonadi_version.h>
#if AKONADI_VERSION >= QT_VERSION_CHECK(5, 19, 40)
......@@ -32,6 +39,7 @@
#endif
#include "contactcollectionmodel.h"
#include "globalcontactmodel.h"
#include "kalendar_contact_debug.h"
#include <Akonadi/EntityMimeTypeFilterModel>
#include <Akonadi/EntityRightsFilterModel>
#include <KCheckableProxyModel>
......@@ -39,10 +47,12 @@
#include <KContacts/Addressee>
#include <KContacts/ContactGroup>
#include <KDescendantsProxyModel>
#include <KLocalizedString>
#include <KSelectionProxyModel>
#include <KSharedConfig>
#include <QBuffer>
#include <QItemSelectionModel>
#include <QPointer>
#include <colorproxymodel.h>
#include <sortedcollectionproxymodel.h>
......@@ -137,6 +147,11 @@ QUrl ContactManager::decorationToUrl(QVariant decoration)
return QUrl(QLatin1String("data:image/png;base64,") + base64);
}
void ContactManager::deleteItem(const Akonadi::Item &item)
{
new Akonadi::ItemDeleteJob(item);
}
void ContactManager::updateAllCollections()
{
for (int i = 0; i < contactCollections()->rowCount(); i++) {
......@@ -144,3 +159,72 @@ void ContactManager::updateAllCollections()
Akonadi::AgentManager::self()->synchronizeCollection(collection, true);
}
}
void ContactManager::updateCollection(const Akonadi::Collection &collection)
{
Akonadi::AgentManager::self()->synchronizeCollection(collection, false);
}
void ContactManager::deleteCollection(const Akonadi::Collection &collection)
{
const bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
if (!isTopLevel) {
// deletes contents
auto job = new Akonadi::CollectionDeleteJob(collection, this);
connect(job, &Akonadi::CollectionDeleteJob::result, this, [](KJob *job) {
if (job->error()) {
qCWarning(KALENDAR_LOG) << "Error occurred deleting collection: " << job->errorString();
}
});
return;
}
// deletes the agent, not the contents
const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
if (instance.isValid()) {
Akonadi::AgentManager::self()->removeInstance(instance);
}
}
void ContactManager::editCollection(const Akonadi::Collection &collection)
{
// TODO: Reimplement this dialog in QML
QPointer<Akonadi::CollectionPropertiesDialog> dlg = new Akonadi::CollectionPropertiesDialog(collection);
dlg->setWindowTitle(i18nc("@title:window", "Properties of Address Book %1", collection.name()));
dlg->show();
}
QVariantMap ContactManager::getCollectionDetails(const Akonadi::Collection &collection)
{
QVariantMap collectionDetails;
collectionDetails[QLatin1String("id")] = collection.id();
collectionDetails[QLatin1String("name")] = collection.name();
collectionDetails[QLatin1String("displayName")] = collection.displayName();
collectionDetails[QLatin1String("color")] = m_colorProxy->colorCache[QString::number(collection.id())];
collectionDetails[QLatin1String("count")] = collection.statistics().count();
collectionDetails[QLatin1String("isResource")] = Akonadi::CollectionUtils::isResource(collection);
collectionDetails[QLatin1String("resource")] = collection.resource();
collectionDetails[QLatin1String("readOnly")] = collection.rights().testFlag(Akonadi::Collection::ReadOnly);
collectionDetails[QLatin1String("canChange")] = collection.rights().testFlag(Akonadi::Collection::CanChangeCollection);
collectionDetails[QLatin1String("canCreate")] = collection.rights().testFlag(Akonadi::Collection::CanCreateCollection);
collectionDetails[QLatin1String("canDelete")] =
collection.rights().testFlag(Akonadi::Collection::CanDeleteCollection) && !Akonadi::CollectionUtils::isResource(collection);
return collectionDetails;
}
void ContactManager::setCollectionColor(Akonadi::Collection collection, const QColor &color)
{
auto colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>(Akonadi::Collection::AddIfMissing);
colorAttr->setColor(color);
auto modifyJob = new Akonadi::CollectionModifyJob(collection);
connect(modifyJob, &Akonadi::CollectionModifyJob::result, this, [this, collection, color](KJob *job) {
if (job->error()) {
qCWarning(KALENDAR_LOG) << "Error occurred modifying collection color: " << job->errorString();
} else {
m_colorProxy->colorCache[QString::number(collection.id())] = color;
m_colorProxy->save();
}
});
}
......@@ -7,6 +7,7 @@
#include <Akonadi/Item>
#include <QObject>
#include <QSortFilterProxyModel>
#include <qobjectdefs.h>
namespace Akonadi
{
......@@ -33,10 +34,17 @@ public:
QAbstractItemModel *contactCollections() const;
QAbstractItemModel *filteredContacts() const;
Q_INVOKABLE void updateAllCollections();
Q_INVOKABLE QUrl decorationToUrl(QVariant decoration);
Q_INVOKABLE Akonadi::Item getItem(qint64 itemId);
Q_INVOKABLE void setCollectionColor(Akonadi::Collection collection, const QColor &color);
Q_INVOKABLE void deleteItem(const Akonadi::Item &item);
Q_INVOKABLE void updateAllCollections();
Q_INVOKABLE void updateCollection(const Akonadi::Collection &collection);
Q_INVOKABLE void deleteCollection(const Akonadi::Collection &collection);
Q_INVOKABLE void editCollection(const Akonadi::Collection &collection);
Q_INVOKABLE QVariantMap getCollectionDetails(const Akonadi::Collection &collection);
private:
Akonadi::EntityMimeTypeFilterModel *m_collectionTree;
Akonadi::CollectionFilterProxyModel *m_contactViewCollectionModel = nullptr;
......
// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kalendar 1.0 as Kalendar
import org.kde.kalendar.contact 1.0
import './private'
TapHandler {
id: handler
property var collection
property var collectionDetails
acceptedButtons: Kirigami.Settings.isMobile ? Qt.LeftButton | Qt.RightButton : Qt.RightButton
onTapped: addressBookActions.createObject(handler, {}).popup();
onLongPressed: if (Kirigami.Settings.isMobile) {
addressBookActions.createObject(handler, {}).popup();
}
property Loader colorDialogLoader: Loader {
active: false
sourceComponent: ColorDialog {
id: colorDialog
title: i18nc("@title:window", "Choose Address Book Color")
color: handler.collectionDetails.color
onAccepted: ContactManager.setCollectionColor(handler.collection, color)
onRejected: {
close();
colorDialogLoader.active = false;
}
}
}
property Component addressBookActions: Component {
AddressBookMenu {
parent: handler.parent
collection: handler.collection
collectionDetails: handler.collectionDetails
}
}
}
......@@ -2,14 +2,15 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kalendar.contact 1.0
import org.kde.kalendar 1.0
import './private'
Kirigami.ScrollablePage {
id: page
objectName: "contactView"
property int mode: KalendarApplication.Contact
property var attendeeAkonadiIds
......@@ -43,6 +44,7 @@ Kirigami.ScrollablePage {
clip: true
model: ContactManager.filteredContacts
delegate: ContactListItem {
id: contactListItem
height: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
name: model && model.display
avatarIcon: model && model.decoration
......@@ -56,6 +58,49 @@ Kirigami.ScrollablePage {
itemId: model.itemId,
})
}
onCreateContextMenu: createContactListContextMenu(model.item, model.display)
Component {
id: contactListContextMenu
QQC2.Menu {
id: actionsPopup
property var item: null
property var name: ''
QQC2.MenuItem {
icon.name: "edit-entry"
text: i18nc("@action:inmenu", "Edit contact…")
onClicked: if (model.mimeType === 'application/x-vnd.kde.contactgroup') {
const page = applicationWindow().pageStack.push(Qt.resolvedUrl('./private/ContactGroupPage.qml'), {
itemId: model.itemId,
})
page.openEditor();
} else {
const page = applicationWindow().pageStack.push(Qt.resolvedUrl('./private/ContactPage.qml'), {
itemId: model.itemId,
})
page.openEditor();
}
}
QQC2.MenuItem {
icon.name: "delete"
text: i18nc("@action:inmenu", "Delete contact")
action: DeleteContactAction {
item: actionsPopup.item
name: actionsPopup.name
}
}
}
}
function createContactListContextMenu(item, name: string) {
const menu = contactListContextMenu.createObject(page, {
item: item,
name: name,
})
menu.popup()
}
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
......
// SPDX-FileCopyrightText: 2022 Claudio Cambra <claudio.cambra@gmail.com>
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.4
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kalendar 1.0 as Kalendar
import org.kde.kalendar.contact 1.0
QQC2.Menu {
id: actionsPopup
z: 1000
required property var collection
required property var collectionDetails
QQC2.MenuItem {
icon.name: "edit-entry"
text: i18nc("@action:inmenu", "Edit address book…")
onClicked: ContactManager.editCollection(actionsPopup.collection);
}
QQC2.MenuItem {
icon.name: "view-refresh"
text: i18nc("@action:inmenu", "Update address book")
onClicked: ContactManager.updateCollection(actionsPopup.collection);
}
QQC2.MenuItem {
icon.name: "edit-delete"
text: i18nc("@action:inmenu", "Delete address book")
enabled: actionsPopup.collectionDetails.canDelete
onClicked: ContactManager.deleteCollection(actionsPopup.collection)
}
QQC2.MenuSeparator {
}
QQC2.MenuItem {
icon.name: "color-picker"
text: i18nc("@action:inmenu", "Set address book colour…")
onClicked: {
colorDialogLoader.active = true;
colorDialogLoader.item.open();
}
}
QQC2.MenuSeparator {
visible: collectionDetails.isResource
}
QQC2.MenuItem {
icon.name: "settings-configure"
text: i18nc("@action:inmenu", "Address book source settings…")
onClicked: Kalendar.AgentConfiguration.editIdentifier(collectionDetails.resource)
visible: collectionDetails.isResource
}
QQC2.MenuItem {
icon.name: "view-refresh"
text: i18nc("@action:inmenu", "Update address book source")
onClicked: Kalendar.AgentConfiguration.restartIdentifier(collectionDetails.resource)
visible: collectionDetails.isResource
}
QQC2.MenuItem {
icon.name: "edit-delete"
text: i18nc("@action:inmenu", "Delete address source")
onClicked: Kalendar.AgentConfiguration.removeIdentifier(collectionDetails.resource)
visible: collectionDetails.isResource
}
}
......@@ -18,6 +18,13 @@ Kirigami.ScrollablePage {
rightPadding: 0
topPadding: 0
function openEditor() {
pageStack.pushDialogLayer(Qt.resolvedUrl("ContactGroupEditorPage.qml"), {
mode: ContactGroupEditor.EditMode,
item: page.contactGroup.item
})
}
property ContactGroupWrapper contactGroup: ContactGroupWrapper {
id: contactGroup
item: ContactManager.getItem(page.itemId)
......@@ -27,10 +34,7 @@ Kirigami.ScrollablePage {
main: Kirigami.Action {
iconName: "document-edit"
text: i18n("Edit")
onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl("ContactGroupEditorPage.qml"), {
mode: ContactGroupEditor.EditMode,
item: page.contactGroup.item
})
onTriggered: openEditor()
}
}
......
......@@ -6,23 +6,24 @@
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kalendar.contact 1.0
Kirigami.AbstractListItem {
Kirigami.BasicListItem {
id: listItem
property string name
property bool added: false
property var avatarIcon
contentItem: RowLayout {
signal createContextMenu()
contentItem: RowLayout {
Kirigami.Avatar {
id: avatar
Layout.maximumHeight: parent.height
......@@ -46,5 +47,10 @@ Kirigami.AbstractListItem {
source: "checkmark"
visible: added
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: createContextMenu()
}
}
}
......@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kalendar 1.0
......@@ -11,7 +11,7 @@ import org.kde.kalendar.contact 1.0
Kirigami.ScrollablePage {
id: page
property int itemId
title: addressee.name
title: addressee.formattedName
property int mode: KalendarApplication.Contact
leftPadding: 0
......@@ -22,38 +22,35 @@ Kirigami.ScrollablePage {
addresseeItem: ContactManager.getItem(page.itemId)
}
function openEditor() {
pageStack.pushDialogLayer(Qt.resolvedUrl("ContactEditorPage.qml"), {
mode: ContactEditor.EditMode,
item: page.addressee.addresseeItem,
})
}
actions {
main: Kirigami.Action {
iconName: "document-edit"
text: i18n("Edit")
text: i18nc("@action:inmenu", "Edit")
onTriggered: openEditor()
}
contextualActions: DeleteContactAction {
name: page.addressee.formattedName
item: page.addressee.addresseeItem
}
left: Kirigami.Action {
text: i18n("Cancel")
icon.name: "dialog-cancel"
visible: Kirigami.Settings.isMobile
onTriggered: {
pageStack.pushDialogLayer(Qt.resolvedUrl("ContactEditorPage.qml"), {
mode: ContactEditor.EditMode,
item: page.addressee.addresseeItem,
})
pageStack.pop()
}
}
}
// contextualActions: [
// Kirigami.Action {
// iconName: "delete"
// text: i18n("Delete contact")
// onTriggered: {
// KPeople.PersonPluginManager.deleteContact(page.personUri)
// pageStack.pop()
// }
// }
// ]
// left: Kirigami.Action {
// text: i18n("Cancel")
// icon.name: "dialog-cancel"
// visible: Kirigami.Settings.isMobile
// onTriggered: {
// pageStack.pop()
// }
// }
//}
Kirigami.Theme.colorSet: Kirigami.Theme.View
......@@ -88,7 +85,7 @@ Kirigami.ScrollablePage {
}
}
Controls.ToolBar {
QQC2.ToolBar {
Layout.fillWidth: true
contentItem: Kirigami.ActionToolBar {
id: toolbar
......@@ -160,19 +157,19 @@ Kirigami.ScrollablePage {
Kirigami.FormData.label: i18n("Contact information")
}
Controls.Label {
QQC2.Label {
visible: text !== ""
text: addressee.formattedName
Kirigami.FormData.label: i18n("Name:")
}
Controls.Label {
QQC2.Label {
visible: text !== ""
text: addressee.nickName
Kirigami.FormData.label: i18n("Nickname:")
}
Controls.Label {
QQC2.Label {
id: blogFeed
visible: addressee.blogFeed + '' !== ''
// We do not always have the year
......@@ -194,7 +191,7 @@ Kirigami.ScrollablePage {
Kirigami.FormData.label: i18n("Personal information")
}
Controls.Label {
QQC2.Label {
id: birthday
visible: text !== ""
// We do not always have the year
......@@ -206,7 +203,7 @@ Kirigami.ScrollablePage {
Kirigami.FormData.label: i18n("Birthday:")
}
Controls.Label {
QQC2.Label {
id: anniversary
visible: text !== ""
// We do not always have the year
......@@ -218,7 +215,7 @@ Kirigami.ScrollablePage {
Kirigami.FormData.label: i18n("Anniversary:")
}
Controls.Label {
QQC2.Label {
id: spousesName
visible: text !== ""
text: addressee.spousesName
......@@ -240,7 +237,7 @@ Kirigami.ScrollablePage {
Repeater {
id: phoneRepeater
model: addressee.phoneModel
Controls.Label {
QQC2.Label {
visible: text !== ""
text: `<a href="tel:${model.display}">${model.display}</a>`
onLinkActivated: Qt.openUrlExternally(link)
......@@ -272,7 +269,7 @@ Kirigami.ScrollablePage {
Repeater {
id: addressesRepeater
model: addressee.addressesModel
Controls.Label {
QQC2.Label {
visible: text !== ""
text: model.formattedAddress
Kirigami.FormData.label: model.typeLabel ? i18nc("%1 is the type of the address, e.g. home, work, ...", "%1:", model.typeLabel) : i18n("Home:")
......@@ -292,49 +289,49 @@ Kirigami.ScrollablePage {
Kirigami.FormData.label: i18n("Business Information")
}
Controls.Label {
QQC2.Label {
id: organization
visible: text !== ""
text: addressee.organization
Kirigami.FormData.label: i18n("Organization:")
}
Controls.Label {
QQC2.Label {
id: profession
visible: text !== ""
text: addressee.profession
Kirigami.FormData.label: i18n("Profession:")
}
Controls.Label {
QQC2.Label {
id: title
visible: text.trim() !== ""
text: addressee.title