MauiKit Calendar

attendeesmodel.cpp
1// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#include "attendeesmodel.h"
5#include "kalendar_debug.h"
6#include <KContacts/Addressee>
7#include <KLocalizedString>
8#include <QMetaEnum>
9#include <QModelIndex>
10#include <QRegularExpression>
11
12#include <Akonadi/Item>
13#include <Akonadi/ItemFetchJob>
14#include <Akonadi/ItemFetchScope>
15#include <Akonadi/SearchQuery>
16
17#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
18#include <akonadi_version.h>
19#if AKONADI_VERSION >= QT_VERSION_CHECK(5, 19, 40)
20#include <Akonadi/ContactSearchJob>
21#else
22#include <Akonadi/Contact/ContactSearchJob>
23#endif
24#else
25#include <Akonadi/ContactSearchJob>
26#endif
27
28AttendeeStatusModel::AttendeeStatusModel(QObject *parent)
29 : QAbstractListModel(parent)
30{
31 QRegularExpression lowerToCapitalSep(QStringLiteral("([a-z])([A-Z])"));
32 QRegularExpression capitalToCapitalSep(QStringLiteral("([A-Z])([A-Z])"));
33
34 for (int i = 0; i < QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().keyCount(); i++) {
35 int value = QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().value(i);
36
37 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII.
38 // Regular expression adds space between every lowercase and Capitalised character then does the same
39 // for capitalised letters together, e.g. ThisIsATest. Not a problem right now, but best to be safe.
40 const QLatin1String enumName = QLatin1String(QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().key(i));
41 QString displayName = enumName;
42 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2"));
43 displayName.replace(capitalToCapitalSep, QStringLiteral("\\1 \\2"));
44 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2"));
45
46 m_status[value] = i18n(displayName.toStdString().c_str());
47 }
48}
49
50QVariant AttendeeStatusModel::data(const QModelIndex &idx, int role) const
51{
52 if (!idx.isValid()) {
53 return {};
54 }
55
56 const int value = QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().value(idx.row());
57
58 switch (role) {
59 case DisplayNameRole:
60 return m_status[value];
61 case ValueRole:
62 return value;
63 default:
64 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role);
65 return {};
66 }
67}
68
69QHash<int, QByteArray> AttendeeStatusModel::roleNames() const
70{
71 return {
72 {DisplayNameRole, QByteArrayLiteral("display")},
73 {ValueRole, QByteArrayLiteral("value")},
74 };
75}
76
77int AttendeeStatusModel::rowCount(const QModelIndex &) const
78{
79 return m_status.size();
80}
81
82AttendeesModel::AttendeesModel(QObject *parent, KCalendarCore::Incidence::Ptr incidencePtr)
83 : QAbstractListModel(parent)
84 , m_incidence(incidencePtr)
85 , m_attendeeStatusModel(parent)
86{
87 connect(this, &AttendeesModel::attendeesChanged, this, &AttendeesModel::updateAkonadiContactIds);
88}
89
90KCalendarCore::Incidence::Ptr AttendeesModel::incidencePtr() const
91{
92 return m_incidence;
93}
94
95void AttendeesModel::setIncidencePtr(KCalendarCore::Incidence::Ptr incidence)
96{
97 if (m_incidence == incidence) {
98 return;
99 }
100 m_incidence = incidence;
101
102 Q_EMIT incidencePtrChanged();
103 Q_EMIT attendeesChanged();
104 Q_EMIT attendeeStatusModelChanged();
106}
107
108KCalendarCore::Attendee::List AttendeesModel::attendees() const
109{
110 return m_incidence->attendees();
111}
112
113void AttendeesModel::updateAkonadiContactIds()
114{
115 m_attendeesAkonadiIds.clear();
116
117 const auto attendees = m_incidence->attendees();
118 for (const auto &attendee : attendees) {
119 auto job = new Akonadi::ContactSearchJob();
120 job->setQuery(Akonadi::ContactSearchJob::Email, attendee.email());
121
122 connect(job, &Akonadi::ContactSearchJob::result, this, [this](KJob *job) {
124
125 const auto items = searchJob->items();
126 for (const auto &item : items) {
127 m_attendeesAkonadiIds.append(item.id());
128 }
129
130 Q_EMIT attendeesAkonadiIdsChanged();
131 });
132 }
133
134 Q_EMIT attendeesAkonadiIdsChanged();
135}
136
137AttendeeStatusModel *AttendeesModel::attendeeStatusModel()
138{
139 return &m_attendeeStatusModel;
140}
141
142QList<qint64> AttendeesModel::attendeesAkonadiIds() const
143{
144 return m_attendeesAkonadiIds;
145}
146
147QVariant AttendeesModel::data(const QModelIndex &idx, int role) const
148{
149 if (!hasIndex(idx.row(), idx.column())) {
150 return {};
151 }
152 const auto attendee = m_incidence->attendees().at(idx.row());
153 switch (role) {
154 case CuTypeRole:
155 return attendee.cuType();
156 case DelegateRole:
157 return attendee.delegate();
158 case DelegatorRole:
159 return attendee.delegator();
160 case EmailRole:
161 return attendee.email();
162 case FullNameRole:
163 return attendee.fullName();
164 case IsNullRole:
165 return attendee.isNull();
166 case NameRole:
167 return attendee.name();
168 case RoleRole:
169 return attendee.role();
170 case RSVPRole:
171 return attendee.RSVP();
172 case StatusRole:
173 return attendee.status();
174 case UidRole:
175 return attendee.uid();
176 default:
177 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role);
178 return {};
179 }
180}
181
182bool AttendeesModel::setData(const QModelIndex &idx, const QVariant &value, int role)
183{
184 if (!idx.isValid()) {
185 return false;
186 }
187
188 // When modifying attendees, remember you cannot change them directly from m_incidence->attendees (is a const).
189 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees());
190
191 switch (role) {
192 case CuTypeRole: {
193 const auto cuType = static_cast<KCalendarCore::Attendee::CuType>(value.toInt());
194 currentAttendees[idx.row()].setCuType(cuType);
195 break;
196 }
197 case DelegateRole: {
198 const QString delegate = value.toString();
199 currentAttendees[idx.row()].setDelegate(delegate);
200 break;
201 }
202 case DelegatorRole: {
203 const QString delegator = value.toString();
204 currentAttendees[idx.row()].setDelegator(delegator);
205 break;
206 }
207 case EmailRole: {
208 const QString email = value.toString();
209 currentAttendees[idx.row()].setEmail(email);
210 break;
211 }
212 case FullNameRole: {
213 // Not a writable property
214 return false;
215 }
216 case IsNullRole: {
217 // Not an editable value
218 return false;
219 }
220 case NameRole: {
221 const QString name = value.toString();
222 currentAttendees[idx.row()].setName(name);
223 break;
224 }
225 case RoleRole: {
226 const auto role = static_cast<KCalendarCore::Attendee::Role>(value.toInt());
227 currentAttendees[idx.row()].setRole(role);
228 break;
229 }
230 case RSVPRole: {
231 const bool rsvp = value.toBool();
232 currentAttendees[idx.row()].setRSVP(rsvp);
233 break;
234 }
235 case StatusRole: {
236 const auto status = static_cast<KCalendarCore::Attendee::PartStat>(value.toInt());
237 currentAttendees[idx.row()].setStatus(status);
238 break;
239 }
240 case UidRole: {
241 const QString uid = value.toString();
242 currentAttendees[idx.row()].setUid(uid);
243 break;
244 }
245 default:
246 qCWarning(KALENDAR_LOG) << "Unknown role for incidence:" << QMetaEnum::fromType<Roles>().valueToKey(role);
247 return false;
248 }
249 m_incidence->setAttendees(currentAttendees);
251 return true;
252}
253
254QHash<int, QByteArray> AttendeesModel::roleNames() const
255{
256 return {
257 {CuTypeRole, QByteArrayLiteral("cuType")},
258 {DelegateRole, QByteArrayLiteral("delegate")},
259 {DelegatorRole, QByteArrayLiteral("delegator")},
260 {EmailRole, QByteArrayLiteral("email")},
261 {FullNameRole, QByteArrayLiteral("fullName")},
262 {IsNullRole, QByteArrayLiteral("isNull")},
263 {NameRole, QByteArrayLiteral("name")},
264 {RoleRole, QByteArrayLiteral("role")},
265 {RSVPRole, QByteArrayLiteral("rsvp")},
266 {StatusRole, QByteArrayLiteral("status")},
267 {UidRole, QByteArrayLiteral("uid")},
268 };
269}
270
271int AttendeesModel::rowCount(const QModelIndex &) const
272{
273 return m_incidence->attendeeCount();
274}
275
276void AttendeesModel::addAttendee(qint64 itemId, const QString &email)
277{
278 if (itemId) {
279 Akonadi::Item item(itemId);
280
281 auto job = new Akonadi::ItemFetchJob(item);
282 job->fetchScope().fetchFullPayload();
283
284 connect(job, &Akonadi::ItemFetchJob::result, this, [this, email](KJob *job) {
286 const auto item = fetchJob->items().at(0);
287 const auto payload = item.payload<KContacts::Addressee>();
288
289 KCalendarCore::Attendee attendee(payload.name(),
290 payload.preferredEmail(),
291 true,
294
295 if (!email.isNull()) {
296 attendee.setEmail(email);
297 }
298
299 m_incidence->addAttendee(attendee);
300 // Otherwise won't update
301 Q_EMIT attendeesChanged();
303 });
304 } else {
305 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII
306 // addAttendee method does not work with null strings, so we use empty strings
308 QLatin1String(""),
309 true,
312
313 // addAttendee won't actually add any attendees without a set name
314 m_incidence->addAttendee(attendee);
315 }
316
317 Q_EMIT attendeesChanged();
319}
320
321void AttendeesModel::deleteAttendee(int row)
322{
323 if (!hasIndex(row, 0)) {
324 return;
325 }
326
327 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees());
328 currentAttendees.removeAt(row);
329 m_incidence->setAttendees(currentAttendees);
330
331 Q_EMIT attendeesChanged();
333}
334
335void AttendeesModel::deleteAttendeeFromAkonadiId(qint64 itemId)
336{
337 Akonadi::Item item(itemId);
338
339 auto job = new Akonadi::ItemFetchJob(item);
340 job->fetchScope().fetchFullPayload();
341
342 connect(job, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) {
344
345 auto item = fetchJob->items().at(0);
346 auto payload = item.payload<KContacts::Addressee>();
347
348 for (int i = 0; i < m_incidence->attendeeCount(); i++) {
349 const auto emails = payload.emails();
350 for (const auto &email : emails) {
351 if (m_incidence->attendees()[i].email() == email) {
352 deleteAttendee(i);
353 break;
354 }
355 }
356 }
357 });
358}
QStringList emails() const
void result(KJob *job)
Q_SCRIPTABLE CaptureState status()
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
QString name(StandardAction id)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
bool hasIndex(int row, int column, const QModelIndex &parent) const const
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
qsizetype size() const const
void append(QList< T > &&value)
void clear()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
bool isNull() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
std::string toStdString() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool toBool() const const
int toInt(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:50:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.