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#include <Akonadi/ContactSearchJob>
18
19AttendeeStatusModel::AttendeeStatusModel(QObject *parent)
20 : QAbstractListModel(parent)
21{
22 QRegularExpression lowerToCapitalSep(QStringLiteral("([a-z])([A-Z])"));
23 QRegularExpression capitalToCapitalSep(QStringLiteral("([A-Z])([A-Z])"));
24
25 for (int i = 0; i < QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().keyCount(); i++) {
27
28 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII.
29 // Regular expression adds space between every lowercase and Capitalised character then does the same
30 // for capitalised letters together, e.g. ThisIsATest. Not a problem right now, but best to be safe.
32 QString displayName = enumName;
33 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2"));
34 displayName.replace(capitalToCapitalSep, QStringLiteral("\\1 \\2"));
35 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2"));
36
37 m_status[value] = i18n(displayName.toStdString().c_str());
38 }
39}
40
41QVariant AttendeeStatusModel::data(const QModelIndex &idx, int role) const
42{
43 if (!idx.isValid()) {
44 return {};
45 }
46
48
49 switch (role) {
50 case DisplayNameRole:
51 return m_status[value];
52 case ValueRole:
53 return value;
54 default:
55 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role);
56 return {};
57 }
58}
59
60QHash<int, QByteArray> AttendeeStatusModel::roleNames() const
61{
62 return {{DisplayNameRole, QByteArrayLiteral("display")},
63 {ValueRole, QByteArrayLiteral("value")},
64 };
65}
66
67int AttendeeStatusModel::rowCount(const QModelIndex &) const
68{
69 return m_status.size();
70}
71
72AttendeesModel::AttendeesModel(QObject *parent, KCalendarCore::Incidence::Ptr incidencePtr)
73 : QAbstractListModel(parent)
74 , m_incidence(incidencePtr)
75 , m_attendeeStatusModel(parent)
76{
77 connect(this, &AttendeesModel::attendeesChanged, this, &AttendeesModel::updateAkonadiContactIds);
78}
79
80KCalendarCore::Incidence::Ptr AttendeesModel::incidencePtr() const
81{
82 return m_incidence;
83}
84
85void AttendeesModel::setIncidencePtr(KCalendarCore::Incidence::Ptr incidence)
86{
87 if (m_incidence == incidence) {
88 return;
89 }
90 m_incidence = incidence;
91
92 Q_EMIT incidencePtrChanged();
93 Q_EMIT attendeesChanged();
94 Q_EMIT attendeeStatusModelChanged();
96}
97
98KCalendarCore::Attendee::List AttendeesModel::attendees() const
99{
100 return m_incidence->attendees();
101}
102
103void AttendeesModel::updateAkonadiContactIds()
104{
105 m_attendeesAkonadiIds.clear();
106
107 const auto attendees = m_incidence->attendees();
108 for (const auto &attendee : attendees) {
109 auto job = new Akonadi::ContactSearchJob();
110 job->setQuery(Akonadi::ContactSearchJob::Email, attendee.email());
111
112 connect(job, &Akonadi::ContactSearchJob::result, this, [this](KJob *job) {
113 auto searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
114
115 const auto items = searchJob->items();
116 for (const auto &item : items) {
117 m_attendeesAkonadiIds.append(item.id());
118 }
119
120 Q_EMIT attendeesAkonadiIdsChanged();
121 });
122 }
123
124 Q_EMIT attendeesAkonadiIdsChanged();
125}
126
127AttendeeStatusModel *AttendeesModel::attendeeStatusModel()
128{
129 return &m_attendeeStatusModel;
130}
131
132QList<qint64> AttendeesModel::attendeesAkonadiIds() const
133{
134 return m_attendeesAkonadiIds;
135}
136
137QVariant AttendeesModel::data(const QModelIndex &idx, int role) const
138{
139 if (!hasIndex(idx.row(), idx.column())) {
140 return {};
141 }
142 const auto attendee = m_incidence->attendees().at(idx.row());
143 switch (role) {
144 case CuTypeRole:
145 return attendee.cuType();
146 case DelegateRole:
147 return attendee.delegate();
148 case DelegatorRole:
149 return attendee.delegator();
150 case EmailRole:
151 return attendee.email();
152 case FullNameRole:
153 return attendee.fullName();
154 case IsNullRole:
155 return attendee.isNull();
156 case NameRole:
157 return attendee.name();
158 case RoleRole:
159 return attendee.role();
160 case RSVPRole:
161 return attendee.RSVP();
162 case StatusRole:
163 return attendee.status();
164 case UidRole:
165 return attendee.uid();
166 default:
167 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role);
168 return {};
169 }
170}
171
172bool AttendeesModel::setData(const QModelIndex &idx, const QVariant &value, int role)
173{
174 if (!idx.isValid()) {
175 return false;
176 }
177
178 // When modifying attendees, remember you cannot change them directly from m_incidence->attendees (is a const).
179 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees());
180
181 switch (role) {
182 case CuTypeRole: {
183 const auto cuType = static_cast<KCalendarCore::Attendee::CuType>(value.toInt());
184 currentAttendees[idx.row()].setCuType(cuType);
185 break;
186 }
187 case DelegateRole: {
188 const QString delegate = value.toString();
189 currentAttendees[idx.row()].setDelegate(delegate);
190 break;
191 }
192 case DelegatorRole: {
193 const QString delegator = value.toString();
194 currentAttendees[idx.row()].setDelegator(delegator);
195 break;
196 }
197 case EmailRole: {
198 const QString email = value.toString();
199 currentAttendees[idx.row()].setEmail(email);
200 break;
201 }
202 case FullNameRole: {
203 // Not a writable property
204 return false;
205 }
206 case IsNullRole: {
207 // Not an editable value
208 return false;
209 }
210 case NameRole: {
211 const QString name = value.toString();
212 currentAttendees[idx.row()].setName(name);
213 break;
214 }
215 case RoleRole: {
216 const auto role = static_cast<KCalendarCore::Attendee::Role>(value.toInt());
217 currentAttendees[idx.row()].setRole(role);
218 break;
219 }
220 case RSVPRole: {
221 const bool rsvp = value.toBool();
222 currentAttendees[idx.row()].setRSVP(rsvp);
223 break;
224 }
225 case StatusRole: {
226 const auto status = static_cast<KCalendarCore::Attendee::PartStat>(value.toInt());
227 currentAttendees[idx.row()].setStatus(status);
228 break;
229 }
230 case UidRole: {
231 const QString uid = value.toString();
232 currentAttendees[idx.row()].setUid(uid);
233 break;
234 }
235 default:
236 qCWarning(KALENDAR_LOG) << "Unknown role for incidence:" << QMetaEnum::fromType<Roles>().valueToKey(role);
237 return false;
238 }
239 m_incidence->setAttendees(currentAttendees);
240 Q_EMIT dataChanged(idx, idx);
241 return true;
242}
243
244QHash<int, QByteArray> AttendeesModel::roleNames() const
245{
246 return {
247 {CuTypeRole, QByteArrayLiteral("cuType")},
248 {DelegateRole, QByteArrayLiteral("delegate")},
249 {DelegatorRole, QByteArrayLiteral("delegator")},
250 {EmailRole, QByteArrayLiteral("email")},
251 {FullNameRole, QByteArrayLiteral("fullName")},
252 {IsNullRole, QByteArrayLiteral("isNull")},
253 {NameRole, QByteArrayLiteral("name")},
254 {RoleRole, QByteArrayLiteral("role")},
255 {RSVPRole, QByteArrayLiteral("rsvp")},
256 {StatusRole, QByteArrayLiteral("status")},
257 {UidRole, QByteArrayLiteral("uid")},
258 };
259}
260
261int AttendeesModel::rowCount(const QModelIndex &) const
262{
263 return m_incidence->attendeeCount();
264}
265
266void AttendeesModel::addAttendee(qint64 itemId, const QString &email)
267{
268 if (itemId) {
269 Akonadi::Item item(itemId);
270
271 auto job = new Akonadi::ItemFetchJob(item);
272 job->fetchScope().fetchFullPayload();
273
274 connect(job, &Akonadi::ItemFetchJob::result, this, [this, email](KJob *job) {
276 const auto item = fetchJob->items().at(0);
277 const auto payload = item.payload<KContacts::Addressee>();
278
279 KCalendarCore::Attendee attendee(payload.name(),
280 payload.preferredEmail(),
281 true,
284
285 if (!email.isNull()) {
286 attendee.setEmail(email);
287 }
288
289 m_incidence->addAttendee(attendee);
290 // Otherwise won't update
291 Q_EMIT attendeesChanged();
293 });
294 } else {
295 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII
296 // addAttendee method does not work with null strings, so we use empty strings
298 QLatin1String(""),
299 true,
302
303 // addAttendee won't actually add any attendees without a set name
304 m_incidence->addAttendee(attendee);
305 }
306
307 Q_EMIT attendeesChanged();
309}
310
311void AttendeesModel::deleteAttendee(int row)
312{
313 if (!hasIndex(row, 0)) {
314 return;
315 }
316
317 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees());
318 currentAttendees.removeAt(row);
319 m_incidence->setAttendees(currentAttendees);
320
321 Q_EMIT attendeesChanged();
323}
324
325void AttendeesModel::deleteAttendeeFromAkonadiId(qint64 itemId)
326{
327 Akonadi::Item item(itemId);
328
329 auto job = new Akonadi::ItemFetchJob(item);
330 job->fetchScope().fetchFullPayload();
331
332 connect(job, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) {
333 auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
334
335 auto item = fetchJob->items().at(0);
336 auto payload = item.payload<KContacts::Addressee>();
337
338 for (int i = 0; i < m_incidence->attendeeCount(); i++) {
339 const auto emails = payload.emails();
340 for (const auto &email : emails) {
341 if (m_incidence->attendees()[i].email() == email) {
342 deleteAttendee(i);
343 break;
344 }
345 }
346 }
347 });
348}
Item::List items() const
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)
const_reference at(qsizetype i) const const
void clear()
QMetaEnum fromType()
int value(int index) const const
const char * valueToKey(int value) const const
int column() const const
bool isValid() const const
int row() const const
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 Oct 11 2024 12:08:19 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.