Incidenceeditor

incidenceattendee.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
3 SPDX-FileCopyrightText: 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4
5 Based on old attendeeeditor.cpp:
6 SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org>
7 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
8 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13#include "incidenceattendee.h"
14using namespace Qt::Literals::StringLiterals;
15
16#include "attendeecomboboxdelegate.h"
17#include "attendeeeditor.h"
18#include "attendeelineeditdelegate.h"
19#include "attendeetablemodel.h"
20#include "conflictresolver.h"
21#include "editorconfig.h"
22#include "incidencedatetime.h"
23#include "schedulingdialog.h"
24#include "ui_dialogdesktop.h"
25#include <CalendarSupport/FreeBusyItemModel>
26
27#include <Akonadi/AbstractEmailAddressSelectionDialog>
28#include <Akonadi/ContactGroupExpandJob>
29#include <Akonadi/ContactGroupSearchJob>
30#include <Akonadi/EmailAddressSelectionDialog>
31
32#include <KCalUtils/Stringify>
33#include <KEmailAddress>
34#include <KPluginFactory>
35
36#include "incidenceeditor_debug.h"
37#include <KLocalizedString>
38#include <KMessageBox>
39#include <QPointer>
40#include <QTreeView>
41
42Q_DECLARE_METATYPE(IncidenceEditorNG::EditorConfig::Organizer)
43
44using namespace IncidenceEditorNG;
45
46IncidenceAttendee::IncidenceAttendee(QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui)
47 : mUi(ui)
48 , mParentWidget(parent)
49 , mDateTime(dateTime)
50 , mStateDelegate(new AttendeeComboBoxDelegate(this))
51 , mRoleDelegate(new AttendeeComboBoxDelegate(this))
52 , mResponseDelegate(new AttendeeComboBoxDelegate(this))
53{
54 mDataModel = new AttendeeTableModel(this);
55 mDataModel->setKeepEmpty(true);
56 mDataModel->setRemoveEmptyLines(true);
57 mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-participant.png")),
58 KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::ReqParticipant));
59 mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-participant-optional.png")),
60 KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::OptParticipant));
61 mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-observer.png")),
62 KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::NonParticipant));
63 mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/meeting-chair.png")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::Chair));
64
65 mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-request-response")), i18nc("@item:inlistbox", "Request Response"));
66 mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-no-response")), i18nc("@item:inlistbox", "Request No Response"));
67
68 mStateDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the current attendance status of the attendee."));
69
70 mRoleDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the role of the attendee."));
71
72 mResponseDelegate->setToolTip(i18nc("@info:tooltip", "Request a response from the attendee"));
73 mResponseDelegate->setWhatsThis(i18nc("@info:whatsthis",
74 "Edits whether to send an email to the "
75 "attendee to request a response concerning "
76 "attendance."));
77
78 setObjectName("IncidenceAttendee"_L1);
79
80 auto filterProxyModel = new AttendeeFilterProxyModel(this);
81 filterProxyModel->setSourceModel(mDataModel);
82
83 connect(mUi->mGroupSubstitution, &QPushButton::clicked, this, &IncidenceAttendee::slotGroupSubstitutionPressed);
84
85 mUi->mAttendeeTable->setModel(filterProxyModel);
86
87 mAttendeeDelegate = new AttendeeLineEditDelegate(this);
88
89 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate());
90 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate());
91 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate());
92 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate());
93
94 mUi->mOrganizerStack->setCurrentIndex(0);
95
96 fillOrganizerCombo();
97 slotUpdateCryptoPreferences();
98 mUi->mSolveButton->setEnabled(false);
99 mUi->mOrganizerLabel->setVisible(false);
100
101 mConflictResolver = new ConflictResolver(parent, parent);
102 mConflictResolver->setEarliestDate(mDateTime->startDate());
103 mConflictResolver->setEarliestTime(mDateTime->startTime());
104 mConflictResolver->setLatestDate(mDateTime->endDate());
105 mConflictResolver->setLatestTime(mDateTime->endTime());
106
107 connect(mUi->mSelectButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSelectAddresses);
108 connect(mUi->mSolveButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSolveConflictPressed);
109 /* Added as part of kolab/issue2297, which is currently under review
110 connect(mUi->mOrganizerCombo, qOverload<const QString &>(&QComboBox::activated),
111 this, &IncidenceAttendee::slotOrganizerChanged);
112 */
113 connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::checkDirtyStatus);
114 connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::slotUpdateCryptoPreferences);
115
116 connect(mDateTime, &IncidenceDateTime::startDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
117 connect(mDateTime, &IncidenceDateTime::endDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
118 connect(mDateTime, &IncidenceDateTime::startTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
119 connect(mDateTime, &IncidenceDateTime::endTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
120
121 connect(mConflictResolver, &ConflictResolver::conflictsDetected, this, &IncidenceAttendee::slotUpdateConflictLabel);
122
123 connect(mConflictResolver->model(), &QAbstractItemModel::rowsInserted, this, &IncidenceAttendee::slotFreeBusyAdded);
124 connect(mConflictResolver->model(), &QAbstractItemModel::layoutChanged, this, qOverload<>(&IncidenceAttendee::updateFBStatus));
125 connect(mConflictResolver->model(), &QAbstractItemModel::dataChanged, this, &IncidenceAttendee::slotFreeBusyChanged);
126
127 slotUpdateConflictLabel(0); // initialize label
128
129 // conflict resolver (should show also resources)
130 connect(mDataModel, &AttendeeTableModel::layoutChanged, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
131 connect(mDataModel, &AttendeeTableModel::modelReset, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
132 connect(mDataModel, &AttendeeTableModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotConflictResolverAttendeeRemoved);
133 connect(mDataModel, &AttendeeTableModel::rowsInserted, this, &IncidenceAttendee::slotConflictResolverAttendeeAdded);
134 connect(mDataModel, &AttendeeTableModel::dataChanged, this, &IncidenceAttendee::slotConflictResolverAttendeeChanged);
135
136 // Group substitution
137 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
138 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
139 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved);
140 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeAdded);
141 connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeChanged);
142
143 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::updateCount);
144 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsRemoved, this, &IncidenceAttendee::updateCount);
145 // only update when FullName is changed
146 connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::updateCount);
147 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::updateCount);
148 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::filterLayoutChanged);
149 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::updateCount);
150 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::filterLayoutChanged);
151}
152
153IncidenceAttendee::~IncidenceAttendee() = default;
154
155void IncidenceAttendee::load(const KCalendarCore::Incidence::Ptr &incidence)
156{
157 mLoadedIncidence = incidence;
158
159 if (iAmOrganizer() || incidence->organizer().isEmpty()) {
160 mUi->mOrganizerStack->setCurrentIndex(0);
161
162 int found = -1;
163 const QString fullOrganizer = incidence->organizer().fullName();
164 const QString organizerEmail = incidence->organizer().email();
165 for (int i = 0; i < mUi->mOrganizerCombo->count(); ++i) {
166 KCalendarCore::Person organizerCandidate = KCalendarCore::Person::fromFullName(mUi->mOrganizerCombo->itemText(i));
167 if (organizerCandidate.email() == organizerEmail) {
168 found = i;
169 mUi->mOrganizerCombo->setCurrentIndex(i);
170 break;
171 }
172 }
173 if (found < 0 && !fullOrganizer.isEmpty()) {
174 mUi->mOrganizerCombo->insertItem(0, fullOrganizer);
175 mUi->mOrganizerCombo->setCurrentIndex(0);
176 }
177
178 mUi->mOrganizerLabel->setVisible(false);
179 } else { // someone else is the organizer
180 mUi->mOrganizerStack->setCurrentIndex(1);
181 mUi->mOrganizerLabel->setText(incidence->organizer().fullName());
182 mUi->mOrganizerLabel->setVisible(true);
183 }
184
186 const KCalendarCore::Attendee::List incidenceAttendees = incidence->attendees();
187 attendees.reserve(incidenceAttendees.count());
188 for (const KCalendarCore::Attendee &a : incidenceAttendees) {
189 attendees << KCalendarCore::Attendee(a);
190 }
191
192 mDataModel->setAttendees(attendees);
193 slotUpdateConflictLabel(0);
194
195 setActions(incidence->type());
196
197 mWasDirty = false;
198}
199
200void IncidenceAttendee::save(const KCalendarCore::Incidence::Ptr &incidence)
201{
202 incidence->clearAttendees();
203 const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
204
205 for (const KCalendarCore::Attendee &attendee : attendees) {
206 bool skip = false;
207 if (attendee.fullName().isEmpty()) {
208 continue;
209 }
210 if (KEmailAddress::isValidAddress(attendee.email())) {
212 i18nc("@info",
213 "%1 does not look like a valid email address. "
214 "Are you sure you want to invite this participant?",
215 attendee.email()),
216 i18nc("@title:window", "Invalid Email Address"),
217 KGuiItem(i18nc("@action:button", "Invite"), QStringLiteral("dialog-ok")),
218 KGuiItem(i18nc("@action:button", "Do Not Invite"), QStringLiteral("dialog-cancel")))
219 != KMessageBox::ButtonCode::PrimaryAction) {
220 skip = true;
221 }
222 }
223 if (!skip) {
224 incidence->addAttendee(attendee);
225 }
226 }
227
228 // Must not have an organizer for items without attendees
229 if (!incidence->attendeeCount()) {
230 return;
231 }
232
233 if (mUi->mOrganizerStack->currentIndex() == 0) {
234 incidence->setOrganizer(mUi->mOrganizerCombo->currentText());
235 } else {
236 incidence->setOrganizer(mUi->mOrganizerLabel->text());
237 }
238}
239
240bool IncidenceAttendee::isDirty() const
241{
242 if (iAmOrganizer()) {
244 tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
245
246 if (mLoadedIncidence->organizer().email() != tmp.organizer().email()) {
247 qCDebug(INCIDENCEEDITOR_LOG) << "Organizer changed. Old was " << mLoadedIncidence->organizer().name() << mLoadedIncidence->organizer().email()
248 << "; new is " << tmp.organizer().name() << tmp.organizer().email();
249 return true;
250 }
251 }
252
253 const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
255
256 const auto lstAttendees = mDataModel->attendees();
257 for (const KCalendarCore::Attendee &attendee : lstAttendees) {
258 if (!attendee.fullName().isEmpty()) {
259 newList.append(attendee);
260 }
261 }
262
263 // The lists sizes *must* be the same. When the organizer is attending the
264 // event as well, he should be in the attendees list as well.
265 if (originalList.size() != newList.size()) {
266 return true;
267 }
268
269 // Okay, again not the most efficient algorithm, but I'm assuming that in the
270 // bulk of the use cases, the number of attendees is not much higher than 10 or so.
271 for (const KCalendarCore::Attendee &attendee : originalList) {
272 bool found = false;
273 for (int i = 0; i < newList.count(); ++i) {
274 if (newList[i] == attendee) {
275 newList.remove(i);
276 found = true;
277 break;
278 }
279 }
280
281 if (!found) {
282 // One of the attendees in the original list was not found in the new list.
283 return true;
284 }
285 }
286
287 return false;
288}
289
290void IncidenceAttendee::changeStatusForMe(KCalendarCore::Attendee::PartStat stat)
291{
292 const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
293 Q_ASSERT(config);
294
295 for (int i = 0; i < mDataModel->rowCount(); ++i) {
296 QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email);
297 if (config->thatIsMe(mDataModel->data(index, Qt::DisplayRole).toString())) {
298 index = mDataModel->index(i, AttendeeTableModel::Status);
299 mDataModel->setData(index, stat);
300 break;
301 }
302 }
303
305}
306
307void IncidenceAttendee::acceptForMe()
308{
309 changeStatusForMe(KCalendarCore::Attendee::Accepted);
310}
311
312void IncidenceAttendee::declineForMe()
313{
314 changeStatusForMe(KCalendarCore::Attendee::Declined);
315}
316
317void IncidenceAttendee::fillOrganizerCombo()
318{
319 mUi->mOrganizerCombo->clear();
320 const auto organizers = IncidenceEditorNG::EditorConfig::instance()->allOrganizers();
321 for (auto organizer = organizers.cbegin(), end = organizers.cend(); organizer != end; ++organizer) {
322 if (std::any_of(organizers.cbegin(), organizer, [organizer](const auto &pastOrg) {
323 return organizer->email == pastOrg.email && organizer->name == pastOrg.name;
324 })) {
325 continue;
326 }
327
328 mUi->mOrganizerCombo->addItem(QStringLiteral("%1 <%2>").arg(organizer->name, organizer->email), QVariant::fromValue(*organizer));
329 }
330}
331
332void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalendarCore::Attendee &attendee)
333{
334 QString fullname = attendee.fullName();
335
336 // stop old job
337 KJob *oldJob = mMightBeGroupJobs.key(attendee.uid());
338 if (oldJob != nullptr) {
339 disconnect(oldJob);
340 oldJob->deleteLater();
341 mMightBeGroupJobs.remove(oldJob);
342 }
343
344 mGroupList.remove(attendee.uid());
345
346 if (!fullname.isEmpty()) {
347 auto job = new Akonadi::ContactGroupSearchJob();
348 job->setQuery(Akonadi::ContactGroupSearchJob::Name, fullname);
349 connect(job, &Akonadi::ContactGroupSearchJob::result, this, &IncidenceAttendee::groupSearchResult);
350
351 mMightBeGroupJobs.insert(job, attendee.uid());
352 }
353}
354
355void IncidenceAttendee::groupSearchResult(KJob *job)
356{
358 Q_ASSERT(searchJob);
359
360 Q_ASSERT(mMightBeGroupJobs.contains(job));
361 const auto uid = mMightBeGroupJobs.take(job);
362
363 const KContacts::ContactGroup::List contactGroups = searchJob->contactGroups();
364 if (contactGroups.isEmpty()) {
365 updateGroupExpand();
366 return; // Nothing todo, probably a normal email address was entered
367 }
368
369 // TODO: Give the user the possibility to choose a group when there is more than one?!
370 KContacts::ContactGroup group = contactGroups.first();
371
372 const int row = rowOfAttendee(uid);
373 QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType);
374 dataModel()->setData(index, KCalendarCore::Attendee::Group);
375
376 mGroupList.insert(uid, group);
377 updateGroupExpand();
378}
379
380void IncidenceAttendee::updateGroupExpand()
381{
382 mUi->mGroupSubstitution->setEnabled(!mGroupList.isEmpty());
383}
384
385void IncidenceAttendee::slotGroupSubstitutionPressed()
386{
387 for (auto it = mGroupList.cbegin(), end = mGroupList.cend(); it != end; ++it) {
388 auto expandJob = new Akonadi::ContactGroupExpandJob(it.value(), this);
389 connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
390 mExpandGroupJobs.insert(expandJob, it.key());
391 expandJob->start();
392 }
393}
394
395void IncidenceAttendee::expandResult(KJob *job)
396{
398 Q_ASSERT(expandJob);
399 Q_ASSERT(mExpandGroupJobs.contains(job));
400 const auto uid = mExpandGroupJobs.take(job);
401 const int row = rowOfAttendee(uid);
402 const auto attendee = dataModel()->attendees().at(row);
403 const QString currentEmail = attendee.email();
404 const KContacts::Addressee::List groupMembers = expandJob->contacts();
405 bool wasACorrectEmail = false;
406 for (const KContacts::Addressee &member : groupMembers) {
407 if (member.preferredEmail() == currentEmail) {
408 wasACorrectEmail = true;
409 break;
410 }
411 }
412
413 if (!wasACorrectEmail) {
414 dataModel()->removeRow(row);
415 for (const KContacts::Addressee &member : groupMembers) {
416 KCalendarCore::Attendee newAt(member.realName(), member.preferredEmail(), attendee.RSVP(), attendee.status(), attendee.role(), member.uid());
417 dataModel()->insertAttendee(row, newAt);
418 }
419 }
420}
421
422void IncidenceAttendee::insertAddresses(const KContacts::Addressee::List &list)
423{
424 for (const KContacts::Addressee &contact : list) {
425 insertAttendeeFromAddressee(contact);
426 }
427}
428
429void IncidenceAttendee::slotSelectAddresses()
430{
432 const KPluginMetaData editWidgetPlugin(QStringLiteral("pim6/akonadi/emailaddressselectionldapdialogplugin"));
433
434 const auto result = KPluginFactory::instantiatePlugin<Akonadi::AbstractEmailAddressSelectionDialog>(editWidgetPlugin, mParentWidget);
435 if (result) {
436 dialog = result.plugin;
437
438 } else {
439 dialog = new Akonadi::EmailAddressSelectionDialog(mParentWidget);
440 }
441 dialog->view()->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
442 dialog->setWindowTitle(i18nc("@title:window", "Select Attendees"));
443 connect(dialog.data(), &Akonadi::AbstractEmailAddressSelectionDialog::insertAddresses, this, &IncidenceEditorNG::IncidenceAttendee::insertAddresses);
444 if (dialog->exec() == QDialog::Accepted) {
445 const Akonadi::EmailAddressSelection::List list = dialog->selectedAddresses();
446 for (const Akonadi::EmailAddressSelection &selection : list) {
447 if (selection.item().hasPayload<KContacts::ContactGroup>()) {
448 auto job = new Akonadi::ContactGroupExpandJob(selection.item().payload<KContacts::ContactGroup>(), this);
449 connect(job, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
451 bool rsvp = true;
452
453 int pos = 0;
455 QString email;
456 KEmailAddress::extractEmailAddressAndName(selection.email(), email, name);
457 KCalendarCore::Attendee newAt(selection.name(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant);
458 dataModel()->insertAttendee(pos, newAt);
459
460 mExpandGroupJobs.insert(job, newAt.uid());
461 job->start();
462 } else {
463 KContacts::Addressee contact;
464 contact.setName(selection.name());
465 contact.addEmail(KContacts::Email(selection.email()));
466
467 if (selection.item().hasPayload<KContacts::Addressee>()) {
468 contact.setUid(selection.item().payload<KContacts::Addressee>().uid());
469 }
470 insertAttendeeFromAddressee(contact);
471 }
472 }
473 }
474 delete dialog;
475}
476
477void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed()
478{
479 const int duration = mDateTime->startTime().secsTo(mDateTime->endTime());
480 QScopedPointer<SchedulingDialog> dialog(new SchedulingDialog(mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget));
481 dialog->slotUpdateIncidenceStartEnd(mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime());
482 if (dialog->exec() == QDialog::Accepted) {
483 qCDebug(INCIDENCEEDITOR_LOG) << dialog->selectedStartDate() << dialog->selectedStartTime();
484 if (dialog->selectedStartDate().isValid() && dialog->selectedStartTime().isValid()) {
485 mDateTime->setStartDate(dialog->selectedStartDate());
486 mDateTime->setStartTime(dialog->selectedStartTime());
487 }
488 }
489}
490
491void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
492{
493 if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
494 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
495 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
496 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
497 if (mConflictResolver->containsAttendee(attendee)) {
498 mConflictResolver->removeAttendee(attendee);
499 }
500 if (!dataModel()->data(email).toString().isEmpty()) {
501 mConflictResolver->insertAttendee(attendee);
502 }
503 }
504 }
506}
507
508void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last)
509{
510 for (int i = first; i <= last; ++i) {
511 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
512 if (!dataModel()->data(email).toString().isEmpty()) {
513 mConflictResolver->insertAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
514 }
515 }
517}
518
519void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last)
520{
521 for (int i = first; i <= last; ++i) {
522 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
523 if (!dataModel()->data(email).toString().isEmpty()) {
524 mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
525 }
526 }
528}
529
530void IncidenceAttendee::slotConflictResolverLayoutChanged()
531{
532 const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
533 mConflictResolver->clearAttendees();
534 for (const KCalendarCore::Attendee &attendee : attendees) {
535 if (!attendee.email().isEmpty()) {
536 mConflictResolver->insertAttendee(attendee);
537 }
538 }
540}
541
542void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last)
543{
544 // We are only interested in toplevel changes
545 if (parent.isValid()) {
546 return;
547 }
548 QAbstractItemModel *model = mConflictResolver->model();
549 for (int i = first; i <= last; ++i) {
550 QModelIndex index = model->index(i, 0, parent);
551 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
552 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
553 if (!attendee.isNull()) {
554 updateFBStatus(attendee, fb);
555 }
556 }
557}
558
559void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
560{
561 // We are only interested in toplevel changes
562 if (topLeft.parent().isValid()) {
563 return;
564 }
565 QAbstractItemModel *model = mConflictResolver->model();
566 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
567 QModelIndex index = model->index(i, 0);
568 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
569 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
570 if (!attendee.isNull()) {
571 updateFBStatus(attendee, fb);
572 }
573 }
574}
575
576void IncidenceAttendee::updateFBStatus()
577{
578 QAbstractItemModel *model = mConflictResolver->model();
579 for (int i = 0; i < model->rowCount(); ++i) {
580 QModelIndex index = model->index(i, 0);
581 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
582 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
583 if (!attendee.isNull()) {
584 updateFBStatus(attendee, fb);
585 }
586 }
587}
588
589void IncidenceAttendee::updateFBStatus(const KCalendarCore::Attendee &attendee, const KCalendarCore::FreeBusy::Ptr &fb)
590{
591 KCalendarCore::Attendee::List attendees = mDataModel->attendees();
592 QDateTime startTime = mDateTime->currentStartDateTime();
593 QDateTime endTime = mDateTime->currentEndDateTime();
594 if (attendees.contains(attendee)) {
595 int row = dataModel()->attendees().indexOf(attendee);
596 QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available);
597 if (fb) {
598 KCalendarCore::Period::List busyPeriods = fb->busyPeriods();
599 for (auto it = busyPeriods.begin(); it != busyPeriods.end(); ++it) {
600 // periods started before and lapping into the incidence (s < startTime && e >= startTime)
601 // periods starting in the time of incidence (s >= startTime && s <= endTime)
602 if (((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime)) {
603 switch (attendee.status()) {
605 dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted);
606 return;
607 default:
608 dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy);
609 return;
610 }
611 }
612 }
613 dataModel()->setData(attendeeIndex, AttendeeTableModel::Free);
614 } else {
615 dataModel()->setData(attendeeIndex, AttendeeTableModel::Unknown);
616 }
617 }
618}
619
620void IncidenceAttendee::slotUpdateConflictLabel(int count)
621{
622 if (attendeeCount() > 0) {
623 mUi->mSolveButton->setEnabled(true);
624 if (count > 0) {
625 QString label = i18ncp("@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count);
626 mUi->mConflictsLabel->setText(label);
627 mUi->mConflictsLabel->setVisible(true);
628 } else {
629 mUi->mConflictsLabel->setVisible(false);
630 }
631 } else {
632 mUi->mSolveButton->setEnabled(false);
633 mUi->mConflictsLabel->setVisible(false);
634 }
635}
636
637void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
638{
639 if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
640 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
641 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
642 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
643 checkIfExpansionIsNeeded(attendee);
644 }
645 }
646 updateGroupExpand();
647}
648
649void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last)
650{
651 Q_UNUSED(index)
652 for (int i = first; i <= last; ++i) {
653 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
654 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
655 checkIfExpansionIsNeeded(attendee);
656 }
657 updateGroupExpand();
658}
659
660void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last)
661{
662 Q_UNUSED(index)
663 for (int i = first; i <= last; ++i) {
664 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
665 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
666 KJob *job = mMightBeGroupJobs.key(attendee.uid());
667 if (job) {
668 disconnect(job);
669 job->deleteLater();
670 mMightBeGroupJobs.remove(job);
671 }
672 job = mExpandGroupJobs.key(attendee.uid());
673 if (job) {
674 disconnect(job);
675 job->deleteLater();
676 mExpandGroupJobs.remove(job);
677 }
678 mGroupList.remove(attendee.uid());
679 }
680 updateGroupExpand();
681}
682
683void IncidenceAttendee::slotGroupSubstitutionLayoutChanged()
684{
685 for (auto it = mMightBeGroupJobs.cbegin(), end = mMightBeGroupJobs.cend(); it != end; ++it) {
686 KJob *job = it.key();
687 disconnect(job);
688 job->deleteLater();
689 }
690
691 for (auto it = mExpandGroupJobs.cbegin(), end = mExpandGroupJobs.cend(); it != end; ++it) {
692 KJob *job = it.key();
693 disconnect(job);
694 job->deleteLater();
695 }
696 mMightBeGroupJobs.clear();
697 mExpandGroupJobs.clear();
698 mGroupList.clear();
699
700 QAbstractItemModel *model = mUi->mAttendeeTable->model();
701 if (!model) {
702 return;
703 }
704 for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
705 QModelIndex index = model->index(i, AttendeeTableModel::FullName);
706 if (!model->data(index).toString().isEmpty()) {
707 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
708 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
709 checkIfExpansionIsNeeded(attendee);
710 }
711 }
712
713 updateGroupExpand();
714}
715
716bool IncidenceAttendee::iAmOrganizer() const
717{
718 if (mLoadedIncidence) {
719 const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
720 return config->thatIsMe(mLoadedIncidence->organizer().email());
721 }
722
723 return true;
724}
725
726void IncidenceAttendee::insertAttendeeFromAddressee(const KContacts::Addressee &a, int pos /*=-1*/)
727{
728 const bool sameAsOrganizer = mUi->mOrganizerCombo && KEmailAddress::compareEmail(a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false);
730 bool rsvp = true;
731
732 if (iAmOrganizer() && sameAsOrganizer) {
734 rsvp = false;
735 }
737 QString email;
739
740 KCalendarCore::Attendee newAt(a.realName(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant, a.uid());
741 if (pos < 0) {
742 pos = dataModel()->rowCount() - 1;
743 }
744
745 dataModel()->insertAttendee(pos, newAt);
746}
747
748void IncidenceAttendee::slotEventDurationChanged()
749{
750 const QDateTime start = mDateTime->currentStartDateTime();
751 const QDateTime end = mDateTime->currentEndDateTime();
752
753 if (start >= end) { // This can happen, especially for todos.
754 return;
755 }
756
757 mConflictResolver->setEarliestDateTime(start);
758 mConflictResolver->setLatestDateTime(end);
759 updateFBStatus();
760}
761
762void IncidenceAttendee::slotOrganizerChanged(const QString &newOrganizer)
763{
764 if (KEmailAddress::compareEmail(newOrganizer, mOrganizer, false)) {
765 return;
766 }
767
769 QString email;
770 bool success = KEmailAddress::extractEmailAddressAndName(newOrganizer, email, name);
771
772 if (!success) {
773 qCWarning(INCIDENCEEDITOR_LOG) << "Could not extract email address and name";
774 return;
775 }
776
777 int currentOrganizerAttendee = -1;
778 int newOrganizerAttendee = -1;
779
780 for (int i = 0; i < mDataModel->rowCount(); ++i) {
781 QModelIndex index = mDataModel->index(i, AttendeeTableModel::FullName);
782 QString fullName = mDataModel->data(index, Qt::DisplayRole).toString();
783 if (fullName == mOrganizer) {
784 currentOrganizerAttendee = i;
785 }
786
787 if (fullName == newOrganizer) {
788 newOrganizerAttendee = i;
789 }
790 }
791
792 int answer;
793 if (currentOrganizerAttendee > -1) {
794 answer = KMessageBox::questionTwoActions(mParentWidget,
795 i18nc("@option",
796 "You are changing the organizer of this event. "
797 "Since the organizer is also attending this event, would you "
798 "like to change the corresponding attendee as well?"),
799 QString(),
800 KGuiItem(i18nc("@action:button", "Change Attendee"), QStringLiteral("dialog-ok")),
801 KGuiItem(i18nc("@action:button", "Do Not Change"), QStringLiteral("dialog-cancel")));
802 } else {
803 answer = KMessageBox::ButtonCode::PrimaryAction;
804 }
805
806 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
807 if (currentOrganizerAttendee > -1) {
808 mDataModel->removeRows(currentOrganizerAttendee, 1);
809 }
810
811 if (newOrganizerAttendee == -1) {
812 bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp.
814
816
817 mDataModel->insertAttendee(mDataModel->rowCount(), newAt);
818 }
819 }
820 mOrganizer = newOrganizer;
821}
822
823AttendeeTableModel *IncidenceAttendee::dataModel() const
824{
825 return mDataModel;
826}
827
828AttendeeComboBoxDelegate *IncidenceAttendee::responseDelegate() const
829{
830 return mResponseDelegate;
831}
832
833AttendeeComboBoxDelegate *IncidenceAttendee::roleDelegate() const
834{
835 return mRoleDelegate;
836}
837
838AttendeeComboBoxDelegate *IncidenceAttendee::stateDelegate() const
839{
840 return mStateDelegate;
841}
842
843AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() const
844{
845 return mAttendeeDelegate;
846}
847
848void IncidenceAttendee::filterLayoutChanged()
849{
850 QHeaderView *headerView = mUi->mAttendeeTable->horizontalHeader();
851 headerView->setSectionResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents);
852 headerView->setSectionResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch);
853 headerView->setSectionResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents);
854 headerView->setSectionResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents);
855 headerView->setSectionHidden(AttendeeTableModel::CuType, true);
856 headerView->setSectionHidden(AttendeeTableModel::Name, true);
857 headerView->setSectionHidden(AttendeeTableModel::Email, true);
858 headerView->setSectionHidden(AttendeeTableModel::Available, true);
859}
860
861void IncidenceAttendee::updateCount()
862{
863 Q_EMIT attendeeCountChanged(attendeeCount());
864
866}
867
868int IncidenceAttendee::attendeeCount() const
869{
870 int c = 0;
871 QModelIndex index;
872 QAbstractItemModel *model = mUi->mAttendeeTable->model();
873 if (!model) {
874 return 0;
875 }
876 for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
877 index = model->index(i, AttendeeTableModel::FullName);
878 if (!model->data(index).toString().isEmpty()) {
879 ++c;
880 }
881 }
882 return c;
883}
884
885void IncidenceAttendee::setActions(KCalendarCore::Incidence::IncidenceType actions)
886{
887 mStateDelegate->clear();
889 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
890 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
891 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
892 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
893 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
894 } else {
895 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
896 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
897 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
898 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
899 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
900 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-complete.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Completed));
901 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-ongoing.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::InProcess));
902 }
903}
904
905void IncidenceAttendee::printDebugInfo() const
906{
907 qCDebug(INCIDENCEEDITOR_LOG) << "I'm organizer : " << iAmOrganizer();
908 qCDebug(INCIDENCEEDITOR_LOG) << "Loaded organizer: " << mLoadedIncidence->organizer().email();
909
910 if (iAmOrganizer()) {
912 tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
913 qCDebug(INCIDENCEEDITOR_LOG) << "Organizer combo: " << tmp.organizer().email();
914 }
915
916 const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
918 qCDebug(INCIDENCEEDITOR_LOG) << "List sizes: " << originalList.count() << newList.count();
919
920 const auto lstAttendees = mDataModel->attendees();
921 for (const KCalendarCore::Attendee &attendee : lstAttendees) {
922 if (!attendee.fullName().isEmpty()) {
923 newList.append(attendee);
924 }
925 }
926
927 // Okay, again not the most efficient algorithm, but I'm assuming that in the
928 // bulk of the use cases, the number of attendees is not much higher than 10 or so.
929 for (const KCalendarCore::Attendee &attendee : originalList) {
930 bool found = false;
931 for (int i = 0; i < newList.count(); ++i) {
932 if (newList[i] == attendee) {
933 newList.remove(i);
934 found = true;
935 break;
936 }
937 }
938
939 if (!found) {
940 qCDebug(INCIDENCEEDITOR_LOG) << "Attendee not found: " << attendee.email() << attendee.name() << attendee.status() << attendee.RSVP()
941 << attendee.role() << attendee.uid() << attendee.cuType() << attendee.delegate() << attendee.delegator()
942 << "; we have:";
943 for (int i = 0, total = newList.count(); i < total; ++i) {
944 const KCalendarCore::Attendee newAttendee = newList[i];
945 qCDebug(INCIDENCEEDITOR_LOG) << "Attendee: " << newAttendee.email() << newAttendee.name() << newAttendee.status() << newAttendee.RSVP()
946 << newAttendee.role() << newAttendee.uid() << newAttendee.cuType() << newAttendee.delegate()
947 << newAttendee.delegator();
948 }
949
950 return;
951 }
952 }
953}
954
955int IncidenceAttendee::rowOfAttendee(const QString &uid) const
956{
957 const auto attendees = dataModel()->attendees();
958 const auto it = std::find_if(attendees.begin(), attendees.end(), [uid](const KCalendarCore::Attendee &att) {
959 return att.uid() == uid;
960 });
961 return std::distance(attendees.begin(), it);
962}
963
964void IncidenceAttendee::slotUpdateCryptoPreferences()
965{
966 const auto idx = mUi->mOrganizerCombo->currentIndex();
967 if (idx < 0) {
968 return;
969 }
970 const auto organizer = mUi->mOrganizerCombo->currentData().value<EditorConfig::Organizer>();
971
972 mUi->mSignItip->setChecked(organizer.sign);
973 mUi->mEncryptItip->setChecked(organizer.encrypt);
974}
975
976#include "moc_incidenceattendee.cpp"
class to show a Icon and Text for an Attendee you have to set the Items via addItem to have a list to...
Takes a list of attendees and event info (e.g., min time start, max time end) fetches their freebusy ...
void clearAttendees()
Clear all attendees.
bool containsAttendee(const KCalendarCore::Attendee &attendee)
Returns whether the resolver contains the attendee.
void insertAttendee(const KCalendarCore::Attendee &attendee)
Add an attendee The attendees free busy info will be fetched and integrated into the resolver.
void removeAttendee(const KCalendarCore::Attendee &attendee)
Removes an attendee The attendee will no longer be considered when resolving conflicts.
void conflictsDetected(int number)
Emitted when there are conflicts.
Configuration details.
virtual QList< Organizer > allOrganizers() const
Returns all email addresses together with the full username for the user.
virtual bool thatIsMe(const QString &email) const
Return true if the given email belongs to the user.
void checkDirtyStatus()
Checks if the dirty status has changed until last check and emits the dirtyStatusChanged signal if ne...
QSharedPointer< IncidenceT > incidence() const
Convenience method to get a pointer for a specific const Incidence Type.
QString name() const
QString fullName() const
QString delegate() const
QString delegator() const
QString uid() const
QString email() const
PartStat status() const
CuType cuType() const
void setOrganizer(const Person &organizer)
QString email() const
QString name() const
static Person fromFullName(const QString &fullName)
void addEmail(const Email &email)
AddresseeList List
QString preferredEmail() const
QString realName() const
void setName(const QString &name)
void setUid(const QString &uid)
QString uid() const
void result(KJob *job)
virtual Q_SCRIPTABLE void start()=0
Q_SCRIPTABLE CaptureState status()
Q_SCRIPTABLE Q_NOREPLY void start()
KCODECS_EXPORT bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
KCODECS_EXPORT bool compareEmail(const QString &email1, const QString &email2, bool matchName)
KCODECS_EXPORT EmailParseResult isValidAddress(const QString &aStr)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
QString fullName(const PartType &type)
char * toString(const EngineQuery &query)
QString name(GameStandardAction id)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString label(StandardShortcut id)
void clicked(bool checked)
virtual QVariant data(const QModelIndex &index, int role) const const=0
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
bool removeRow(int row, const QModelIndex &parent)
virtual int rowCount(const QModelIndex &parent) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
void currentIndexChanged(int index)
void setSectionHidden(int logicalIndex, bool hide)
void setSectionResizeMode(ResizeMode mode)
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
bool contains(const AT &value) const const
qsizetype count() const const
iterator end()
T & first()
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
void remove(qsizetype i, qsizetype n)
void reserve(qsizetype size)
qsizetype size() const const
const_iterator cbegin() const const
const_iterator cend() const const
void clear()
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
Key key(const T &value, const Key &defaultKey) const const
size_type remove(const Key &key)
T take(const Key &key)
int column() const const
bool isValid() const const
QModelIndex parent() const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T qobject_cast(QObject *object)
T * data() const const
bool isEmpty() const const
DisplayRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:03:25 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.