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->setDynamicSortFilter(true);
82 filterProxyModel->setSourceModel(mDataModel);
83
84 connect(mUi->mGroupSubstitution, &QPushButton::clicked, this, &IncidenceAttendee::slotGroupSubstitutionPressed);
85
86 mUi->mAttendeeTable->setModel(filterProxyModel);
87
88 mAttendeeDelegate = new AttendeeLineEditDelegate(this);
89
90 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate());
91 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate());
92 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate());
93 mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate());
94
95 mUi->mOrganizerStack->setCurrentIndex(0);
96
97 fillOrganizerCombo();
98 slotUpdateCryptoPreferences();
99 mUi->mSolveButton->setEnabled(false);
100 mUi->mOrganizerLabel->setVisible(false);
101
102 mConflictResolver = new ConflictResolver(parent, parent);
103 mConflictResolver->setEarliestDate(mDateTime->startDate());
104 mConflictResolver->setEarliestTime(mDateTime->startTime());
105 mConflictResolver->setLatestDate(mDateTime->endDate());
106 mConflictResolver->setLatestTime(mDateTime->endTime());
107
108 connect(mUi->mSelectButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSelectAddresses);
109 connect(mUi->mSolveButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSolveConflictPressed);
110 /* Added as part of kolab/issue2297, which is currently under review
111 connect(mUi->mOrganizerCombo, qOverload<const QString &>(&QComboBox::activated),
112 this, &IncidenceAttendee::slotOrganizerChanged);
113 */
114 connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::checkDirtyStatus);
115 connect(mUi->mOrganizerCombo, &QComboBox::currentIndexChanged, this, &IncidenceAttendee::slotUpdateCryptoPreferences);
116
117 connect(mDateTime, &IncidenceDateTime::startDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
118 connect(mDateTime, &IncidenceDateTime::endDateChanged, this, &IncidenceAttendee::slotEventDurationChanged);
119 connect(mDateTime, &IncidenceDateTime::startTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
120 connect(mDateTime, &IncidenceDateTime::endTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged);
121
122 connect(mConflictResolver, &ConflictResolver::conflictsDetected, this, &IncidenceAttendee::slotUpdateConflictLabel);
123
124 connect(mConflictResolver->model(), &QAbstractItemModel::rowsInserted, this, &IncidenceAttendee::slotFreeBusyAdded);
125 connect(mConflictResolver->model(), &QAbstractItemModel::layoutChanged, this, qOverload<>(&IncidenceAttendee::updateFBStatus));
126 connect(mConflictResolver->model(), &QAbstractItemModel::dataChanged, this, &IncidenceAttendee::slotFreeBusyChanged);
127
128 slotUpdateConflictLabel(0); // initialize label
129
130 // conflict resolver (should show also resources)
131 connect(mDataModel, &AttendeeTableModel::layoutChanged, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
132 connect(mDataModel, &AttendeeTableModel::modelReset, this, &IncidenceAttendee::slotConflictResolverLayoutChanged);
133 connect(mDataModel, &AttendeeTableModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotConflictResolverAttendeeRemoved);
134 connect(mDataModel, &AttendeeTableModel::rowsInserted, this, &IncidenceAttendee::slotConflictResolverAttendeeAdded);
135 connect(mDataModel, &AttendeeTableModel::dataChanged, this, &IncidenceAttendee::slotConflictResolverAttendeeChanged);
136
137 // Group substitution
138 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
139 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged);
140 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved);
141 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeAdded);
142 connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeChanged);
143
144 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::updateCount);
145 connect(filterProxyModel, &AttendeeFilterProxyModel::rowsRemoved, this, &IncidenceAttendee::updateCount);
146 // only update when FullName is changed
147 connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::updateCount);
148 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::updateCount);
149 connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::filterLayoutChanged);
150 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::updateCount);
151 connect(filterProxyModel, &AttendeeFilterProxyModel::modelReset, this, &IncidenceAttendee::filterLayoutChanged);
152}
153
154IncidenceAttendee::~IncidenceAttendee() = default;
155
156void IncidenceAttendee::load(const KCalendarCore::Incidence::Ptr &incidence)
157{
158 mLoadedIncidence = incidence;
159
160 if (iAmOrganizer() || incidence->organizer().isEmpty()) {
161 mUi->mOrganizerStack->setCurrentIndex(0);
162
163 int found = -1;
164 const QString fullOrganizer = incidence->organizer().fullName();
165 const QString organizerEmail = incidence->organizer().email();
166 for (int i = 0; i < mUi->mOrganizerCombo->count(); ++i) {
167 KCalendarCore::Person organizerCandidate = KCalendarCore::Person::fromFullName(mUi->mOrganizerCombo->itemText(i));
168 if (organizerCandidate.email() == organizerEmail) {
169 found = i;
170 mUi->mOrganizerCombo->setCurrentIndex(i);
171 break;
172 }
173 }
174 if (found < 0 && !fullOrganizer.isEmpty()) {
175 mUi->mOrganizerCombo->insertItem(0, fullOrganizer);
176 mUi->mOrganizerCombo->setCurrentIndex(0);
177 }
178
179 mUi->mOrganizerLabel->setVisible(false);
180 } else { // someone else is the organizer
181 mUi->mOrganizerStack->setCurrentIndex(1);
182 mUi->mOrganizerLabel->setText(incidence->organizer().fullName());
183 mUi->mOrganizerLabel->setVisible(true);
184 }
185
187 const KCalendarCore::Attendee::List incidenceAttendees = incidence->attendees();
188 attendees.reserve(incidenceAttendees.count());
189 for (const KCalendarCore::Attendee &a : incidenceAttendees) {
190 attendees << KCalendarCore::Attendee(a);
191 }
192
193 mDataModel->setAttendees(attendees);
194 slotUpdateConflictLabel(0);
195
196 setActions(incidence->type());
197
198 mWasDirty = false;
199}
200
201void IncidenceAttendee::save(const KCalendarCore::Incidence::Ptr &incidence)
202{
203 incidence->clearAttendees();
204 const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
205
206 for (const KCalendarCore::Attendee &attendee : attendees) {
207 bool skip = false;
208 if (attendee.fullName().isEmpty()) {
209 continue;
210 }
211 if (KEmailAddress::isValidAddress(attendee.email())) {
213 i18nc("@info",
214 "%1 does not look like a valid email address. "
215 "Are you sure you want to invite this participant?",
216 attendee.email()),
217 i18nc("@title:window", "Invalid Email Address"),
218 KGuiItem(i18nc("@action:button", "Invite"), QStringLiteral("dialog-ok")),
219 KGuiItem(i18nc("@action:button", "Do Not Invite"), QStringLiteral("dialog-cancel")))
220 != KMessageBox::ButtonCode::PrimaryAction) {
221 skip = true;
222 }
223 }
224 if (!skip) {
225 incidence->addAttendee(attendee);
226 }
227 }
228
229 // Must not have an organizer for items without attendees
230 if (!incidence->attendeeCount()) {
231 return;
232 }
233
234 if (mUi->mOrganizerStack->currentIndex() == 0) {
235 incidence->setOrganizer(mUi->mOrganizerCombo->currentText());
236 } else {
237 incidence->setOrganizer(mUi->mOrganizerLabel->text());
238 }
239}
240
241bool IncidenceAttendee::isDirty() const
242{
243 if (iAmOrganizer()) {
245 tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
246
247 if (mLoadedIncidence->organizer().email() != tmp.organizer().email()) {
248 qCDebug(INCIDENCEEDITOR_LOG) << "Organizer changed. Old was " << mLoadedIncidence->organizer().name() << mLoadedIncidence->organizer().email()
249 << "; new is " << tmp.organizer().name() << tmp.organizer().email();
250 return true;
251 }
252 }
253
254 const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
256
257 const auto lstAttendees = mDataModel->attendees();
258 for (const KCalendarCore::Attendee &attendee : lstAttendees) {
259 if (!attendee.fullName().isEmpty()) {
260 newList.append(attendee);
261 }
262 }
263
264 // The lists sizes *must* be the same. When the organizer is attending the
265 // event as well, he should be in the attendees list as well.
266 if (originalList.size() != newList.size()) {
267 return true;
268 }
269
270 // Okay, again not the most efficient algorithm, but I'm assuming that in the
271 // bulk of the use cases, the number of attendees is not much higher than 10 or so.
272 for (const KCalendarCore::Attendee &attendee : originalList) {
273 bool found = false;
274 for (int i = 0; i < newList.count(); ++i) {
275 if (newList[i] == attendee) {
276 newList.remove(i);
277 found = true;
278 break;
279 }
280 }
281
282 if (!found) {
283 // One of the attendees in the original list was not found in the new list.
284 return true;
285 }
286 }
287
288 return false;
289}
290
291void IncidenceAttendee::changeStatusForMe(KCalendarCore::Attendee::PartStat stat)
292{
293 const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
294 Q_ASSERT(config);
295
296 for (int i = 0; i < mDataModel->rowCount(); ++i) {
297 QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email);
298 if (config->thatIsMe(mDataModel->data(index, Qt::DisplayRole).toString())) {
299 index = mDataModel->index(i, AttendeeTableModel::Status);
300 mDataModel->setData(index, stat);
301 break;
302 }
303 }
304
306}
307
308void IncidenceAttendee::acceptForMe()
309{
310 changeStatusForMe(KCalendarCore::Attendee::Accepted);
311}
312
313void IncidenceAttendee::declineForMe()
314{
315 changeStatusForMe(KCalendarCore::Attendee::Declined);
316}
317
318void IncidenceAttendee::fillOrganizerCombo()
319{
320 mUi->mOrganizerCombo->clear();
321 const auto organizers = IncidenceEditorNG::EditorConfig::instance()->allOrganizers();
322 for (auto organizer = organizers.cbegin(), end = organizers.cend(); organizer != end; ++organizer) {
323 if (std::any_of(organizers.cbegin(), organizer, [organizer](const auto &pastOrg) {
324 return organizer->email == pastOrg.email && organizer->name == pastOrg.name;
325 })) {
326 continue;
327 }
328
329 mUi->mOrganizerCombo->addItem(QStringLiteral("%1 <%2>").arg(organizer->name, organizer->email), QVariant::fromValue(*organizer));
330 }
331}
332
333void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalendarCore::Attendee &attendee)
334{
335 QString fullname = attendee.fullName();
336
337 // stop old job
338 KJob *oldJob = mMightBeGroupJobs.key(attendee.uid());
339 if (oldJob != nullptr) {
340 disconnect(oldJob);
341 oldJob->deleteLater();
342 mMightBeGroupJobs.remove(oldJob);
343 }
344
345 mGroupList.remove(attendee.uid());
346
347 if (!fullname.isEmpty()) {
348 auto job = new Akonadi::ContactGroupSearchJob();
349 job->setQuery(Akonadi::ContactGroupSearchJob::Name, fullname);
350 connect(job, &Akonadi::ContactGroupSearchJob::result, this, &IncidenceAttendee::groupSearchResult);
351
352 mMightBeGroupJobs.insert(job, attendee.uid());
353 }
354}
355
356void IncidenceAttendee::groupSearchResult(KJob *job)
357{
359 Q_ASSERT(searchJob);
360
361 Q_ASSERT(mMightBeGroupJobs.contains(job));
362 const auto uid = mMightBeGroupJobs.take(job);
363
364 const KContacts::ContactGroup::List contactGroups = searchJob->contactGroups();
365 if (contactGroups.isEmpty()) {
366 updateGroupExpand();
367 return; // Nothing todo, probably a normal email address was entered
368 }
369
370 // TODO: Give the user the possibility to choose a group when there is more than one?!
371 KContacts::ContactGroup group = contactGroups.first();
372
373 const int row = rowOfAttendee(uid);
374 QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType);
375 dataModel()->setData(index, KCalendarCore::Attendee::Group);
376
377 mGroupList.insert(uid, group);
378 updateGroupExpand();
379}
380
381void IncidenceAttendee::updateGroupExpand()
382{
383 mUi->mGroupSubstitution->setEnabled(!mGroupList.isEmpty());
384}
385
386void IncidenceAttendee::slotGroupSubstitutionPressed()
387{
388 for (auto it = mGroupList.cbegin(), end = mGroupList.cend(); it != end; ++it) {
389 auto expandJob = new Akonadi::ContactGroupExpandJob(it.value(), this);
390 connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
391 mExpandGroupJobs.insert(expandJob, it.key());
392 expandJob->start();
393 }
394}
395
396void IncidenceAttendee::expandResult(KJob *job)
397{
399 Q_ASSERT(expandJob);
400 Q_ASSERT(mExpandGroupJobs.contains(job));
401 const auto uid = mExpandGroupJobs.take(job);
402 const int row = rowOfAttendee(uid);
403 const auto attendee = dataModel()->attendees().at(row);
404 const QString currentEmail = attendee.email();
405 const KContacts::Addressee::List groupMembers = expandJob->contacts();
406 bool wasACorrectEmail = false;
407 for (const KContacts::Addressee &member : groupMembers) {
408 if (member.preferredEmail() == currentEmail) {
409 wasACorrectEmail = true;
410 break;
411 }
412 }
413
414 if (!wasACorrectEmail) {
415 dataModel()->removeRow(row);
416 for (const KContacts::Addressee &member : groupMembers) {
417 KCalendarCore::Attendee newAt(member.realName(), member.preferredEmail(), attendee.RSVP(), attendee.status(), attendee.role(), member.uid());
418 dataModel()->insertAttendee(row, newAt);
419 }
420 }
421}
422
423void IncidenceAttendee::insertAddresses(const KContacts::Addressee::List &list)
424{
425 for (const KContacts::Addressee &contact : list) {
426 insertAttendeeFromAddressee(contact);
427 }
428}
429
430void IncidenceAttendee::slotSelectAddresses()
431{
433 const KPluginMetaData editWidgetPlugin(QStringLiteral("pim6/akonadi/emailaddressselectionldapdialogplugin"));
434
435 const auto result = KPluginFactory::instantiatePlugin<Akonadi::AbstractEmailAddressSelectionDialog>(editWidgetPlugin, mParentWidget);
436 if (result) {
437 dialog = result.plugin;
438
439 } else {
440 dialog = new Akonadi::EmailAddressSelectionDialog(mParentWidget);
441 }
442 dialog->view()->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
443 dialog->setWindowTitle(i18nc("@title:window", "Select Attendees"));
444 connect(dialog.data(), &Akonadi::AbstractEmailAddressSelectionDialog::insertAddresses, this, &IncidenceEditorNG::IncidenceAttendee::insertAddresses);
445 if (dialog->exec() == QDialog::Accepted) {
446 const Akonadi::EmailAddressSelection::List list = dialog->selectedAddresses();
447 for (const Akonadi::EmailAddressSelection &selection : list) {
448 if (selection.item().hasPayload<KContacts::ContactGroup>()) {
449 auto job = new Akonadi::ContactGroupExpandJob(selection.item().payload<KContacts::ContactGroup>(), this);
450 connect(job, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult);
452 bool rsvp = true;
453
454 int pos = 0;
456 QString email;
457 KEmailAddress::extractEmailAddressAndName(selection.email(), email, name);
458 KCalendarCore::Attendee newAt(selection.name(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant);
459 dataModel()->insertAttendee(pos, newAt);
460
461 mExpandGroupJobs.insert(job, newAt.uid());
462 job->start();
463 } else {
464 KContacts::Addressee contact;
465 contact.setName(selection.name());
466 contact.addEmail(KContacts::Email(selection.email()));
467
468 if (selection.item().hasPayload<KContacts::Addressee>()) {
469 contact.setUid(selection.item().payload<KContacts::Addressee>().uid());
470 }
471 insertAttendeeFromAddressee(contact);
472 }
473 }
474 }
475 delete dialog;
476}
477
478void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed()
479{
480 const int duration = mDateTime->startTime().secsTo(mDateTime->endTime());
481 QScopedPointer<SchedulingDialog> dialog(new SchedulingDialog(mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget));
482 dialog->slotUpdateIncidenceStartEnd(mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime());
483 if (dialog->exec() == QDialog::Accepted) {
484 qCDebug(INCIDENCEEDITOR_LOG) << dialog->selectedStartDate() << dialog->selectedStartTime();
485 if (dialog->selectedStartDate().isValid() && dialog->selectedStartTime().isValid()) {
486 mDateTime->setStartDate(dialog->selectedStartDate());
487 mDateTime->setStartTime(dialog->selectedStartTime());
488 }
489 }
490}
491
492void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
493{
494 if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
495 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
496 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
497 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
498 if (mConflictResolver->containsAttendee(attendee)) {
499 mConflictResolver->removeAttendee(attendee);
500 }
501 if (!dataModel()->data(email).toString().isEmpty()) {
502 mConflictResolver->insertAttendee(attendee);
503 }
504 }
505 }
507}
508
509void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last)
510{
511 for (int i = first; i <= last; ++i) {
512 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
513 if (!dataModel()->data(email).toString().isEmpty()) {
514 mConflictResolver->insertAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
515 }
516 }
518}
519
520void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last)
521{
522 for (int i = first; i <= last; ++i) {
523 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index);
524 if (!dataModel()->data(email).toString().isEmpty()) {
525 mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>());
526 }
527 }
529}
530
531void IncidenceAttendee::slotConflictResolverLayoutChanged()
532{
533 const KCalendarCore::Attendee::List attendees = mDataModel->attendees();
534 mConflictResolver->clearAttendees();
535 for (const KCalendarCore::Attendee &attendee : attendees) {
536 if (!attendee.email().isEmpty()) {
537 mConflictResolver->insertAttendee(attendee);
538 }
539 }
541}
542
543void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last)
544{
545 // We are only interested in toplevel changes
546 if (parent.isValid()) {
547 return;
548 }
549 QAbstractItemModel *model = mConflictResolver->model();
550 for (int i = first; i <= last; ++i) {
551 QModelIndex index = model->index(i, 0, parent);
552 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
553 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
554 if (!attendee.isNull()) {
555 updateFBStatus(attendee, fb);
556 }
557 }
558}
559
560void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
561{
562 // We are only interested in toplevel changes
563 if (topLeft.parent().isValid()) {
564 return;
565 }
566 QAbstractItemModel *model = mConflictResolver->model();
567 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
568 QModelIndex index = model->index(i, 0);
569 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
570 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
571 if (!attendee.isNull()) {
572 updateFBStatus(attendee, fb);
573 }
574 }
575}
576
577void IncidenceAttendee::updateFBStatus()
578{
579 QAbstractItemModel *model = mConflictResolver->model();
580 for (int i = 0; i < model->rowCount(); ++i) {
581 QModelIndex index = model->index(i, 0);
582 const KCalendarCore::Attendee &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value<KCalendarCore::Attendee>();
583 const KCalendarCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value<KCalendarCore::FreeBusy::Ptr>();
584 if (!attendee.isNull()) {
585 updateFBStatus(attendee, fb);
586 }
587 }
588}
589
590void IncidenceAttendee::updateFBStatus(const KCalendarCore::Attendee &attendee, const KCalendarCore::FreeBusy::Ptr &fb)
591{
592 KCalendarCore::Attendee::List attendees = mDataModel->attendees();
593 QDateTime startTime = mDateTime->currentStartDateTime();
594 QDateTime endTime = mDateTime->currentEndDateTime();
595 if (attendees.contains(attendee)) {
596 int row = dataModel()->attendees().indexOf(attendee);
597 QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available);
598 if (fb) {
599 KCalendarCore::Period::List busyPeriods = fb->busyPeriods();
600 for (auto it = busyPeriods.begin(); it != busyPeriods.end(); ++it) {
601 // periods started before and lapping into the incidence (s < startTime && e >= startTime)
602 // periods starting in the time of incidence (s >= startTime && s <= endTime)
603 if (((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime)) {
604 switch (attendee.status()) {
606 dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted);
607 return;
608 default:
609 dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy);
610 return;
611 }
612 }
613 }
614 dataModel()->setData(attendeeIndex, AttendeeTableModel::Free);
615 } else {
616 dataModel()->setData(attendeeIndex, AttendeeTableModel::Unknown);
617 }
618 }
619}
620
621void IncidenceAttendee::slotUpdateConflictLabel(int count)
622{
623 if (attendeeCount() > 0) {
624 mUi->mSolveButton->setEnabled(true);
625 if (count > 0) {
626 QString label = i18ncp("@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count);
627 mUi->mConflictsLabel->setText(label);
628 mUi->mConflictsLabel->setVisible(true);
629 } else {
630 mUi->mConflictsLabel->setVisible(false);
631 }
632 } else {
633 mUi->mSolveButton->setEnabled(false);
634 mUi->mConflictsLabel->setVisible(false);
635 }
636}
637
638void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
639{
640 if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) {
641 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
642 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
643 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
644 checkIfExpansionIsNeeded(attendee);
645 }
646 }
647 updateGroupExpand();
648}
649
650void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last)
651{
652 Q_UNUSED(index)
653 for (int i = first; i <= last; ++i) {
654 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
655 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
656 checkIfExpansionIsNeeded(attendee);
657 }
658 updateGroupExpand();
659}
660
661void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last)
662{
663 Q_UNUSED(index)
664 for (int i = first; i <= last; ++i) {
665 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
666 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
667 KJob *job = mMightBeGroupJobs.key(attendee.uid());
668 if (job) {
669 disconnect(job);
670 job->deleteLater();
671 mMightBeGroupJobs.remove(job);
672 }
673 job = mExpandGroupJobs.key(attendee.uid());
674 if (job) {
675 disconnect(job);
676 job->deleteLater();
677 mExpandGroupJobs.remove(job);
678 }
679 mGroupList.remove(attendee.uid());
680 }
681 updateGroupExpand();
682}
683
684void IncidenceAttendee::slotGroupSubstitutionLayoutChanged()
685{
686 for (auto it = mMightBeGroupJobs.cbegin(), end = mMightBeGroupJobs.cend(); it != end; ++it) {
687 KJob *job = it.key();
688 disconnect(job);
689 job->deleteLater();
690 }
691
692 for (auto it = mExpandGroupJobs.cbegin(), end = mExpandGroupJobs.cend(); it != end; ++it) {
693 KJob *job = it.key();
694 disconnect(job);
695 job->deleteLater();
696 }
697 mMightBeGroupJobs.clear();
698 mExpandGroupJobs.clear();
699 mGroupList.clear();
700
701 QAbstractItemModel *model = mUi->mAttendeeTable->model();
702 if (!model) {
703 return;
704 }
705 for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
706 QModelIndex index = model->index(i, AttendeeTableModel::FullName);
707 if (!model->data(index).toString().isEmpty()) {
708 QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email);
709 auto attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value<KCalendarCore::Attendee>();
710 checkIfExpansionIsNeeded(attendee);
711 }
712 }
713
714 updateGroupExpand();
715}
716
717bool IncidenceAttendee::iAmOrganizer() const
718{
719 if (mLoadedIncidence) {
720 const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance();
721 return config->thatIsMe(mLoadedIncidence->organizer().email());
722 }
723
724 return true;
725}
726
727void IncidenceAttendee::insertAttendeeFromAddressee(const KContacts::Addressee &a, int pos /*=-1*/)
728{
729 const bool sameAsOrganizer = mUi->mOrganizerCombo && KEmailAddress::compareEmail(a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false);
731 bool rsvp = true;
732
733 if (iAmOrganizer() && sameAsOrganizer) {
735 rsvp = false;
736 }
738 QString email;
740
741 KCalendarCore::Attendee newAt(a.realName(), email, rsvp, partStat, KCalendarCore::Attendee::ReqParticipant, a.uid());
742 if (pos < 0) {
743 pos = dataModel()->rowCount() - 1;
744 }
745
746 dataModel()->insertAttendee(pos, newAt);
747}
748
749void IncidenceAttendee::slotEventDurationChanged()
750{
751 const QDateTime start = mDateTime->currentStartDateTime();
752 const QDateTime end = mDateTime->currentEndDateTime();
753
754 if (start >= end) { // This can happen, especially for todos.
755 return;
756 }
757
758 mConflictResolver->setEarliestDateTime(start);
759 mConflictResolver->setLatestDateTime(end);
760 updateFBStatus();
761}
762
763void IncidenceAttendee::slotOrganizerChanged(const QString &newOrganizer)
764{
765 if (KEmailAddress::compareEmail(newOrganizer, mOrganizer, false)) {
766 return;
767 }
768
770 QString email;
771 bool success = KEmailAddress::extractEmailAddressAndName(newOrganizer, email, name);
772
773 if (!success) {
774 qCWarning(INCIDENCEEDITOR_LOG) << "Could not extract email address and name";
775 return;
776 }
777
778 int currentOrganizerAttendee = -1;
779 int newOrganizerAttendee = -1;
780
781 for (int i = 0; i < mDataModel->rowCount(); ++i) {
782 QModelIndex index = mDataModel->index(i, AttendeeTableModel::FullName);
783 QString fullName = mDataModel->data(index, Qt::DisplayRole).toString();
784 if (fullName == mOrganizer) {
785 currentOrganizerAttendee = i;
786 }
787
788 if (fullName == newOrganizer) {
789 newOrganizerAttendee = i;
790 }
791 }
792
793 int answer;
794 if (currentOrganizerAttendee > -1) {
795 answer = KMessageBox::questionTwoActions(mParentWidget,
796 i18nc("@option",
797 "You are changing the organizer of this event. "
798 "Since the organizer is also attending this event, would you "
799 "like to change the corresponding attendee as well?"),
800 QString(),
801 KGuiItem(i18nc("@action:button", "Change Attendee"), QStringLiteral("dialog-ok")),
802 KGuiItem(i18nc("@action:button", "Do Not Change"), QStringLiteral("dialog-cancel")));
803 } else {
804 answer = KMessageBox::ButtonCode::PrimaryAction;
805 }
806
807 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
808 if (currentOrganizerAttendee > -1) {
809 mDataModel->removeRows(currentOrganizerAttendee, 1);
810 }
811
812 if (newOrganizerAttendee == -1) {
813 bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp.
815
817
818 mDataModel->insertAttendee(mDataModel->rowCount(), newAt);
819 }
820 }
821 mOrganizer = newOrganizer;
822}
823
824AttendeeTableModel *IncidenceAttendee::dataModel() const
825{
826 return mDataModel;
827}
828
829AttendeeComboBoxDelegate *IncidenceAttendee::responseDelegate() const
830{
831 return mResponseDelegate;
832}
833
834AttendeeComboBoxDelegate *IncidenceAttendee::roleDelegate() const
835{
836 return mRoleDelegate;
837}
838
839AttendeeComboBoxDelegate *IncidenceAttendee::stateDelegate() const
840{
841 return mStateDelegate;
842}
843
844AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() const
845{
846 return mAttendeeDelegate;
847}
848
849void IncidenceAttendee::filterLayoutChanged()
850{
851 QHeaderView *headerView = mUi->mAttendeeTable->horizontalHeader();
852 headerView->setSectionResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents);
853 headerView->setSectionResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch);
854 headerView->setSectionResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents);
855 headerView->setSectionResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents);
856 headerView->setSectionHidden(AttendeeTableModel::CuType, true);
857 headerView->setSectionHidden(AttendeeTableModel::Name, true);
858 headerView->setSectionHidden(AttendeeTableModel::Email, true);
859 headerView->setSectionHidden(AttendeeTableModel::Available, true);
860}
861
862void IncidenceAttendee::updateCount()
863{
864 Q_EMIT attendeeCountChanged(attendeeCount());
865
867}
868
869int IncidenceAttendee::attendeeCount() const
870{
871 int c = 0;
872 QModelIndex index;
873 QAbstractItemModel *model = mUi->mAttendeeTable->model();
874 if (!model) {
875 return 0;
876 }
877 for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
878 index = model->index(i, AttendeeTableModel::FullName);
879 if (!model->data(index).toString().isEmpty()) {
880 ++c;
881 }
882 }
883 return c;
884}
885
886void IncidenceAttendee::setActions(KCalendarCore::Incidence::IncidenceType actions)
887{
888 mStateDelegate->clear();
890 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
891 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
892 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
893 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
894 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
895 } else {
896 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attention.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction));
897 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-accepted.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted));
898 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-reject.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined));
899 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-attempt.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative));
900 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-delegate.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated));
901 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-complete.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Completed));
902 mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral(":/task-ongoing.png")), KCalUtils::Stringify::attendeeStatus(AttendeeData::InProcess));
903 }
904}
905
906void IncidenceAttendee::printDebugInfo() const
907{
908 qCDebug(INCIDENCEEDITOR_LOG) << "I'm organizer : " << iAmOrganizer();
909 qCDebug(INCIDENCEEDITOR_LOG) << "Loaded organizer: " << mLoadedIncidence->organizer().email();
910
911 if (iAmOrganizer()) {
913 tmp.setOrganizer(mUi->mOrganizerCombo->currentText());
914 qCDebug(INCIDENCEEDITOR_LOG) << "Organizer combo: " << tmp.organizer().email();
915 }
916
917 const KCalendarCore::Attendee::List originalList = mLoadedIncidence->attendees();
919 qCDebug(INCIDENCEEDITOR_LOG) << "List sizes: " << originalList.count() << newList.count();
920
921 const auto lstAttendees = mDataModel->attendees();
922 for (const KCalendarCore::Attendee &attendee : lstAttendees) {
923 if (!attendee.fullName().isEmpty()) {
924 newList.append(attendee);
925 }
926 }
927
928 // Okay, again not the most efficient algorithm, but I'm assuming that in the
929 // bulk of the use cases, the number of attendees is not much higher than 10 or so.
930 for (const KCalendarCore::Attendee &attendee : originalList) {
931 bool found = false;
932 for (int i = 0; i < newList.count(); ++i) {
933 if (newList[i] == attendee) {
934 newList.remove(i);
935 found = true;
936 break;
937 }
938 }
939
940 if (!found) {
941 qCDebug(INCIDENCEEDITOR_LOG) << "Attendee not found: " << attendee.email() << attendee.name() << attendee.status() << attendee.RSVP()
942 << attendee.role() << attendee.uid() << attendee.cuType() << attendee.delegate() << attendee.delegator()
943 << "; we have:";
944 for (int i = 0, total = newList.count(); i < total; ++i) {
945 const KCalendarCore::Attendee newAttendee = newList[i];
946 qCDebug(INCIDENCEEDITOR_LOG) << "Attendee: " << newAttendee.email() << newAttendee.name() << newAttendee.status() << newAttendee.RSVP()
947 << newAttendee.role() << newAttendee.uid() << newAttendee.cuType() << newAttendee.delegate()
948 << newAttendee.delegator();
949 }
950
951 return;
952 }
953 }
954}
955
956int IncidenceAttendee::rowOfAttendee(const QString &uid) const
957{
958 const auto attendees = dataModel()->attendees();
959 const auto it = std::find_if(attendees.begin(), attendees.end(), [uid](const KCalendarCore::Attendee &att) {
960 return att.uid() == uid;
961 });
962 return std::distance(attendees.begin(), it);
963}
964
965void IncidenceAttendee::slotUpdateCryptoPreferences()
966{
967 const auto idx = mUi->mOrganizerCombo->currentIndex();
968 if (idx < 0) {
969 return;
970 }
971 const auto organizer = mUi->mOrganizerCombo->currentData().value<EditorConfig::Organizer>();
972
973 mUi->mSignItip->setChecked(organizer.sign);
974 mUi->mEncryptItip->setChecked(organizer.encrypt);
975}
976
977#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)
AddresseeList List
void addEmail(const Email &email)
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 Q_NOREPLY void start()
Q_SCRIPTABLE CaptureState status()
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 Jul 26 2024 11:52:44 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.