Akonadi Calendar

calendarbase.cpp
1/*
2 SPDX-FileCopyrightText: 2011 Sérgio Martins <sergio.martins@kdab.com>
3 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "calendarbase.h"
9#include "akonadicalendar_debug.h"
10#include "calendarbase_p.h"
11#include "calendarutils.h"
12#include "incidencechanger.h"
13#include <Akonadi/CollectionFetchJob>
14
15#include <KLocalizedString>
16#include <QTimeZone>
17
18using namespace Akonadi;
19using namespace KCalendarCore;
20
21static QString itemToString(const Akonadi::Item &item)
22{
24 QString str;
25 QTextStream stream(&str);
26 stream << item.id() << "; summary=" << incidence->summary() << "; uid=" << incidence->uid() << "; type=" << incidence->type()
27 << "; recurs=" << incidence->recurs() << "; recurrenceId=" << incidence->recurrenceId().toString() << "; dtStart=" << incidence->dtStart().toString()
28 << "; dtEnd=" << incidence->dateTime(Incidence::RoleEnd).toString() << "; parentCollection=" << item.storageCollectionId()
29 << item.parentCollection().displayName();
30
31 return str;
32}
33
34CalendarBasePrivate::CalendarBasePrivate(CalendarBase *qq)
35 : QObject()
36 , mIncidenceChanger(new IncidenceChanger())
37 , q(qq)
38{
39 connect(mIncidenceChanger, &IncidenceChanger::createFinished, this, &CalendarBasePrivate::slotCreateFinished);
40
41 connect(mIncidenceChanger, &IncidenceChanger::deleteFinished, this, &CalendarBasePrivate::slotDeleteFinished);
42
43 connect(mIncidenceChanger, &IncidenceChanger::modifyFinished, this, &CalendarBasePrivate::slotModifyFinished);
44
45 mIncidenceChanger->setDestinationPolicy(IncidenceChanger::DestinationPolicyAsk);
46 mIncidenceChanger->setGroupwareCommunication(false);
47 mIncidenceChanger->setHistoryEnabled(false);
48}
49
50CalendarBasePrivate::~CalendarBasePrivate()
51{
52 delete mIncidenceChanger;
53}
54
55void CalendarBasePrivate::internalInsert(const Akonadi::Item &item)
56{
57 Q_ASSERT(item.isValid());
60
61 if (!incidence) {
62 qCritical() << "Incidence is null. id=" << item.id() << "; hasPayload()=" << item.hasPayload()
63 << "; has incidence=" << item.hasPayload<KCalendarCore::Incidence::Ptr>() << "; mime type=" << item.mimeType();
64 Q_ASSERT(false);
65 return;
66 }
67
68 // qCDebug(AKONADICALENDAR_LOG) << "Inserting incidence in calendar. id=" << item.id() << "uid=" << incidence->uid();
69 const QString uid = incidence->instanceIdentifier();
70
71 if (uid.isEmpty()) {
72 // This code path should never happen
73 qCritical() << "Incidence has empty UID. id=" << item.id() << "; summary=" << incidence->summary() << "Please fix it. Ignoring this incidence.";
74 return;
75 }
76
77 if (mItemIdByUid.contains(uid) && mItemIdByUid[uid] != item.id()) {
78 // We only allow duplicate UIDs if they have the same item id, for example
79 // when using virtual folders.
80#if 0
81 qCWarning(AKONADICALENDAR_LOG) << "Discarding duplicate incidence with instanceIdentifier=" << uid
82 << "and summary " << incidence->summary()
83 << "; recurrenceId() =" << incidence->recurrenceId()
84 << "; new id=" << item.id()
85 << "; existing id=" << mItemIdByUid[uid];
86#endif
87 return;
88 }
89
90 if (incidence->type() == KCalendarCore::Incidence::TypeEvent && !incidence->dtStart().isValid()) {
91 // TODO: make the parser discard them would also be a good idea
92 qCWarning(AKONADICALENDAR_LOG) << "Discarding event with invalid DTSTART. identifier=" << incidence->instanceIdentifier()
93 << "; summary=" << incidence->summary();
94 return;
95 }
96
97 Akonadi::Collection collection = item.parentCollection();
98 if (collection.isValid()) {
99 // Some items don't have collection set
100 if (item.storageCollectionId() != collection.id() && item.storageCollectionId() > -1) {
101 if (mCollections.contains(item.storageCollectionId())) {
102 collection = mCollections.value(item.storageCollectionId());
103 incidence->setReadOnly(!(collection.rights() & Akonadi::Collection::CanChangeItem));
104 } else if (!mCollectionJobs.key(item.storageCollectionId())) {
105 collection = Akonadi::Collection(item.storageCollectionId());
106 auto job = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this);
107 QObject::connect(job, &KJob::result, this, &CalendarBasePrivate::collectionFetchResult);
108 mCollectionJobs.insert(job, collection.id());
109 }
110 } else {
111 mCollections.insert(collection.id(), collection);
112 incidence->setReadOnly(!(collection.rights() & Akonadi::Collection::CanChangeItem));
113 }
114 }
115
116 mItemById.insert(item.id(), item);
117 mItemIdByUid.insert(uid, item.id());
118 mItemsByCollection.insert(item.storageCollectionId(), item);
119
120 if (!incidence->hasRecurrenceId()) {
121 // Insert parent relationships
122 const QString parentUid = incidence->relatedTo();
123 if (!parentUid.isEmpty()) {
124 mParentUidToChildrenUid[parentUid].append(incidence->uid());
125 mUidToParent.insert(uid, parentUid);
126 }
127 }
128
129 incidence->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(item.id()));
130 incidence->setCustomProperty("VOLATILE", "COLLECTION-ID", QString::number(item.storageCollectionId()));
131 // Must be the last one due to re-entrancy
132 const bool result = q->MemoryCalendar::addIncidence(incidence);
133 if (!result) {
134 qCritical() << "Error adding incidence " << itemToString(item);
135 Q_ASSERT(false);
136 }
137}
138
139void CalendarBasePrivate::collectionFetchResult(KJob *job)
140{
141 Akonadi::Collection::Id colid = mCollectionJobs.take(job);
142
143 if (job->error()) {
144 qWarning() << "Error occurred: " << job->errorString();
145 return;
146 }
147
148 auto fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
149
150 const Akonadi::Collection collection = fetchJob->collections().at(0);
151 if (collection.id() != colid) {
152 qCritical() << "Fetched the wrong collection, should fetch: " << colid << "fetched: " << collection.id();
153 }
154
155 bool isReadOnly = !(collection.rights() & Akonadi::Collection::CanChangeItem);
156 const auto lst = mItemsByCollection.values(collection.id());
157 for (const Akonadi::Item &item : lst) {
159 incidence->setReadOnly(isReadOnly);
160 }
161
162 mCollections.insert(collection.id(), collection);
163
164 if (mCollectionJobs.isEmpty()) {
165 Q_EMIT fetchFinished();
166 }
167}
168
169void CalendarBasePrivate::internalRemove(const Akonadi::Item &item)
170{
171 Q_ASSERT(item.isValid());
172
174 if (!tmp) {
175 qCritical() << "CalendarBase::internalRemove1: incidence is null, item.id=" << item.id();
176 return;
177 }
178
179 // We want the one stored in the calendar
180 Incidence::Ptr incidence = q->incidence(tmp->uid(), tmp->recurrenceId());
181
182 // Null incidence means it was deleted via CalendarBase::deleteIncidence(), but then
183 // the ETMCalendar received the monitor notification and tried to delete it again.
184 if (incidence) {
185 q->Calendar::notifyIncidenceAboutToBeDeleted(incidence);
186
187 mItemById.remove(item.id());
188 // qCDebug(AKONADICALENDAR_LOG) << "Deleting incidence from calendar .id=" << item.id() << "uid=" << incidence->uid();
189 mItemIdByUid.remove(incidence->instanceIdentifier());
190
191 mItemsByCollection.remove(item.storageCollectionId(), item);
192
193 if (!incidence->hasRecurrenceId()) {
194 const QString uid = incidence->uid();
195 const QString parentUid = incidence->relatedTo();
196 mParentUidToChildrenUid.remove(uid);
197 if (!parentUid.isEmpty()) {
198 mParentUidToChildrenUid[parentUid].removeAll(uid);
199 mUidToParent.remove(uid);
200 }
201 }
202
203 q->Calendar::setObserversEnabled(false);
204 // Must be the last one due to re-entrancy
205 const bool result = q->MemoryCalendar::deleteIncidence(incidence);
206 q->Calendar::setObserversEnabled(true);
207 q->Calendar::notifyIncidenceDeleted(incidence);
208 if (!result) {
209 qCritical() << "Error removing incidence " << itemToString(item);
210 Q_ASSERT(false);
211 }
212 } else {
213 qCWarning(AKONADICALENDAR_LOG) << "CalendarBase::internalRemove2: incidence is null, item.id=" << itemToString(item);
214 }
215}
216
217void CalendarBasePrivate::slotDeleteFinished(int changeId,
218 const QList<Akonadi::Item::Id> &itemIds,
219 IncidenceChanger::ResultCode resultCode,
220 const QString &errorMessage)
221{
222 Q_UNUSED(changeId)
223 if (resultCode == IncidenceChanger::ResultCodeSuccess) {
224 for (const Akonadi::Item::Id &id : itemIds) {
225 if (mItemById.contains(id)) {
226 internalRemove(mItemById.value(id));
227 }
228 }
229 }
230
231 Q_EMIT q->deleteFinished(resultCode == IncidenceChanger::ResultCodeSuccess, errorMessage);
232}
233
234void CalendarBasePrivate::slotCreateFinished(int changeId, const Akonadi::Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorMessage)
235{
236 Q_UNUSED(changeId)
237 Q_UNUSED(item)
238 if (resultCode == IncidenceChanger::ResultCodeSuccess && !mListensForNewItems) {
239 Q_ASSERT(item.isValid());
241 internalInsert(item);
242 }
243
244 mLastCreationCancelled = (resultCode == IncidenceChanger::ResultCodeUserCanceled);
245
246 Q_EMIT q->createFinished(resultCode == IncidenceChanger::ResultCodeSuccess, errorMessage);
247}
248
249void CalendarBasePrivate::slotModifyFinished(int changeId, const Akonadi::Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorMessage)
250{
251 Q_UNUSED(changeId)
252 Q_UNUSED(item)
253 QString message = errorMessage;
254 if (resultCode == IncidenceChanger::ResultCodeSuccess) {
256 Q_ASSERT(incidence);
257 KCalendarCore::Incidence::Ptr localIncidence = q->incidence(incidence->instanceIdentifier());
258
259 if (localIncidence) {
260 // update our local one
261 *(static_cast<KCalendarCore::IncidenceBase *>(localIncidence.data())) = *(incidence.data());
262 } else {
263 // This shouldn't happen, unless the incidence gets deleted between event loops
264 qCWarning(AKONADICALENDAR_LOG) << "CalendarBasePrivate::slotModifyFinished() Incidence was deleted already probably? id=" << item.id();
265 message = i18n("Could not find incidence to update, it probably was deleted recently.");
266 resultCode = IncidenceChanger::ResultCodeAlreadyDeleted;
267 }
268 }
269 Q_EMIT q->modifyFinished(resultCode == IncidenceChanger::ResultCodeSuccess, message);
270}
271
272void CalendarBasePrivate::handleUidChange(const Akonadi::Item &oldItem, const Akonadi::Item &newItem, const QString &newIdentifier)
273{
274 Q_ASSERT(oldItem.isValid());
275 Incidence::Ptr newIncidence = CalendarUtils::incidence(newItem);
276 Q_ASSERT(newIncidence);
277 Incidence::Ptr oldIncidence = CalendarUtils::incidence(oldItem);
278 Q_ASSERT(oldIncidence);
279
280 const QString newUid = newIncidence->uid();
281 if (mItemIdByUid.contains(newIdentifier)) {
282 Q_ASSERT(false);
283 return;
284 }
285
286 mItemIdByUid[newIdentifier] = newItem.id();
287
288 // Get the real pointer
289 oldIncidence = q->MemoryCalendar::incidence(oldIncidence->uid());
290
291 if (!oldIncidence) {
292 // How can this happen ?
293 qCWarning(AKONADICALENDAR_LOG) << "Couldn't find old incidence";
294 Q_ASSERT(false);
295 return;
296 }
297
298 if (newIncidence->instanceIdentifier() == oldIncidence->instanceIdentifier()) {
299 Q_ASSERT(false); // The reason we're here in the first place
300 return;
301 }
302
303 mItemIdByUid.remove(oldIncidence->instanceIdentifier());
304 const QString oldUid = oldIncidence->uid();
305
306 if (mParentUidToChildrenUid.contains(oldUid)) {
307 Q_ASSERT(!mParentUidToChildrenUid.contains(newIdentifier));
308 QStringList children = mParentUidToChildrenUid.value(oldUid);
309 mParentUidToChildrenUid.insert(newIdentifier, children);
310 mParentUidToChildrenUid.remove(oldUid);
311 }
312
313 // Update internal maps of the base class, MemoryCalendar
314 q->setObserversEnabled(false);
315 q->MemoryCalendar::deleteIncidence(oldIncidence);
316 q->MemoryCalendar::addIncidence(newIncidence);
317
318 newIncidence->setUid(oldUid); // We set and unset just to notify observers of a change.
319 q->setObserversEnabled(true);
320 newIncidence->setUid(newUid);
321}
322
323void CalendarBasePrivate::handleParentChanged(const KCalendarCore::Incidence::Ptr &newIncidence)
324{
325 Q_ASSERT(newIncidence);
326
327 if (newIncidence->hasRecurrenceId()) { // These ones don't/shouldn't have a parent
328 return;
329 }
330
331 const QString originalParentUid = mUidToParent.value(newIncidence->uid());
332 const QString newParentUid = newIncidence->relatedTo();
333
334 if (originalParentUid == newParentUid) {
335 return; // nothing changed
336 }
337
338 if (!originalParentUid.isEmpty()) {
339 // Remove this child from it's old parent:
340 Q_ASSERT(mParentUidToChildrenUid.contains(originalParentUid));
341 mParentUidToChildrenUid[originalParentUid].removeAll(newIncidence->uid());
342 }
343
344 mUidToParent.remove(newIncidence->uid());
345
346 if (!newParentUid.isEmpty()) {
347 // Deliver this child to it's new parent:
348 Q_ASSERT(!mParentUidToChildrenUid[newParentUid].contains(newIncidence->uid()));
349 mParentUidToChildrenUid[newParentUid].append(newIncidence->uid());
350 mUidToParent.insert(newIncidence->uid(), newParentUid);
351 }
352}
353
355 : MemoryCalendar(QTimeZone::systemTimeZone())
356 , d_ptr(new CalendarBasePrivate(this))
357{
359}
360
361CalendarBase::CalendarBase(CalendarBasePrivate *const dd, QObject *parent)
362 : MemoryCalendar(QTimeZone::systemTimeZone())
363 , d_ptr(dd)
364{
366}
367
369
371{
372 Q_D(const CalendarBase);
374 auto it = d->mItemById.constFind(id);
375 if (it != d->mItemById.cend()) {
376 i = *it;
377 }
378 return i;
379}
380
382{
383 Q_D(const CalendarBase);
385
386 if (uid.isEmpty()) {
387 return i;
388 }
389
390 auto it = d->mItemIdByUid.constFind(uid);
391 if (it != d->mItemIdByUid.cend()) {
392 const Akonadi::Item::Id id = *it;
393 auto it2 = d->mItemById.constFind(id);
394 if (it2 == d->mItemById.cend()) {
395 qCritical() << "Item with id " << id << "(uid=" << uid << ") not found, but in uid map";
396 Q_ASSERT_X(false, "CalendarBase::item", "not in mItemById");
397 } else {
398 i = *it2;
399 }
400 } else {
401 qCDebug(AKONADICALENDAR_LOG) << "Can't find any incidence with uid " << uid;
402 }
403 return i;
404}
405
407{
408 return incidence ? item(incidence->instanceIdentifier()) : Item();
409}
410
412{
413 Q_D(const CalendarBase);
414
415 Akonadi::Item::List result;
416 if (id == -1) {
417 result.reserve(d->mItemsByCollection.size());
418 }
419
420 auto it = id == -1 ? d->mItemsByCollection.cbegin() : d->mItemsByCollection.constFind(id);
421 while (it != d->mItemsByCollection.cend() && (id == -1 || it.key() == id)) {
422 result.push_back(*it);
423 ++it;
424 }
425
426 return result;
427}
428
430{
432 items.reserve(incidences.size());
433
435 if (incidence) {
436 items << item(incidence->instanceIdentifier());
437 } else {
438 items << Akonadi::Item();
439 }
440 }
441
442 return items;
443}
444
446{
447 Q_D(const CalendarBase);
449
450 if (d->mItemById.contains(parentId)) {
451 const Akonadi::Item item = d->mItemById.value(parentId);
452 Q_ASSERT(item.isValid());
454
455 if (parent) {
457 } else {
458 Q_ASSERT(false);
459 }
460 }
461
462 return children;
463}
464
466{
467 Q_D(const CalendarBase);
469 const QStringList uids = d->mParentUidToChildrenUid.value(parentUid);
470 for (const QString &uid : uids) {
471 Incidence::Ptr child = incidence(uid);
472 if (child) {
473 children.append(child);
474 } else {
475 qCWarning(AKONADICALENDAR_LOG) << "Invalid child with uid " << uid;
476 }
477 }
478 return children;
479}
480
482{
483 Q_D(const CalendarBase);
485
486 if (d->mItemById.contains(parentId)) {
487 const Akonadi::Item item = d->mItemById.value(parentId);
488 Q_ASSERT(item.isValid());
490
491 if (parent) {
492 children = childItems(parent->uid());
493 } else {
494 Q_ASSERT(false);
495 }
496 }
497
498 return children;
499}
500
502{
503 Q_D(const CalendarBase);
505 const QStringList uids = d->mParentUidToChildrenUid.value(parentUid);
506 for (const QString &uid : uids) {
507 Akonadi::Item child = item(uid);
508 if (child.isValid() && child.hasPayload<KCalendarCore::Incidence::Ptr>()) {
509 children.append(child);
510 } else {
511 qCWarning(AKONADICALENDAR_LOG) << "Invalid child with uid " << uid;
512 }
513 }
514 return children;
515}
516
521
526
531
536
541
546
548{
549 // TODO: Parent for dialogs
551
552 // User canceled on the collection selection dialog
553 if (batchAdding() && d->mBatchInsertionCancelled) {
554 return false;
555 }
556
557 d->mLastCreationCancelled = false;
558
559 Akonadi::Collection collection;
560
561 if (batchAdding() && d->mCollectionForBatchInsertion.isValid()) {
562 collection = d->mCollectionForBatchInsertion;
563 }
564
565 if (incidence->hasRecurrenceId() && !collection.isValid()) {
566 // We are creating an exception, reuse the same collection that the main incidence uses
567 Item mainItem = item(incidence->uid());
568 if (mainItem.isValid()) {
569 collection = Collection(mainItem.storageCollectionId());
570 }
571 }
572
573 const int changeId = d->mIncidenceChanger->createIncidence(incidence, collection);
574
575 if (batchAdding()) {
576 const Akonadi::Collection lastCollection = d->mIncidenceChanger->lastCollectionUsed();
577 if (changeId != -1 && !lastCollection.isValid()) {
578 d->mBatchInsertionCancelled = true;
579 } else if (lastCollection.isValid() && !d->mCollectionForBatchInsertion.isValid()) {
580 d->mCollectionForBatchInsertion = d->mIncidenceChanger->lastCollectionUsed();
581 }
582 }
583
584 return changeId != -1;
585}
586
588{
590 Q_ASSERT(incidence);
591 if (!incidence->hasRecurrenceId() && incidence->recurs()) {
593 }
594 Akonadi::Item item_ = item(incidence->instanceIdentifier());
595 return -1 != d->mIncidenceChanger->deleteIncidence(item_);
596}
597
599{
601 Q_ASSERT(newIncidence);
602 Akonadi::Item item_ = item(newIncidence->instanceIdentifier());
603 item_.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence);
604 return -1 != d->mIncidenceChanger->modifyIncidence(item_);
605}
606
607IncidenceChanger *CalendarBase::incidenceChanger() const
608{
609 Q_D(const CalendarBase);
610 return d->mIncidenceChanger;
611}
612
617
619{
621 d->mCollectionForBatchInsertion = Akonadi::Collection();
622 d->mBatchInsertionCancelled = false;
624}
625
626#include "moc_calendarbase.cpp"
627#include "moc_calendarbase_p.cpp"
The base class for all akonadi aware calendars.
bool addEvent(const KCalendarCore::Event::Ptr &event) override
Adds an Event to the calendar.
bool deleteEvent(const KCalendarCore::Event::Ptr &event) override
Deletes an Event from the calendar.
~CalendarBase() override
Destroys the calendar.
Akonadi::IncidenceChanger * incidenceChanger() const
Returns the IncidenceChanger used by this calendar to make changes in akonadi.
bool addTodo(const KCalendarCore::Todo::Ptr &todo) override
Adds a Todo to the calendar.
CalendarBase(QObject *parent=nullptr)
Constructs a CalendarBase object.
bool addJournal(const KCalendarCore::Journal::Ptr &journal) override
Adds a Journal to the calendar.
void endBatchAdding() override
Tells the Calendar that you stopped adding a batch of incidences.
KCalendarCore::Incidence::List childIncidences(const QString &parentUid) const
Returns the child incidences of the parent identified by parentUid.
bool addIncidence(const KCalendarCore::Incidence::Ptr &incidence) override
Adds an incidence to the calendar.
bool deleteIncidence(const KCalendarCore::Incidence::Ptr &incidence) override
Deletes an incidence from the calendar.
bool modifyIncidence(const KCalendarCore::Incidence::Ptr &newIncidence)
Modifies an incidence.
bool deleteTodo(const KCalendarCore::Todo::Ptr &todo) override
Deletes a Todo from the calendar.
Akonadi::Item::List childItems(const QString &parentUid) const
Returns the child items of the parent identified by parentUid.
Akonadi::Item::List itemList(const KCalendarCore::Incidence::List &incidenceList) const
Returns the item list that corresponds to the incidenceList.
Akonadi::Item::List items(Akonadi::Collection::Id=-1) const
Returns the list of items contained in this calendar that belong to the specified collection.
Akonadi::Item item(const QString &uid) const
Returns the Item containing the incidence with uid uid or an invalid Item if the incidence isn't foun...
bool deleteJournal(const KCalendarCore::Journal::Ptr &journal) override
Deletes a Journal from the calendar.
void startBatchAdding() override
Call this to tell the calendar that you're adding a batch of incidences.
bool isValid() const
QString displayName() const
Rights rights() const
void setPayload(const T &p)
QString mimeType() const
Collection & parentCollection()
bool hasPayload() const
Id id() const
Collection::Id storageCollectionId() const
bool isValid() const
QList< Item > List
Incidence::Ptr incidence(const QString &uid, const QDateTime &recurrenceId={}) const
virtual Incidence::List incidences() const
virtual void startBatchAdding()
bool batchAdding() const
virtual void endBatchAdding()
QSharedPointer< Event > Ptr
QSharedPointer< Incidence > Ptr
QSharedPointer< Journal > Ptr
bool deleteIncidenceInstances(const Incidence::Ptr &incidence) override
Todo::Ptr todo(const QString &uid, const QDateTime &recurrenceId={}) const override
Event::Ptr event(const QString &uid, const QDateTime &recurrenceId={}) const override
MemoryCalendar(const QByteArray &timeZoneId)
Journal::Ptr journal(const QString &uid, const QDateTime &recurrenceId={}) const override
QSharedPointer< Todo > Ptr
virtual QString errorString() const
int error() const
void result(KJob *job)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
Returns the incidence from an Akonadi item, or a null pointer if the item has no such payload.
FreeBusyManager::Singleton.
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
void push_back(parameter_type value)
void reserve(qsizetype size)
T value(qsizetype i) const const
QObject(QObject *parent)
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
void setParent(QObject *parent)
T * data() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:57:04 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.