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