Commit 3dfe51b7 authored by Carl Schwan's avatar Carl Schwan 🚴
Browse files

Add a contact and contact group editor



Signed-off-by: Carl Schwan's avatarCarl Schwan <carl@carlschwan.eu>
parent e9bd1fdc
Pipeline #194213 passed with stage
in 3 minutes and 18 seconds
......@@ -3,6 +3,7 @@
# SPDX-License-Identifier: BSD-2-Clause
add_subdirectory(lib)
add_subdirectory(quick)
add_subdirectory(contacts)
add_executable(kalendar)
......
......@@ -57,6 +57,7 @@
#include <colorproxymodel.h>
#include <incidencewrapper.h>
#include <sortedcollectionproxymodel.h>
using namespace Akonadi;
......@@ -161,29 +162,6 @@ protected:
}
};
class KalendarCollectionFilterProxyModel : public Akonadi::CollectionFilterProxyModel
{
public:
explicit KalendarCollectionFilterProxyModel(QObject *parent = nullptr)
: Akonadi::CollectionFilterProxyModel(parent)
{
}
protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
{
const auto leftHasChildren = sourceModel()->hasChildren(source_left);
const auto rightHasChildren = sourceModel()->hasChildren(source_right);
if (leftHasChildren && !rightHasChildren) {
return false;
} else if (!leftHasChildren && rightHasChildren) {
return true;
}
return Akonadi::CollectionFilterProxyModel::lessThan(source_left, source_right);
}
};
Q_GLOBAL_STATIC(CalendarManager, calendarManagerGlobalInstance)
CalendarManager *CalendarManager::instance()
......@@ -253,28 +231,8 @@ CalendarManager::CalendarManager(QObject *parent)
m_todoRightsFilterModel->setAccessRights(Collection::CanCreateItem);
m_todoRightsFilterModel->setSourceModel(m_todoMimeTypeFilterModel);
// Use our custom class to order them properly
m_selectableCollectionsModel = new KalendarCollectionFilterProxyModel(this);
m_selectableCollectionsModel->setSourceModel(m_allCollectionsRightsFilterModel);
m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
m_selectableCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_selectableCollectionsModel->sort(0, Qt::AscendingOrder);
m_selectableEventCollectionsModel = new KalendarCollectionFilterProxyModel(this);
m_selectableEventCollectionsModel->setSourceModel(m_eventRightsFilterModel);
m_selectableEventCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
m_selectableEventCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_selectableEventCollectionsModel->sort(0, Qt::AscendingOrder);
m_selectableTodoCollectionsModel = new KalendarCollectionFilterProxyModel(this);
m_selectableTodoCollectionsModel->setSourceModel(m_todoRightsFilterModel);
m_selectableTodoCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
m_selectableTodoCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_selectableTodoCollectionsModel->sort(0, Qt::AscendingOrder);
// Model for todo via collection picker
m_todoViewCollectionModel = new KalendarCollectionFilterProxyModel(this);
m_todoViewCollectionModel = new SortedCollectionProxModel(this);
m_todoViewCollectionModel->setSourceModel(collectionFilter);
m_todoViewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
m_todoViewCollectionModel->setExcludeVirtualCollections(true);
......@@ -282,7 +240,7 @@ CalendarManager::CalendarManager(QObject *parent)
m_todoViewCollectionModel->sort(0, Qt::AscendingOrder);
// Model for the mainDrawer
m_viewCollectionModel = new KalendarCollectionFilterProxyModel(this);
m_viewCollectionModel = new SortedCollectionProxModel(this);
m_viewCollectionModel->setSourceModel(collectionFilter);
m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
......@@ -410,21 +368,6 @@ Akonadi::CollectionFilterProxyModel *CalendarManager::allCalendars()
return m_allCalendars;
}
Akonadi::CollectionFilterProxyModel *CalendarManager::selectableCalendars() const
{
return m_selectableCollectionsModel;
}
Akonadi::CollectionFilterProxyModel *CalendarManager::selectableEventCalendars() const
{
return m_selectableEventCollectionsModel;
}
Akonadi::CollectionFilterProxyModel *CalendarManager::selectableTodoCalendars() const
{
return m_selectableTodoCollectionsModel;
}
qint64 CalendarManager::defaultCalendarId(IncidenceWrapper *incidenceWrapper)
{
// Checks if default collection accepts this type of incidence
......@@ -452,33 +395,6 @@ qint64 CalendarManager::defaultCalendarId(IncidenceWrapper *incidenceWrapper)
return -1;
}
int CalendarManager::getCalendarSelectableIndex(IncidenceWrapper *incidenceWrapper)
{
auto model = new KDescendantsProxyModel;
switch (incidenceWrapper->incidencePtr()->type()) {
default:
case (KCalendarCore::IncidenceBase::TypeEvent): {
model->setSourceModel(m_selectableEventCollectionsModel);
break;
}
case (KCalendarCore::IncidenceBase::TypeTodo): {
model->setSourceModel(m_selectableTodoCollectionsModel);
break;
}
}
for (int i = 0; i < model->rowCount(); i++) {
QModelIndex idx = model->index(i, 0);
QVariant data = idx.data(Akonadi::EntityTreeModel::Roles::CollectionIdRole);
if (data == incidenceWrapper->collectionId())
return i;
}
return 0;
}
QVariant CalendarManager::getIncidenceSubclassed(KCalendarCore::Incidence::Ptr incidencePtr)
{
switch (incidencePtr->type()) {
......
......@@ -43,9 +43,6 @@ class CalendarManager : public QObject
Q_PROPERTY(QAbstractItemModel *viewCollections READ viewCollections CONSTANT)
Q_PROPERTY(QVector<qint64> enabledTodoCollections READ enabledTodoCollections NOTIFY enabledTodoCollectionsChanged)
Q_PROPERTY(Akonadi::CollectionFilterProxyModel *allCalendars READ allCalendars CONSTANT)
Q_PROPERTY(Akonadi::CollectionFilterProxyModel *selectableCalendars READ selectableCalendars CONSTANT)
Q_PROPERTY(Akonadi::CollectionFilterProxyModel *selectableEventCalendars READ selectableEventCalendars CONSTANT)
Q_PROPERTY(Akonadi::CollectionFilterProxyModel *selectableTodoCalendars READ selectableTodoCalendars CONSTANT)
Q_PROPERTY(Akonadi::ETMCalendar::Ptr calendar READ calendar CONSTANT)
Q_PROPERTY(Akonadi::IncidenceChanger *incidenceChanger READ incidenceChanger CONSTANT)
Q_PROPERTY(QVariantMap undoRedoData READ undoRedoData NOTIFY undoRedoDataChanged)
......@@ -69,11 +66,7 @@ public:
Akonadi::ETMCalendar::Ptr calendar() const;
Akonadi::IncidenceChanger *incidenceChanger() const;
Akonadi::CollectionFilterProxyModel *allCalendars();
Akonadi::CollectionFilterProxyModel *selectableCalendars() const;
Akonadi::CollectionFilterProxyModel *selectableEventCalendars() const;
Akonadi::CollectionFilterProxyModel *selectableTodoCalendars() const;
Q_INVOKABLE qint64 defaultCalendarId(IncidenceWrapper *incidenceWrapper);
Q_INVOKABLE int getCalendarSelectableIndex(IncidenceWrapper *incidenceWrapper);
QVariantMap undoRedoData();
Q_INVOKABLE Akonadi::Item incidenceItem(KCalendarCore::Incidence::Ptr incidence) const;
......@@ -129,9 +122,6 @@ private:
Akonadi::EntityRightsFilterModel *m_allCollectionsRightsFilterModel = nullptr;
Akonadi::EntityRightsFilterModel *m_eventRightsFilterModel = nullptr;
Akonadi::EntityRightsFilterModel *m_todoRightsFilterModel = nullptr;
Akonadi::CollectionFilterProxyModel *m_selectableCollectionsModel = nullptr;
Akonadi::CollectionFilterProxyModel *m_selectableEventCollectionsModel = nullptr;
Akonadi::CollectionFilterProxyModel *m_selectableTodoCollectionsModel = nullptr;
Akonadi::CollectionFilterProxyModel *m_todoViewCollectionModel = nullptr;
Akonadi::CollectionFilterProxyModel *m_viewCollectionModel = nullptr;
QVector<qint64> m_enabledTodoCollections;
......
......@@ -20,9 +20,26 @@ target_sources(kalendar_contact_plugin PRIVATE
contactmanager.cpp
contactcollectionmodel.cpp
contactcollectionmodel.h
contacteditorbackend.h
contacteditorbackend.cpp
contactgroupeditor.h
contactgroupeditor.cpp
contactgroupwrapper.h
contactgroupwrapper.cpp
contactgroupmodel.h
contactgroupmodel.cpp
contactmetadata.cpp
contactmetadata.h
contactsmodel.cpp
contactsmodel.h
attributes/contactmetadataattribute_p.h
attributes/contactmetadataattribute.cpp
attributes/attributeregistrar.cpp
resources.qrc
)
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
......@@ -30,9 +47,12 @@ ecm_target_qml_sources(kalendar_contact_plugin SOURCES
ecm_target_qml_sources(kalendar_contact_plugin
PRIVATE PATH private SOURCES
qml/private/ContactListItem.qml
qml/private/ContactPage.qml
qml/private/ContactEditorPage.qml
qml/private/ContactGroupPage.qml
qml/private/ContactGroupEditorPage.qml
qml/private/ContactsPage.qml
qml/private/ContactListItem.qml
qml/private/Header.qml
qml/private/PhoneNumberDialog.qml
qml/private/QrCodePage.qml
......
......@@ -7,6 +7,8 @@
#include <KLocalizedString>
#include <QBitArray>
#include <QJSValue>
#include <kcontacts/addressee.h>
#include <qobjectdefs.h>
AddresseeWrapper::AddresseeWrapper(QObject *parent)
: QObject(parent)
......@@ -21,14 +23,27 @@ AddresseeWrapper::AddresseeWrapper(QObject *parent)
scope.setFetchRelations(true);
scope.setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
setFetchScope(scope);
connect(m_emailModel, &EmailModel::changed, this, [this](const KContacts::Email::List &emails) {
m_addressee.setEmailList(emails);
});
connect(m_phoneModel, &PhoneModel::changed, this, [this](const KContacts::PhoneNumber::List &phoneNumbers) {
m_addressee.setPhoneNumbers(phoneNumbers);
});
}
AddresseeWrapper::~AddresseeWrapper() = default;
void AddresseeWrapper::notifyDataChanged()
{
Q_EMIT collectionIdChanged();
Q_EMIT nameChanged();
Q_EMIT collectionChanged();
Q_EMIT formattedNameChanged();
Q_EMIT additionalNameChanged();
Q_EMIT familyNameChanged();
Q_EMIT givenNameChanged();
Q_EMIT prefixChanged();
Q_EMIT suffixChanged();
Q_EMIT birthdayChanged();
Q_EMIT photoChanged();
Q_EMIT phoneNumbersChanged();
......@@ -60,11 +75,11 @@ AddressModel *AddresseeWrapper::addressesModel() const
void AddresseeWrapper::setAddresseeItem(const Akonadi::Item &addresseeItem)
{
Akonadi::ItemMonitor::setItem(addresseeItem);
if (addresseeItem.hasPayload<KContacts::Addressee>()) {
setItem(addresseeItem);
setAddressee(addresseeItem.payload<KContacts::Addressee>());
Q_EMIT addresseeItemChanged();
Q_EMIT collectionIdChanged();
Q_EMIT collectionChanged();
} else {
// Payload not found, try to fetch it
auto job = new Akonadi::ItemFetchJob(addresseeItem);
......@@ -73,10 +88,9 @@ void AddresseeWrapper::setAddresseeItem(const Akonadi::Item &addresseeItem)
auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
auto item = fetchJob->items().at(0);
if (item.hasPayload<KContacts::Addressee>()) {
setItem(item);
setAddressee(item.payload<KContacts::Addressee>());
Q_EMIT addresseeItemChanged();
Q_EMIT collectionIdChanged();
Q_EMIT collectionChanged();
} else {
qCWarning(KALENDAR_LOG) << "This is not an addressee item.";
}
......@@ -89,12 +103,17 @@ void AddresseeWrapper::itemChanged(const Akonadi::Item &item)
setAddressee(item.payload<KContacts::Addressee>());
}
KContacts::Addressee AddresseeWrapper::addressee() const
{
return m_addressee;
}
void AddresseeWrapper::setAddressee(const KContacts::Addressee &addressee)
{
m_addressee = addressee;
m_addressesModel->setAddresses(addressee.addresses());
m_emailModel->setEmails(addressee.emailList());
m_phoneModel->setPhoneNumbers(addressee.phoneNumbers());
m_emailModel->loadContact(addressee);
m_phoneModel->loadContact(addressee);
notifyDataChanged();
}
......@@ -103,29 +122,39 @@ QString AddresseeWrapper::uid() const
return m_addressee.uid();
}
Akonadi::Collection AddresseeWrapper::collection() const
{
return m_collection.isValid() ? m_collection : item().parentCollection();
}
qint64 AddresseeWrapper::collectionId() const
{
return m_collectionId < 0 ? item().parentCollection().id() : m_collectionId;
return collection().id();
}
void AddresseeWrapper::setCollectionId(qint64 collectionId)
void AddresseeWrapper::setCollection(Akonadi::Collection collection)
{
m_collectionId = collectionId;
Q_EMIT collectionIdChanged();
m_collection = collection;
Q_EMIT collectionChanged();
}
QString AddresseeWrapper::name() const
QString AddresseeWrapper::formattedName() const
{
return m_addressee.formattedName();
}
void AddresseeWrapper::setName(const QString &name)
void AddresseeWrapper::setFormattedName(const QString &name)
{
if (name == m_addressee.formattedName()) {
return;
}
m_addressee.setFormattedName(name);
Q_EMIT nameChanged();
m_addressee.setNameFromString(name);
Q_EMIT formattedNameChanged();
Q_EMIT givenNameChanged();
Q_EMIT familyNameChanged();
Q_EMIT suffixChanged();
Q_EMIT prefixChanged();
Q_EMIT additionalNameChanged();
}
QDateTime AddresseeWrapper::birthday() const
......@@ -343,3 +372,92 @@ void AddresseeWrapper::setBlogFeed(const QUrl &blogFeed)
m_addressee.setBlogFeed(blogFeed);
Q_EMIT blogFeedChanged();
}
AddresseeWrapper::DisplayType AddresseeWrapper::displayType() const
{
return m_displayType;
}
void AddresseeWrapper::setDisplayType(AddresseeWrapper::DisplayType displayType)
{
if (m_displayType == displayType) {
return;
}
m_displayType = displayType;
Q_EMIT displayTypeChanged();
}
QString AddresseeWrapper::additionalName() const
{
return m_addressee.additionalName();
}
void AddresseeWrapper::setAdditionalName(const QString &name)
{
if (name == m_addressee.additionalName()) {
return;
}
m_addressee.setAdditionalName(name);
setFormattedName(m_addressee.assembledName());
Q_EMIT additionalNameChanged();
}
QString AddresseeWrapper::givenName() const
{
return m_addressee.givenName();
}
void AddresseeWrapper::setGivenName(const QString &name)
{
if (name == m_addressee.givenName()) {
return;
}
m_addressee.setGivenName(name);
setFormattedName(m_addressee.assembledName());
Q_EMIT givenNameChanged();
}
QString AddresseeWrapper::familyName() const
{
return m_addressee.familyName();
}
void AddresseeWrapper::setFamilyName(const QString &name)
{
if (name == m_addressee.familyName()) {
return;
}
m_addressee.setFamilyName(name);
setFormattedName(m_addressee.assembledName());
Q_EMIT familyNameChanged();
}
QString AddresseeWrapper::prefix() const
{
return m_addressee.prefix();
}
void AddresseeWrapper::setPrefix(const QString &name)
{
if (name == m_addressee.prefix()) {
return;
}
m_addressee.setPrefix(name);
setFormattedName(m_addressee.assembledName());
Q_EMIT prefixChanged();
}
QString AddresseeWrapper::suffix() const
{
return m_addressee.suffix();
}
void AddresseeWrapper::setSuffix(const QString &name)
{
if (name == m_addressee.suffix()) {
return;
}
m_addressee.setSuffix(name);
setFormattedName(m_addressee.assembledName());
Q_EMIT suffixChanged();
}
......@@ -11,6 +11,8 @@
#include <Akonadi/ItemMonitor>
#include <KContacts/Addressee>
#include <QObject>
#include <kcontacts/addressee.h>
#include <qobjectdefs.h>
#include "addressmodel.h"
......@@ -21,12 +23,20 @@
class AddresseeWrapper : public QObject, public Akonadi::ItemMonitor
{
Q_OBJECT
// Akonadi properties
Q_PROPERTY(Akonadi::Item addresseeItem READ addresseeItem WRITE setAddresseeItem NOTIFY addresseeItemChanged)
Q_PROPERTY(Akonadi::Collection collection READ collection WRITE setCollection NOTIFY collectionChanged)
Q_PROPERTY(qint64 collectionId READ collectionId NOTIFY collectionChanged)
Q_PROPERTY(QString uid READ uid NOTIFY uidChanged)
Q_PROPERTY(qint64 collectionId READ collectionId WRITE setCollectionId NOTIFY collectionIdChanged)
// Contact information
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString formattedName READ formattedName WRITE setFormattedName NOTIFY formattedNameChanged)
Q_PROPERTY(QString additionalName READ additionalName WRITE setAdditionalName NOTIFY additionalNameChanged)
Q_PROPERTY(QString familyName READ familyName WRITE setFamilyName NOTIFY familyNameChanged)
Q_PROPERTY(QString givenName READ givenName WRITE setGivenName NOTIFY givenNameChanged)
Q_PROPERTY(QString prefix READ prefix WRITE setPrefix NOTIFY prefixChanged)
Q_PROPERTY(QString suffix READ suffix WRITE setSuffix NOTIFY suffixChanged)
Q_PROPERTY(QString nickName READ nickName WRITE setNickName NOTIFY nickNameChanged)
Q_PROPERTY(QUrl blogFeed READ blogFeed WRITE setBlogFeed NOTIFY blogFeedChanged)
Q_PROPERTY(QString preferredEmail READ preferredEmail NOTIFY preferredEmailChanged)
......@@ -55,19 +65,41 @@ class AddresseeWrapper : public QObject, public Akonadi::ItemMonitor
Q_PROPERTY(QString note READ note WRITE setNote NOTIFY noteChanged)
Q_PROPERTY(KContacts::Picture photo READ photo NOTIFY photoChanged)
Q_PROPERTY(DisplayType displayType READ displayType WRITE setDisplayType NOTIFY displayTypeChanged)
public:
/**
* Describes what the display name should look like.
*/
enum DisplayType {
SimpleName, ///< A name of the form: givenName familyName
FullName, ///< A name of the form: prefix givenName additionalName familyName suffix
ReverseNameWithComma, ///< A name of the form: familyName, givenName
ReverseName, ///< A name of the form: familyName givenName
Organization, ///< The organization name
CustomName ///< Let the user input a display name
};
Q_ENUM(DisplayType);
AddresseeWrapper(QObject *parent = nullptr);
~AddresseeWrapper() override;
Akonadi::Item addresseeItem() const;
void setAddresseeItem(const Akonadi::Item &item);
KContacts::Addressee addressee() const;
void setAddressee(const KContacts::Addressee &addressee);
QString uid() const;
Akonadi::Collection collection() const;
qint64 collectionId() const;
void setCollectionId(qint64 collectionId);
void setCollection(Akonadi::Collection collection);
DisplayType displayType() const;
void setDisplayType(DisplayType displayType);
QString name() const;
void setName(const QString &name);
QString formattedName() const;
void setFormattedName(const QString &formattedName);
QString nickName() const;
void setNickName(const QString &nickName);
......@@ -81,7 +113,6 @@ public:
QString preferredEmail() const;
KContacts::Picture photo() const;
void setAddressee(const KContacts::Addressee &addressee);
AddressModel *addressesModel() const;
EmailModel *emailModel() const;
......@@ -118,6 +149,21 @@ public:
void setSpousesName(const QString &spousesName);
QString spousesName() const;
QString additionalName() const;
void setAdditionalName(const QString &additionalName);
QString familyName() const;
void setFamilyName(const QString &familyName);
QString givenName() const;
void setGivenName(const QString &givenName);
QString prefix() const;
void setPrefix(const QString &prefix);
QString suffix() const;
void setSuffix(const QString &suffix);
// Invokable since we don't want expensive data bindings when any of the
// fields change, instead generate it on demand
Q_INVOKABLE QString qrCodeData() const;
......@@ -125,8 +171,8 @@ public:
void notifyDataChanged();
Q_SIGNALS:
void addresseeItemChanged();
void collectionIdChanged();
void nameChanged();
void collectionChanged();
void formattedNameChanged();
void birthdayChanged();
void photoChanged();
void phoneNumbersChanged();
......@@ -135,6 +181,11 @@ Q_SIGNALS:
void noteChanged();
void nickNameChanged();
void blogFeedChanged();
void additionalNameChanged();
void familyNameChanged();
void givenNameChanged();
void prefixChanged();
void suffixChanged();
void anniversaryChanged();
void spousesNameChanged();
......@@ -146,12 +197,14 @@ Q_SIGNALS:
void officeChanged();
void managersNameChanged();
void assistantsNameChanged();
void displayTypeChanged();
private:
void itemChanged(const Akonadi::Item &item) override;
KContacts::Addressee m_addressee;
qint64 m_collectionId = -1; // For when we want to edit, this is temporary
Akonadi::Collection m_collection; // For when we want to edit, this is temporary
AddressModel *m_addressesModel;
EmailModel *m_emailModel;
PhoneModel *m_phoneModel;
DisplayType m_displayType;
};
......@@ -156,21 +156,11 @@ PlasmaComponents3.ScrollView {
source: addressee.photo.isIntern ? addressee.photo.data : addressee.photo.url
backgroundSource: Qt.resolvedUrl("../resources/fallbackBackground.png")
contentItems: [
PlasmaExtras.Heading {
text: addressee.name
color: "#fcfcfc"
level: 2
},
Repeater {
model: addressee.phoneNumbers
PlasmaExtras.Heading {
text: modelData.normalizedNumber
color: "#fcfcfc"
level: 3
}
}
]
contentItems: PlasmaExtras.Heading {
text: addressee.formattedName
color: "#fcfcfc"
level: 2
}
}
PlasmaComponents3.Label {
......@@ -234,6 +224,36 @@ PlasmaComponents3.ScrollView {
}
}
}
PlasmaExtras.Heading {
Layout.topMargin: PlasmaCore.Units.smallSpacing