File indexing completed on 2024-06-23 04:42:35
0001 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com> 0002 // SPDX-License-Identifier: LGPL-2.1-or-later 0003 0004 #include "attendeesmodel.h" 0005 #include "kalendar_debug.h" 0006 #include <KContacts/Addressee> 0007 #include <KLocalizedString> 0008 #include <QMetaEnum> 0009 #include <QModelIndex> 0010 #include <QRegularExpression> 0011 0012 #include <Akonadi/Item> 0013 #include <Akonadi/ItemFetchJob> 0014 #include <Akonadi/ItemFetchScope> 0015 #include <Akonadi/SearchQuery> 0016 0017 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0018 #include <akonadi_version.h> 0019 #if AKONADI_VERSION >= QT_VERSION_CHECK(5, 19, 40) 0020 #include <Akonadi/ContactSearchJob> 0021 #else 0022 #include <Akonadi/Contact/ContactSearchJob> 0023 #endif 0024 #else 0025 #include <Akonadi/ContactSearchJob> 0026 #endif 0027 0028 AttendeeStatusModel::AttendeeStatusModel(QObject *parent) 0029 : QAbstractListModel(parent) 0030 { 0031 QRegularExpression lowerToCapitalSep(QStringLiteral("([a-z])([A-Z])")); 0032 QRegularExpression capitalToCapitalSep(QStringLiteral("([A-Z])([A-Z])")); 0033 0034 for (int i = 0; i < QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().keyCount(); i++) { 0035 int value = QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().value(i); 0036 0037 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII. 0038 // Regular expression adds space between every lowercase and Capitalised character then does the same 0039 // for capitalised letters together, e.g. ThisIsATest. Not a problem right now, but best to be safe. 0040 const QLatin1String enumName = QLatin1String(QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().key(i)); 0041 QString displayName = enumName; 0042 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2")); 0043 displayName.replace(capitalToCapitalSep, QStringLiteral("\\1 \\2")); 0044 displayName.replace(lowerToCapitalSep, QStringLiteral("\\1 \\2")); 0045 0046 m_status[value] = i18n(displayName.toStdString().c_str()); 0047 } 0048 } 0049 0050 QVariant AttendeeStatusModel::data(const QModelIndex &idx, int role) const 0051 { 0052 if (!idx.isValid()) { 0053 return {}; 0054 } 0055 0056 const int value = QMetaEnum::fromType<KCalendarCore::Attendee::PartStat>().value(idx.row()); 0057 0058 switch (role) { 0059 case DisplayNameRole: 0060 return m_status[value]; 0061 case ValueRole: 0062 return value; 0063 default: 0064 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role); 0065 return {}; 0066 } 0067 } 0068 0069 QHash<int, QByteArray> AttendeeStatusModel::roleNames() const 0070 { 0071 return { 0072 {DisplayNameRole, QByteArrayLiteral("display")}, 0073 {ValueRole, QByteArrayLiteral("value")}, 0074 }; 0075 } 0076 0077 int AttendeeStatusModel::rowCount(const QModelIndex &) const 0078 { 0079 return m_status.size(); 0080 } 0081 0082 AttendeesModel::AttendeesModel(QObject *parent, KCalendarCore::Incidence::Ptr incidencePtr) 0083 : QAbstractListModel(parent) 0084 , m_incidence(incidencePtr) 0085 , m_attendeeStatusModel(parent) 0086 { 0087 connect(this, &AttendeesModel::attendeesChanged, this, &AttendeesModel::updateAkonadiContactIds); 0088 } 0089 0090 KCalendarCore::Incidence::Ptr AttendeesModel::incidencePtr() const 0091 { 0092 return m_incidence; 0093 } 0094 0095 void AttendeesModel::setIncidencePtr(KCalendarCore::Incidence::Ptr incidence) 0096 { 0097 if (m_incidence == incidence) { 0098 return; 0099 } 0100 m_incidence = incidence; 0101 0102 Q_EMIT incidencePtrChanged(); 0103 Q_EMIT attendeesChanged(); 0104 Q_EMIT attendeeStatusModelChanged(); 0105 Q_EMIT layoutChanged(); 0106 } 0107 0108 KCalendarCore::Attendee::List AttendeesModel::attendees() const 0109 { 0110 return m_incidence->attendees(); 0111 } 0112 0113 void AttendeesModel::updateAkonadiContactIds() 0114 { 0115 m_attendeesAkonadiIds.clear(); 0116 0117 const auto attendees = m_incidence->attendees(); 0118 for (const auto &attendee : attendees) { 0119 auto job = new Akonadi::ContactSearchJob(); 0120 job->setQuery(Akonadi::ContactSearchJob::Email, attendee.email()); 0121 0122 connect(job, &Akonadi::ContactSearchJob::result, this, [this](KJob *job) { 0123 auto searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job); 0124 0125 const auto items = searchJob->items(); 0126 for (const auto &item : items) { 0127 m_attendeesAkonadiIds.append(item.id()); 0128 } 0129 0130 Q_EMIT attendeesAkonadiIdsChanged(); 0131 }); 0132 } 0133 0134 Q_EMIT attendeesAkonadiIdsChanged(); 0135 } 0136 0137 AttendeeStatusModel *AttendeesModel::attendeeStatusModel() 0138 { 0139 return &m_attendeeStatusModel; 0140 } 0141 0142 QList<qint64> AttendeesModel::attendeesAkonadiIds() const 0143 { 0144 return m_attendeesAkonadiIds; 0145 } 0146 0147 QVariant AttendeesModel::data(const QModelIndex &idx, int role) const 0148 { 0149 if (!hasIndex(idx.row(), idx.column())) { 0150 return {}; 0151 } 0152 const auto attendee = m_incidence->attendees().at(idx.row()); 0153 switch (role) { 0154 case CuTypeRole: 0155 return attendee.cuType(); 0156 case DelegateRole: 0157 return attendee.delegate(); 0158 case DelegatorRole: 0159 return attendee.delegator(); 0160 case EmailRole: 0161 return attendee.email(); 0162 case FullNameRole: 0163 return attendee.fullName(); 0164 case IsNullRole: 0165 return attendee.isNull(); 0166 case NameRole: 0167 return attendee.name(); 0168 case RoleRole: 0169 return attendee.role(); 0170 case RSVPRole: 0171 return attendee.RSVP(); 0172 case StatusRole: 0173 return attendee.status(); 0174 case UidRole: 0175 return attendee.uid(); 0176 default: 0177 qCWarning(KALENDAR_LOG) << "Unknown role for attendee:" << QMetaEnum::fromType<Roles>().valueToKey(role); 0178 return {}; 0179 } 0180 } 0181 0182 bool AttendeesModel::setData(const QModelIndex &idx, const QVariant &value, int role) 0183 { 0184 if (!idx.isValid()) { 0185 return false; 0186 } 0187 0188 // When modifying attendees, remember you cannot change them directly from m_incidence->attendees (is a const). 0189 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees()); 0190 0191 switch (role) { 0192 case CuTypeRole: { 0193 const auto cuType = static_cast<KCalendarCore::Attendee::CuType>(value.toInt()); 0194 currentAttendees[idx.row()].setCuType(cuType); 0195 break; 0196 } 0197 case DelegateRole: { 0198 const QString delegate = value.toString(); 0199 currentAttendees[idx.row()].setDelegate(delegate); 0200 break; 0201 } 0202 case DelegatorRole: { 0203 const QString delegator = value.toString(); 0204 currentAttendees[idx.row()].setDelegator(delegator); 0205 break; 0206 } 0207 case EmailRole: { 0208 const QString email = value.toString(); 0209 currentAttendees[idx.row()].setEmail(email); 0210 break; 0211 } 0212 case FullNameRole: { 0213 // Not a writable property 0214 return false; 0215 } 0216 case IsNullRole: { 0217 // Not an editable value 0218 return false; 0219 } 0220 case NameRole: { 0221 const QString name = value.toString(); 0222 currentAttendees[idx.row()].setName(name); 0223 break; 0224 } 0225 case RoleRole: { 0226 const auto role = static_cast<KCalendarCore::Attendee::Role>(value.toInt()); 0227 currentAttendees[idx.row()].setRole(role); 0228 break; 0229 } 0230 case RSVPRole: { 0231 const bool rsvp = value.toBool(); 0232 currentAttendees[idx.row()].setRSVP(rsvp); 0233 break; 0234 } 0235 case StatusRole: { 0236 const auto status = static_cast<KCalendarCore::Attendee::PartStat>(value.toInt()); 0237 currentAttendees[idx.row()].setStatus(status); 0238 break; 0239 } 0240 case UidRole: { 0241 const QString uid = value.toString(); 0242 currentAttendees[idx.row()].setUid(uid); 0243 break; 0244 } 0245 default: 0246 qCWarning(KALENDAR_LOG) << "Unknown role for incidence:" << QMetaEnum::fromType<Roles>().valueToKey(role); 0247 return false; 0248 } 0249 m_incidence->setAttendees(currentAttendees); 0250 Q_EMIT dataChanged(idx, idx); 0251 return true; 0252 } 0253 0254 QHash<int, QByteArray> AttendeesModel::roleNames() const 0255 { 0256 return { 0257 {CuTypeRole, QByteArrayLiteral("cuType")}, 0258 {DelegateRole, QByteArrayLiteral("delegate")}, 0259 {DelegatorRole, QByteArrayLiteral("delegator")}, 0260 {EmailRole, QByteArrayLiteral("email")}, 0261 {FullNameRole, QByteArrayLiteral("fullName")}, 0262 {IsNullRole, QByteArrayLiteral("isNull")}, 0263 {NameRole, QByteArrayLiteral("name")}, 0264 {RoleRole, QByteArrayLiteral("role")}, 0265 {RSVPRole, QByteArrayLiteral("rsvp")}, 0266 {StatusRole, QByteArrayLiteral("status")}, 0267 {UidRole, QByteArrayLiteral("uid")}, 0268 }; 0269 } 0270 0271 int AttendeesModel::rowCount(const QModelIndex &) const 0272 { 0273 return m_incidence->attendeeCount(); 0274 } 0275 0276 void AttendeesModel::addAttendee(qint64 itemId, const QString &email) 0277 { 0278 if (itemId) { 0279 Akonadi::Item item(itemId); 0280 0281 auto job = new Akonadi::ItemFetchJob(item); 0282 job->fetchScope().fetchFullPayload(); 0283 0284 connect(job, &Akonadi::ItemFetchJob::result, this, [this, email](KJob *job) { 0285 const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job); 0286 const auto item = fetchJob->items().at(0); 0287 const auto payload = item.payload<KContacts::Addressee>(); 0288 0289 KCalendarCore::Attendee attendee(payload.name(), 0290 payload.preferredEmail(), 0291 true, 0292 KCalendarCore::Attendee::NeedsAction, 0293 KCalendarCore::Attendee::ReqParticipant); 0294 0295 if (!email.isNull()) { 0296 attendee.setEmail(email); 0297 } 0298 0299 m_incidence->addAttendee(attendee); 0300 // Otherwise won't update 0301 Q_EMIT attendeesChanged(); 0302 Q_EMIT layoutChanged(); 0303 }); 0304 } else { 0305 // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII 0306 // addAttendee method does not work with null strings, so we use empty strings 0307 KCalendarCore::Attendee attendee(QLatin1String(""), 0308 QLatin1String(""), 0309 true, 0310 KCalendarCore::Attendee::NeedsAction, 0311 KCalendarCore::Attendee::ReqParticipant); 0312 0313 // addAttendee won't actually add any attendees without a set name 0314 m_incidence->addAttendee(attendee); 0315 } 0316 0317 Q_EMIT attendeesChanged(); 0318 Q_EMIT layoutChanged(); 0319 } 0320 0321 void AttendeesModel::deleteAttendee(int row) 0322 { 0323 if (!hasIndex(row, 0)) { 0324 return; 0325 } 0326 0327 KCalendarCore::Attendee::List currentAttendees(m_incidence->attendees()); 0328 currentAttendees.removeAt(row); 0329 m_incidence->setAttendees(currentAttendees); 0330 0331 Q_EMIT attendeesChanged(); 0332 Q_EMIT layoutChanged(); 0333 } 0334 0335 void AttendeesModel::deleteAttendeeFromAkonadiId(qint64 itemId) 0336 { 0337 Akonadi::Item item(itemId); 0338 0339 auto job = new Akonadi::ItemFetchJob(item); 0340 job->fetchScope().fetchFullPayload(); 0341 0342 connect(job, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) { 0343 auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job); 0344 0345 auto item = fetchJob->items().at(0); 0346 auto payload = item.payload<KContacts::Addressee>(); 0347 0348 for (int i = 0; i < m_incidence->attendeeCount(); i++) { 0349 const auto emails = payload.emails(); 0350 for (const auto &email : emails) { 0351 if (m_incidence->attendees()[i].email() == email) { 0352 deleteAttendee(i); 0353 break; 0354 } 0355 } 0356 } 0357 }); 0358 }