File indexing completed on 2024-04-28 05:11:33

0001 /*
0002   SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
0003   SPDX-FileCopyrightText: 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0004 
0005   Based on old attendeeeditor.cpp:
0006   SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org>
0007   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0008   SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
0009 
0010   SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "incidenceattendee.h"
0014 #include "attendeecomboboxdelegate.h"
0015 #include "attendeeeditor.h"
0016 #include "attendeelineeditdelegate.h"
0017 #include "attendeetablemodel.h"
0018 #include "conflictresolver.h"
0019 #include "editorconfig.h"
0020 #include "incidencedatetime.h"
0021 #include "schedulingdialog.h"
0022 #include "ui_dialogdesktop.h"
0023 #include <CalendarSupport/FreeBusyItemModel>
0024 
0025 #include <Akonadi/AbstractEmailAddressSelectionDialog>
0026 #include <Akonadi/ContactGroupExpandJob>
0027 #include <Akonadi/ContactGroupSearchJob>
0028 #include <Akonadi/EmailAddressSelectionDialog>
0029 
0030 #include <KCalUtils/Stringify>
0031 #include <KEmailAddress>
0032 #include <KPluginFactory>
0033 
0034 #include "incidenceeditor_debug.h"
0035 #include <KLocalizedString>
0036 #include <KMessageBox>
0037 #include <QPointer>
0038 #include <QTreeView>
0039 
0040 Q_DECLARE_METATYPE(IncidenceEditorNG::EditorConfig::Organizer)
0041 
0042 using namespace IncidenceEditorNG;
0043 
0044 IncidenceAttendee::IncidenceAttendee(QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui)
0045     : mUi(ui)
0046     , mParentWidget(parent)
0047     , mDateTime(dateTime)
0048     , mStateDelegate(new AttendeeComboBoxDelegate(this))
0049     , mRoleDelegate(new AttendeeComboBoxDelegate(this))
0050     , mResponseDelegate(new AttendeeComboBoxDelegate(this))
0051 {
0052     mDataModel = new AttendeeTableModel(this);
0053     mDataModel->setKeepEmpty(true);
0054     mDataModel->setRemoveEmptyLines(true);
0055     mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-participant.png")),
0056                            KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::ReqParticipant));
0057     mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-participant-optional.png")),
0058                            KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::OptParticipant));
0059     mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-observer.png")),
0060                            KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::NonParticipant));
0061     mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-chair.png")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::Chair));
0062 
0063     mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-request-response")), i18nc("@item:inlistbox", "Request Response"));
0064     mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-no-response")), i18nc("@item:inlistbox", "Request No Response"));
0065 
0066     mStateDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the current attendance status of the attendee."));
0067 
0068     mRoleDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the role of the attendee."));
0069 
0070     mResponseDelegate->setToolTip(i18nc("@info:tooltip", "Request a response from the attendee"));
0071     mResponseDelegate->setWhatsThis(i18nc("@info:whatsthis",
0072                                           "Edits whether to send an email to the "
0073                                           "attendee to request a response concerning "
0074                                           "attendance."));
0075 
0076     setObjectName(QLatin1StringView("IncidenceAttendee"));
0077 
0078     auto filterProxyModel = new AttendeeFilterProxyModel(this);
0079     filterProxyModel->setDynamicSortFilter(true);
0080     filterProxyModel->setSourceModel(mDataModel);
0081 
0082     connect(mUi->mGroupSubstitution, &QPushButton::clicked, this, &IncidenceAttendee::slotGroupSubstitutionPressed);
0083 
0084     mUi->mAttendeeTable->setModel(filterProxyModel);
0085 
0086     mAttendeeDelegate = new AttendeeLineEditDelegate(this);
0087 
0088     mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate());
0089     mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate());
0090     mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate());
0091     mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate());
0092 
0093     mUi->mOrganizerStack->setCurrentIndex(0);
0094 
0095     fillOrganizerCombo();
0096     slotUpdateCryptoPreferences();
0097     mUi->mSolveButton->setEnabled(false);
0098     mUi->mOrganizerLabel->setVisible(false);
0099 
0100     mConflictResolver = new ConflictResolver(parent, parent);
0101     mConflictResolver->setEarliestDate(mDateTime->startDate());
0102     mConflictResolver->setEarliestTime(mDateTime->startTime());
0103     mConflictResolver->setLatestDate(mDateTime->endDate());
0104     mConflictResolver->setLatestTime(mDateTime->endTime());
0105 
0106     connect(mUi->mSelectButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSelectAddresses);
0107     connect(mUi->mSolveButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSolveConflictPressed);
0108     /* Added as part of kolab/issue2297, which is currently under review
0109     connect(mUi->mOrganizerCombo, qOverload<const QString &>(&QComboBox::activated),
0110             this, &IncidenceAttendee::slotOrganizerChanged);
0111     */
0112     connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::checkDirtyStatus);
0113     connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::slotUpdateCryptoPreferences);
0114 
0115     connect(mDateTime, &IncidenceDateTime::startDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
0116     connect(mDateTime, &IncidenceDateTime::endDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
0117     connect(mDateTime, &IncidenceDateTime::startTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
0118     connect(mDateTime, &IncidenceDateTime::endTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
0119 
0120     connect(mConflictResolver, &ConflictResolver::conflictsDetected, this, &IncidenceAttendee::slotUpdateConflictLabel);
0121 
0122     connect(mConflictResolver->model(), &QAbstractItemModel::rowsInserted, this, &IncidenceAttendee::slotFreeBusyAdded);
0123     connect(mConflictResolver->model(), &QAbstractItemModel::layoutChanged, this, qOverload<>(&IncidenceAttendee::updateFBStatus));
0124     connect(mConflictResolver->model(), &QAbstractItemModel::dataChanged, this, &IncidenceAttendee::slotFreeBusyChanged);
0125 
0126     slotUpdateConflictLabel(0); // initialize label
0127 
0128     // conflict resolver (should show also resources)
0129     connect(mDataModel, &AttendeeTableModel::layoutChanged, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
0130     connect(mDataModel, &AttendeeTableModel::modelReset, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
0131     connect(mDataModel, &AttendeeTableModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotConflictResolverAttendeeRemoved);
0132     connect(mDataModel, &AttendeeTableModel::rowsInserted, this, &IncidenceAttendee::slotConflictResolverAttendeeAdded);
0133     connect(mDataModel, &AttendeeTableModel::dataChanged, this, &IncidenceAttendee::slotConflictResolverAttendeeChanged);
0134 
0135     // Group substitution
0136     connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
0137     connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
0138     connect(filterProxyModel, &AttendeeFilterProxyModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved);
0139     connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeAdded);
0140     connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeChanged);
0141 
0142     connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::updateCount);
0143     connect(filterProxyModel, &AttendeeFilterProxyModel::rowsRemoved, this, &IncidenceAttendee::updateCount);
0144     // only update when FullName is changed
0145     connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::updateCount);
0146     connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::updateCount);
0147     connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::filterLayoutChanged);
0148     connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::updateCount);
0149     connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::filterLayoutChanged);
0150 }
0151 
0152 IncidenceAttendee::~IncidenceAttendee() = default;
0153 
0154 void IncidenceAttendee::load(const KCalendarCore::Incidence::Ptr &incidence)
0155 {
0156     mLoadedIncidence = incidence;
0157 
0158     if (iAmOrganizer() || incidence->organizer().isEmpty()) {
0159         mUi->mOrganizerStack->setCurrentIndex(0);
0160 
0161         int found = -1;
0162         const QString fullOrganizer = incidence->organizer().fullName();
0163         const QString organizerEmail = incidence->organizer().email();
0164         for (int i = 0; i < mUi->mOrganizerCombo->count(); ++i) {
0165             KCalendarCore::Person organizerCandidate = KCalendarCore::Person::fromFullName(mUi->mOrganizerCombo->itemText(i));
0166             if (organizerCandidate.email() == organizerEmail) {
0167                 found = i;
0168                 mUi->mOrganizerCombo->setCurrentIndex(i);
0169                 break;
0170             }
0171         }
0172         if (found < 0 && !fullOrganizer.isEmpty()) {
0173             mUi->mOrganizerCombo->insertItem(0, fullOrganizer);
0174             mUi->mOrganizerCombo->setCurrentIndex(0);
0175         }
0176 
0177         mUi->mOrganizerLabel->setVisible(false);
0178     } else { // someone else is the organizer
0179         mUi->mOrganizerStack->setCurrentIndex(1);
0180         mUi->mOrganizerLabel->setText(incidence->organizer().fullName());
0181         mUi->mOrganizerLabel->setVisible(true);
0182     }
0183 
0184     KCalendarCore::Attendee::List attendees;
0185     const KCalendarCore::Attendee::List incidenceAttendees = incidence->attendees();
0186     attendees.reserve(incidenceAttendees.count());
0187     for (const KCalendarCore::Attendee &a : incidenceAttendees) {
0188         attendees << KCalendarCore::Attendee(a);
0189     }
0190 
0191     mDataModel->setAttendees(attendees);
0192     slotUpdateConflictLabel(0);
0193 
0194     setActions(incidence->type());
0195 
0196     mWasDirty = false;
0197 }
0198 
0199 void IncidenceAttendee::save(const KCalendarCore::Incidence::Ptr &incidence)
0200 {
0201     incidence->clearAttendees();
0202     const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
0203 
0204     for (const KCalendarCore::Attendee &attendee : attendees) {
0205         bool skip = false;
0206         if (attendee.fullName().isEmpty()) {
0207             continue;
0208         }
0209         if (KEmailAddress::isValidAddress(attendee.email())) {
0210             if (KMessageBox::warningTwoActions(nullptr,
0211                                                i18nc("@info",
0212                                                      "%1 does not look like a valid email address. "
0213                                                      "Are you sure you want to invite this participant?",
0214                                                      attendee.email()),
0215                                                i18nc("@title:window", "Invalid Email Address"),
0216                                                KGuiItem(i18nc("@action:button", "Invite"), QStringLiteral("dialog-ok")),
0217                                                KGuiItem(i18nc("@action:button", "Do Not Invite"), QStringLiteral("dialog-cancel")))
0218                 != KMessageBox::ButtonCode::PrimaryAction) {
0219                 skip = true;
0220             }
0221         }
0222         if (!skip) {
0223             incidence->addAttendee(attendee);
0224         }
0225     }
0226 
0227     // Must not have an organizer for items without attendees
0228     if (!incidence->attendeeCount()) {
0229         return;
0230     }
0231 
0232     if (mUi->mOrganizerStack->currentIndex() == 0) {
0233         incidence->setOrganizer(mUi->mOrganizerCombo->currentText());
0234     } else {
0235         incidence->setOrganizer(mUi->mOrganizerLabel->text());
0236     }
0237 }
0238 
0239 bool IncidenceAttendee::isDirty() const
0240 {
0241     if (iAmOrganizer()) {
0242         KCalendarCore::Event tmp;
0243         tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
0244 
0245         if (mLoadedIncidence->organizer().email() != tmp.organizer().email()) {
0246             qCDebug(INCIDENCEEDITOR_LOG) << "Organizer changed. Old was " << mLoadedIncidence->organizer().name() << mLoadedIncidence->organizer().email()
0247                                          << "; new is " << tmp.organizer().name() << tmp.organizer().email();
0248             return true;
0249         }
0250     }
0251 
0252     const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
0253     KCalendarCore::Attendee::List newList;
0254 
0255     const auto lstAttendees = mDataModel->attendees();
0256     for (const KCalendarCore::Attendee &attendee : lstAttendees) {
0257         if (!attendee.fullName().isEmpty()) {
0258             newList.append(attendee);
0259         }
0260     }
0261 
0262     // The lists sizes *must* be the same. When the organizer is attending the
0263     // event as well, he should be in the attendees list as well.
0264     if (originalList.size() != newList.size()) {
0265         return true;
0266     }
0267 
0268     // Okay, again not the most efficient algorithm, but I'm assuming that in the
0269     // bulk of the use cases, the number of attendees is not much higher than 10 or so.
0270     for (const KCalendarCore::Attendee &attendee : originalList) {
0271         bool found = false;
0272         for (int i = 0; i < newList.count(); ++i) {
0273             if (newList[i] == attendee) {
0274                 newList.remove(i);
0275                 found = true;
0276                 break;
0277             }
0278         }
0279 
0280         if (!found) {
0281             // One of the attendees in the original list was not found in the new list.
0282             return true;
0283         }
0284     }
0285 
0286     return false;
0287 }
0288 
0289 void IncidenceAttendee::changeStatusForMe(KCalendarCore::Attendee::PartStat stat)
0290 {
0291     const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
0292     Q_ASSERT(config);
0293 
0294     for (int i = 0; i < mDataModel->rowCount(); ++i) {
0295         QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email);
0296         if (config->thatIsMe(mDataModel->data(index, Qt::DisplayRole).toString())) {
0297             index = mDataModel->index(i, AttendeeTableModel::Status);
0298             mDataModel->setData(index, stat);
0299             break;
0300         }
0301     }
0302 
0303     checkDirtyStatus();
0304 }
0305 
0306 void IncidenceAttendee::acceptForMe()
0307 {
0308     changeStatusForMe(KCalendarCore::Attendee::Accepted);
0309 }
0310 
0311 void IncidenceAttendee::declineForMe()
0312 {
0313     changeStatusForMe(KCalendarCore::Attendee::Declined);
0314 }
0315 
0316 void IncidenceAttendee::fillOrganizerCombo()
0317 {
0318     mUi->mOrganizerCombo->clear();
0319     const auto organizers = IncidenceEditorNG::EditorConfig::instance()->allOrganizers();
0320     for (auto organizer = organizers.cbegin(), end = organizers.cend(); organizer != end; ++organizer) {
0321         if (std::any_of(organizers.cbegin(), organizer, [organizer](const auto &pastOrg) {
0322                 return organizer->email == pastOrg.email && organizer->name == pastOrg.name;
0323             })) {
0324             continue;
0325         }
0326 
0327         mUi->mOrganizerCombo->addItem(QStringLiteral("%1 <%2>").arg(organizer->name, organizer->email), QVariant::fromValue(*organizer));
0328     }
0329 }
0330 
0331 void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalendarCore::Attendee &attendee)
0332 {
0333     QString fullname = attendee.fullName();
0334 
0335     // stop old job
0336     KJob *oldJob = mMightBeGroupJobs.key(attendee.uid());
0337     if (oldJob != nullptr) {
0338         disconnect(oldJob);
0339         oldJob->deleteLater();
0340         mMightBeGroupJobs.remove(oldJob);
0341     }
0342 
0343     mGroupList.remove(attendee.uid());
0344 
0345     if (!fullname.isEmpty()) {
0346         auto job = new Akonadi::ContactGroupSearchJob();
0347         job->setQuery(Akonadi::ContactGroupSearchJob::Name, fullname);
0348         connect(job, &Akonadi::ContactGroupSearchJob::result, this, &IncidenceAttendee::groupSearchResult);
0349 
0350         mMightBeGroupJobs.insert(job, attendee.uid());
0351     }
0352 }
0353 
0354 void IncidenceAttendee::groupSearchResult(KJob *job)
0355 {
0356     auto searchJob = qobject_cast<Akonadi::ContactGroupSearchJob *>(job);
0357     Q_ASSERT(searchJob);
0358 
0359     Q_ASSERT(mMightBeGroupJobs.contains(job));
0360     const auto uid = mMightBeGroupJobs.take(job);
0361 
0362     const KContacts::ContactGroup::List contactGroups = searchJob->contactGroups();
0363     if (contactGroups.isEmpty()) {
0364         updateGroupExpand();
0365         return; // Nothing todo, probably a normal email address was entered
0366     }
0367 
0368     // TODO: Give the user the possibility to choose a group when there is more than one?!
0369     KContacts::ContactGroup group = contactGroups.first();
0370 
0371     const int row = rowOfAttendee(uid);
0372     QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType);
0373     dataModel()->setData(index, KCalendarCore::Attendee::Group);
0374 
0375     mGroupList.insert(uid, group);
0376     updateGroupExpand();
0377 }
0378 
0379 void IncidenceAttendee::updateGroupExpand()
0380 {
0381     mUi->mGroupSubstitution->setEnabled(!mGroupList.isEmpty());
0382 }
0383 
0384 void IncidenceAttendee::slotGroupSubstitutionPressed()
0385 {
0386     for (auto it = mGroupList.cbegin(), end = mGroupList.cend(); it != end; ++it) {
0387         auto expandJob = new Akonadi::ContactGroupExpandJob(it.value(), this);
0388         connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
0389         mExpandGroupJobs.insert(expandJob, it.key());
0390         expandJob->start();
0391     }
0392 }
0393 
0394 void IncidenceAttendee::expandResult(KJob *job)
0395 {
0396     auto expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
0397     Q_ASSERT(expandJob);
0398     Q_ASSERT(mExpandGroupJobs.contains(job));
0399     const auto uid = mExpandGroupJobs.take(job);
0400     const int row = rowOfAttendee(uid);
0401     const auto attendee = dataModel()->attendees().at(row);
0402     const QString currentEmail = attendee.email();
0403     const KContacts::Addressee::List groupMembers = expandJob->contacts();
0404     bool wasACorrectEmail = false;
0405     for (const KContacts::Addressee &member : groupMembers) {
0406         if (member.preferredEmail() == currentEmail) {
0407             wasACorrectEmail = true;
0408             break;
0409         }
0410     }
0411 
0412     if (!wasACorrectEmail) {
0413         dataModel()->removeRow(row);
0414         for (const KContacts::Addressee &member : groupMembers) {
0415             KCalendarCore::Attendee newAt(member.realName(), member.preferredEmail(), attendee.RSVP(), attendee.status(), attendee.role(), member.uid());
0416             dataModel()->insertAttendee(row, newAt);
0417         }
0418     }
0419 }
0420 
0421 void IncidenceAttendee::insertAddresses(const KContacts::Addressee::List &list)
0422 {
0423     for (const KContacts::Addressee &contact : list) {
0424         insertAttendeeFromAddressee(contact);
0425     }
0426 }
0427 
0428 void IncidenceAttendee::slotSelectAddresses()
0429 {
0430     QPointer<Akonadi::AbstractEmailAddressSelectionDialog> dialog;
0431     const KPluginMetaData editWidgetPlugin(QStringLiteral("pim6/akonadi/emailaddressselectionldapdialogplugin"));
0432 
0433     const auto result = KPluginFactory::instantiatePlugin<Akonadi::AbstractEmailAddressSelectionDialog>(editWidgetPlugin, mParentWidget);
0434     if (result) {
0435         dialog = result.plugin;
0436 
0437     } else {
0438         dialog = new Akonadi::EmailAddressSelectionDialog(mParentWidget);
0439     }
0440     dialog->view()->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
0441     dialog->setWindowTitle(i18nc("@title:window", "Select Attendees"));
0442     connect(dialog.data(), &Akonadi::AbstractEmailAddressSelectionDialog::insertAddresses, this, &IncidenceEditorNG::IncidenceAttendee::insertAddresses);
0443     if (dialog->exec() == QDialog::Accepted) {
0444         const Akonadi::EmailAddressSelection::List list = dialog->selectedAddresses();
0445         for (const Akonadi::EmailAddressSelection &selection : list) {
0446             if (selection.item().hasPayload<KContacts::ContactGroup>()) {
0447                 auto job = new Akonadi::ContactGroupExpandJob(selection.item().payload<KContacts::ContactGroup>(), this);
0448                 connect(job, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
0449                 KCalendarCore::Attendee::PartStat partStat = KCalendarCore::Attendee::NeedsAction;
0450                 bool rsvp = true;
0451 
0452                 int pos = 0;
0453                 QString name;
0454                 QString email;
0455                 KEmailAddress::extractEmailAddressAndName(selection.email(), email, name);
0456                 KCalendarCore::Attendee newAt(selection.name(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant);
0457                 dataModel()->insertAttendee(pos, newAt);
0458 
0459                 mExpandGroupJobs.insert(job, newAt.uid());
0460                 job->start();
0461             } else {
0462                 KContacts::Addressee contact;
0463                 contact.setName(selection.name());
0464                 contact.addEmail(KContacts::Email(selection.email()));
0465 
0466                 if (selection.item().hasPayload<KContacts::Addressee>()) {
0467                     contact.setUid(selection.item().payload<KContacts::Addressee>().uid());
0468                 }
0469                 insertAttendeeFromAddressee(contact);
0470             }
0471         }
0472     }
0473     delete dialog;
0474 }
0475 
0476 void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed()
0477 {
0478     const int duration = mDateTime->startTime().secsTo(mDateTime->endTime());
0479     QScopedPointer<SchedulingDialog> dialog(new SchedulingDialog(mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget));
0480     dialog->slotUpdateIncidenceStartEnd(mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime());
0481     if (dialog->exec() == QDialog::Accepted) {
0482         qCDebug(INCIDENCEEDITOR_LOG) << dialog->selectedStartDate() << dialog->selectedStartTime();
0483         if (dialog->selectedStartDate().isValid() && dialog->selectedStartTime().isValid()) {
0484             mDateTime->setStartDate(dialog->selectedStartDate());
0485             mDateTime->setStartTime(dialog->selectedStartTime());
0486         }
0487     }
0488 }
0489 
0490 void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0491 {
0492     if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
0493         for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
0494             QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
0495             auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
0496             if (mConflictResolver->containsAttendee(attendee)) {
0497                 mConflictResolver->removeAttendee(attendee);
0498             }
0499             if (!dataModel()->data(email).toString().isEmpty()) {
0500                 mConflictResolver->insertAttendee(attendee);
0501             }
0502         }
0503     }
0504     checkDirtyStatus();
0505 }
0506 
0507 void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last)
0508 {
0509     for (int i = first; i <= last; ++i) {
0510         QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
0511         if (!dataModel()->data(email).toString().isEmpty()) {
0512             mConflictResolver->insertAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
0513         }
0514     }
0515     checkDirtyStatus();
0516 }
0517 
0518 void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last)
0519 {
0520     for (int i = first; i <= last; ++i) {
0521         QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
0522         if (!dataModel()->data(email).toString().isEmpty()) {
0523             mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
0524         }
0525     }
0526     checkDirtyStatus();
0527 }
0528 
0529 void IncidenceAttendee::slotConflictResolverLayoutChanged()
0530 {
0531     const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
0532     mConflictResolver->clearAttendees();
0533     for (const KCalendarCore::Attendee &attendee : attendees) {
0534         if (!attendee.email().isEmpty()) {
0535             mConflictResolver->insertAttendee(attendee);
0536         }
0537     }
0538     checkDirtyStatus();
0539 }
0540 
0541 void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last)
0542 {
0543     // We are only interested in toplevel changes
0544     if (parent.isValid()) {
0545         return;
0546     }
0547     QAbstractItemModel *model = mConflictResolver->model();
0548     for (int i = first; i <= last; ++i) {
0549         QModelIndex index = model->index(i, 0, parent);
0550         const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
0551         const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
0552         if (!attendee.isNull()) {
0553             updateFBStatus(attendee, fb);
0554         }
0555     }
0556 }
0557 
0558 void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0559 {
0560     // We are only interested in toplevel changes
0561     if (topLeft.parent().isValid()) {
0562         return;
0563     }
0564     QAbstractItemModel *model = mConflictResolver->model();
0565     for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
0566         QModelIndex index = model->index(i, 0);
0567         const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
0568         const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
0569         if (!attendee.isNull()) {
0570             updateFBStatus(attendee, fb);
0571         }
0572     }
0573 }
0574 
0575 void IncidenceAttendee::updateFBStatus()
0576 {
0577     QAbstractItemModel *model = mConflictResolver->model();
0578     for (int i = 0; i < model->rowCount(); ++i) {
0579         QModelIndex index = model->index(i, 0);
0580         const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
0581         const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
0582         if (!attendee.isNull()) {
0583             updateFBStatus(attendee, fb);
0584         }
0585     }
0586 }
0587 
0588 void IncidenceAttendee::updateFBStatus(const KCalendarCore::Attendee &attendee, const KCalendarCore::FreeBusy::Ptr &fb)
0589 {
0590     KCalendarCore::Attendee::List attendees = mDataModel->attendees();
0591     QDateTime startTime = mDateTime->currentStartDateTime();
0592     QDateTime endTime = mDateTime->currentEndDateTime();
0593     if (attendees.contains(attendee)) {
0594         int row = dataModel()->attendees().indexOf(attendee);
0595         QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available);
0596         if (fb) {
0597             KCalendarCore::Period::List busyPeriods = fb->busyPeriods();
0598             for (auto it = busyPeriods.begin(); it != busyPeriods.end(); ++it) {
0599                 // periods started before and lapping into the incidence (s < startTime && e >= startTime)
0600                 // periods starting in the time of incidence (s >= startTime && s <= endTime)
0601                 if (((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime)) {
0602                     switch (attendee.status()) {
0603                     case KCalendarCore::Attendee::Accepted:
0604                         dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted);
0605                         return;
0606                     default:
0607                         dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy);
0608                         return;
0609                     }
0610                 }
0611             }
0612             dataModel()->setData(attendeeIndex, AttendeeTableModel::Free);
0613         } else {
0614             dataModel()->setData(attendeeIndex, AttendeeTableModel::Unknown);
0615         }
0616     }
0617 }
0618 
0619 void IncidenceAttendee::slotUpdateConflictLabel(int count)
0620 {
0621     if (attendeeCount() > 0) {
0622         mUi->mSolveButton->setEnabled(true);
0623         if (count > 0) {
0624             QString label = i18ncp("@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count);
0625             mUi->mConflictsLabel->setText(label);
0626             mUi->mConflictsLabel->setVisible(true);
0627         } else {
0628             mUi->mConflictsLabel->setVisible(false);
0629         }
0630     } else {
0631         mUi->mSolveButton->setEnabled(false);
0632         mUi->mConflictsLabel->setVisible(false);
0633     }
0634 }
0635 
0636 void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0637 {
0638     if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
0639         for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
0640             QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
0641             auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
0642             checkIfExpansionIsNeeded(attendee);
0643         }
0644     }
0645     updateGroupExpand();
0646 }
0647 
0648 void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last)
0649 {
0650     Q_UNUSED(index)
0651     for (int i = first; i <= last; ++i) {
0652         QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
0653         auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
0654         checkIfExpansionIsNeeded(attendee);
0655     }
0656     updateGroupExpand();
0657 }
0658 
0659 void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last)
0660 {
0661     Q_UNUSED(index)
0662     for (int i = first; i <= last; ++i) {
0663         QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
0664         auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
0665         KJob *job = mMightBeGroupJobs.key(attendee.uid());
0666         if (job) {
0667             disconnect(job);
0668             job->deleteLater();
0669             mMightBeGroupJobs.remove(job);
0670         }
0671         job = mExpandGroupJobs.key(attendee.uid());
0672         if (job) {
0673             disconnect(job);
0674             job->deleteLater();
0675             mExpandGroupJobs.remove(job);
0676         }
0677         mGroupList.remove(attendee.uid());
0678     }
0679     updateGroupExpand();
0680 }
0681 
0682 void IncidenceAttendee::slotGroupSubstitutionLayoutChanged()
0683 {
0684     for (auto it = mMightBeGroupJobs.cbegin(), end = mMightBeGroupJobs.cend(); it != end; ++it) {
0685         KJob *job = it.key();
0686         disconnect(job);
0687         job->deleteLater();
0688     }
0689 
0690     for (auto it = mExpandGroupJobs.cbegin(), end = mExpandGroupJobs.cend(); it != end; ++it) {
0691         KJob *job = it.key();
0692         disconnect(job);
0693         job->deleteLater();
0694     }
0695     mMightBeGroupJobs.clear();
0696     mExpandGroupJobs.clear();
0697     mGroupList.clear();
0698 
0699     QAbstractItemModel *model = mUi->mAttendeeTable->model();
0700     if (!model) {
0701         return;
0702     }
0703     for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
0704         QModelIndex index = model->index(i, AttendeeTableModel::FullName);
0705         if (!model->data(index).toString().isEmpty()) {
0706             QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
0707             auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
0708             checkIfExpansionIsNeeded(attendee);
0709         }
0710     }
0711 
0712     updateGroupExpand();
0713 }
0714 
0715 bool IncidenceAttendee::iAmOrganizer() const
0716 {
0717     if (mLoadedIncidence) {
0718         const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
0719         return config->thatIsMe(mLoadedIncidence->organizer().email());
0720     }
0721 
0722     return true;
0723 }
0724 
0725 void IncidenceAttendee::insertAttendeeFromAddressee(const KContacts::Addressee &a, int pos /*=-1*/)
0726 {
0727     const bool sameAsOrganizer = mUi->mOrganizerCombo && KEmailAddress::compareEmail(a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false);
0728     KCalendarCore::Attendee::PartStat partStat = KCalendarCore::Attendee::NeedsAction;
0729     bool rsvp = true;
0730 
0731     if (iAmOrganizer() && sameAsOrganizer) {
0732         partStat = KCalendarCore::Attendee::Accepted;
0733         rsvp = false;
0734     }
0735     QString name;
0736     QString email;
0737     KEmailAddress::extractEmailAddressAndName(a.preferredEmail(), email, name);
0738 
0739     KCalendarCore::Attendee newAt(a.realName(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant, a.uid());
0740     if (pos < 0) {
0741         pos = dataModel()->rowCount() - 1;
0742     }
0743 
0744     dataModel()->insertAttendee(pos, newAt);
0745 }
0746 
0747 void IncidenceAttendee::slotEventDurationChanged()
0748 {
0749     const QDateTime start = mDateTime->currentStartDateTime();
0750     const QDateTime end = mDateTime->currentEndDateTime();
0751 
0752     if (start >= end) { // This can happen, especially for todos.
0753         return;
0754     }
0755 
0756     mConflictResolver->setEarliestDateTime(start);
0757     mConflictResolver->setLatestDateTime(end);
0758     updateFBStatus();
0759 }
0760 
0761 void IncidenceAttendee::slotOrganizerChanged(const QString &newOrganizer)
0762 {
0763     if (KEmailAddress::compareEmail(newOrganizer, mOrganizer, false)) {
0764         return;
0765     }
0766 
0767     QString name;
0768     QString email;
0769     bool success = KEmailAddress::extractEmailAddressAndName(newOrganizer, email, name);
0770 
0771     if (!success) {
0772         qCWarning(INCIDENCEEDITOR_LOG) << "Could not extract email address and name";
0773         return;
0774     }
0775 
0776     int currentOrganizerAttendee = -1;
0777     int newOrganizerAttendee = -1;
0778 
0779     for (int i = 0; i < mDataModel->rowCount(); ++i) {
0780         QModelIndex index = mDataModel->index(i, AttendeeTableModel::FullName);
0781         QString fullName = mDataModel->data(index, Qt::DisplayRole).toString();
0782         if (fullName == mOrganizer) {
0783             currentOrganizerAttendee = i;
0784         }
0785 
0786         if (fullName == newOrganizer) {
0787             newOrganizerAttendee = i;
0788         }
0789     }
0790 
0791     int answer;
0792     if (currentOrganizerAttendee > -1) {
0793         answer = KMessageBox::questionTwoActions(mParentWidget,
0794                                                  i18nc("@option",
0795                                                        "You are changing the organizer of this event. "
0796                                                        "Since the organizer is also attending this event, would you "
0797                                                        "like to change the corresponding attendee as well?"),
0798                                                  QString(),
0799                                                  KGuiItem(i18nc("@action:button", "Change Attendee"), QStringLiteral("dialog-ok")),
0800                                                  KGuiItem(i18nc("@action:button", "Do Not Change"), QStringLiteral("dialog-cancel")));
0801     } else {
0802         answer = KMessageBox::ButtonCode::PrimaryAction;
0803     }
0804 
0805     if (answer == KMessageBox::ButtonCode::PrimaryAction) {
0806         if (currentOrganizerAttendee > -1) {
0807             mDataModel->removeRows(currentOrganizerAttendee, 1);
0808         }
0809 
0810         if (newOrganizerAttendee == -1) {
0811             bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp.
0812             KCalendarCore::Attendee::PartStat status = iAmOrganizer() ? KCalendarCore::Attendee::Accepted : KCalendarCore::Attendee::NeedsAction;
0813 
0814             KCalendarCore::Attendee newAt(name, email, rsvp, status, KCalendarCore::Attendee::ReqParticipant);
0815 
0816             mDataModel->insertAttendee(mDataModel->rowCount(), newAt);
0817         }
0818     }
0819     mOrganizer = newOrganizer;
0820 }
0821 
0822 AttendeeTableModel *IncidenceAttendee::dataModel() const
0823 {
0824     return mDataModel;
0825 }
0826 
0827 AttendeeComboBoxDelegate *IncidenceAttendee::responseDelegate() const
0828 {
0829     return mResponseDelegate;
0830 }
0831 
0832 AttendeeComboBoxDelegate *IncidenceAttendee::roleDelegate() const
0833 {
0834     return mRoleDelegate;
0835 }
0836 
0837 AttendeeComboBoxDelegate *IncidenceAttendee::stateDelegate() const
0838 {
0839     return mStateDelegate;
0840 }
0841 
0842 AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() const
0843 {
0844     return mAttendeeDelegate;
0845 }
0846 
0847 void IncidenceAttendee::filterLayoutChanged()
0848 {
0849     QHeaderView *headerView = mUi->mAttendeeTable->horizontalHeader();
0850     headerView->setSectionResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents);
0851     headerView->setSectionResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch);
0852     headerView->setSectionResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents);
0853     headerView->setSectionResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents);
0854     headerView->setSectionHidden(AttendeeTableModel::CuType, true);
0855     headerView->setSectionHidden(AttendeeTableModel::Name, true);
0856     headerView->setSectionHidden(AttendeeTableModel::Email, true);
0857     headerView->setSectionHidden(AttendeeTableModel::Available, true);
0858 }
0859 
0860 void IncidenceAttendee::updateCount()
0861 {
0862     Q_EMIT attendeeCountChanged(attendeeCount());
0863 
0864     checkDirtyStatus();
0865 }
0866 
0867 int IncidenceAttendee::attendeeCount() const
0868 {
0869     int c = 0;
0870     QModelIndex index;
0871     QAbstractItemModel *model = mUi->mAttendeeTable->model();
0872     if (!model) {
0873         return 0;
0874     }
0875     for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
0876         index = model->index(i, AttendeeTableModel::FullName);
0877         if (!model->data(index).toString().isEmpty()) {
0878             ++c;
0879         }
0880     }
0881     return c;
0882 }
0883 
0884 void IncidenceAttendee::setActions(KCalendarCore::Incidence::IncidenceType actions)
0885 {
0886     mStateDelegate->clear();
0887     if (actions == KCalendarCore::Incidence::TypeEvent) {
0888         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
0889         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
0890         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
0891         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
0892         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
0893     } else {
0894         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
0895         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
0896         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
0897         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
0898         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
0899         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-complete.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Completed));
0900         mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-ongoing.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::InProcess));
0901     }
0902 }
0903 
0904 void IncidenceAttendee::printDebugInfo() const
0905 {
0906     qCDebug(INCIDENCEEDITOR_LOG) << "I'm organizer   : " << iAmOrganizer();
0907     qCDebug(INCIDENCEEDITOR_LOG) << "Loaded organizer: " << mLoadedIncidence->organizer().email();
0908 
0909     if (iAmOrganizer()) {
0910         KCalendarCore::Event tmp;
0911         tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
0912         qCDebug(INCIDENCEEDITOR_LOG) << "Organizer combo: " << tmp.organizer().email();
0913     }
0914 
0915     const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
0916     KCalendarCore::Attendee::List newList;
0917     qCDebug(INCIDENCEEDITOR_LOG) << "List sizes: " << originalList.count() << newList.count();
0918 
0919     const auto lstAttendees = mDataModel->attendees();
0920     for (const KCalendarCore::Attendee &attendee : lstAttendees) {
0921         if (!attendee.fullName().isEmpty()) {
0922             newList.append(attendee);
0923         }
0924     }
0925 
0926     // Okay, again not the most efficient algorithm, but I'm assuming that in the
0927     // bulk of the use cases, the number of attendees is not much higher than 10 or so.
0928     for (const KCalendarCore::Attendee &attendee : originalList) {
0929         bool found = false;
0930         for (int i = 0; i < newList.count(); ++i) {
0931             if (newList[i] == attendee) {
0932                 newList.remove(i);
0933                 found = true;
0934                 break;
0935             }
0936         }
0937 
0938         if (!found) {
0939             qCDebug(INCIDENCEEDITOR_LOG) << "Attendee not found: " << attendee.email() << attendee.name() << attendee.status() << attendee.RSVP()
0940                                          << attendee.role() << attendee.uid() << attendee.cuType() << attendee.delegate() << attendee.delegator()
0941                                          << "; we have:";
0942             for (int i = 0, total = newList.count(); i < total; ++i) {
0943                 const KCalendarCore::Attendee newAttendee = newList[i];
0944                 qCDebug(INCIDENCEEDITOR_LOG) << "Attendee: " << newAttendee.email() << newAttendee.name() << newAttendee.status() << newAttendee.RSVP()
0945                                              << newAttendee.role() << newAttendee.uid() << newAttendee.cuType() << newAttendee.delegate()
0946                                              << newAttendee.delegator();
0947             }
0948 
0949             return;
0950         }
0951     }
0952 }
0953 
0954 int IncidenceAttendee::rowOfAttendee(const QString &uid) const
0955 {
0956     const auto attendees = dataModel()->attendees();
0957     const auto it = std::find_if(attendees.begin(), attendees.end(), [uid](const KCalendarCore::Attendee &att) {
0958         return att.uid() == uid;
0959     });
0960     return std::distance(attendees.begin(), it);
0961 }
0962 
0963 void IncidenceAttendee::slotUpdateCryptoPreferences()
0964 {
0965     const auto idx = mUi->mOrganizerCombo->currentIndex();
0966     if (idx < 0) {
0967         return;
0968     }
0969     const auto organizer = mUi->mOrganizerCombo->currentData().value<EditorConfig::Organizer>();
0970 
0971     mUi->mSignItip->setChecked(organizer.sign);
0972     mUi->mEncryptItip->setChecked(organizer.encrypt);
0973 }
0974 
0975 #include "moc_incidenceattendee.cpp"