1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling calendar data and
25 defines the Incidence class.
26
27 @brief
28 Provides the class common to non-FreeBusy (Events, To-dos, Journals)
29 calendar components known as incidences.
30
31 @author Cornelius Schumacher \<schumacher@kde.org\>
32 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
33*/
34
35#include "incidence.h"
36#include "calformat.h"
37
38#include <KMimeType>
39#include <KTemporaryFile>
40#include <KDebug>
41
42#include <QTextDocument> // for Qt::escape() and Qt::mightBeRichText()
43#include <QStringList>
44#include <QTime>
45
46using namespace KCalCore;
47
48/**
49 Private class that helps to provide binary compatibility between releases.
50 @internal
51*/
52//@cond PRIVATE
53class KCalCore::Incidence::Private
54{
55public:
56 Private()
57 : mRevision(0),
58 mDescriptionIsRich(false),
59 mSummaryIsRich(false),
60 mLocationIsRich(false),
61 mRecurrence(0),
62 mStatus(StatusNone),
63 mSecrecy(SecrecyPublic),
64 mPriority(0),
65 mGeoLatitude(INVALID_LATLON),
66 mGeoLongitude(INVALID_LATLON),
67 mHasGeo(false),
68 mThisAndFuture(false),
69 mLocalOnly(false)
70 {
71 }
72
73 Private(const Private &p)
74 : mCreated(p.mCreated),
75 mRevision(p.mRevision),
76 mDescription(p.mDescription),
77 mDescriptionIsRich(p.mDescriptionIsRich),
78 mSummary(p.mSummary),
79 mSummaryIsRich(p.mSummaryIsRich),
80 mLocation(p.mLocation),
81 mLocationIsRich(p.mLocationIsRich),
82 mCategories(p.mCategories),
83 mRecurrence(0),
84 mResources(p.mResources),
85 mStatus(p.mStatus),
86 mStatusString(p.mStatusString),
87 mSecrecy(p.mSecrecy),
88 mPriority(p.mPriority),
89 mSchedulingID(p.mSchedulingID),
90 mRelatedToUid(p.mRelatedToUid),
91 mGeoLatitude(p.mGeoLatitude),
92 mGeoLongitude(p.mGeoLongitude),
93 mHasGeo(p.mHasGeo),
94 mRecurrenceId(p.mRecurrenceId),
95 mThisAndFuture(p.mThisAndFuture),
96 mLocalOnly(false)
97 {
98 }
99
100 void clear()
101 {
102 mAlarms.clear();
103 mAttachments.clear();
104 delete mRecurrence;
105 mRecurrence = 0;
106 }
107
108 void init(Incidence *dest, const Incidence &src)
109 {
110 mRevision = src.d->mRevision;
111 mCreated = src.d->mCreated;
112 mDescription = src.d->mDescription;
113 mSummary = src.d->mSummary;
114 mCategories = src.d->mCategories;
115 mRelatedToUid = src.d->mRelatedToUid;
116 mResources = src.d->mResources;
117 mStatusString = src.d->mStatusString;
118 mStatus = src.d->mStatus;
119 mSecrecy = src.d->mSecrecy;
120 mPriority = src.d->mPriority;
121 mLocation = src.d->mLocation;
122 mGeoLatitude = src.d->mGeoLatitude;
123 mGeoLongitude = src.d->mGeoLongitude;
124 mHasGeo = src.d->mHasGeo;
125 mRecurrenceId = src.d->mRecurrenceId;
126 mThisAndFuture = src.d->mThisAndFuture;
127 mLocalOnly = src.d->mLocalOnly;
128
129 // Alarms and Attachments are stored in ListBase<...>, which is a QValueList<...*>.
130 // We need to really duplicate the objects stored therein, otherwise deleting
131 // i will also delete all attachments from this object (setAutoDelete...)
132 foreach(Alarm::Ptr alarm, src.d->mAlarms) {
133 Alarm::Ptr b(new Alarm(*alarm.data()));
134 b->setParent(dest);
135 mAlarms.append(b);
136 }
137
138 foreach(Attachment::Ptr attachment, src.d->mAttachments) {
139 Attachment::Ptr a(new Attachment(*attachment));
140 mAttachments.append(a);
141 }
142
143 if (src.d->mRecurrence) {
144 mRecurrence = new Recurrence(*(src.d->mRecurrence));
145 mRecurrence->addObserver(dest);
146 } else {
147 mRecurrence = 0;
148 }
149 }
150
151 KDateTime mCreated; // creation datetime
152 int mRevision; // revision number
153
154 QString mDescription; // description string
155 bool mDescriptionIsRich; // description string is richtext.
156 QString mSummary; // summary string
157 bool mSummaryIsRich; // summary string is richtext.
158 QString mLocation; // location string
159 bool mLocationIsRich; // location string is richtext.
160 QStringList mCategories; // category list
161 mutable Recurrence *mRecurrence; // recurrence
162 Attachment::List mAttachments; // attachments list
163 Alarm::List mAlarms; // alarms list
164 QStringList mResources; // resources list (not calendar resources)
165 Status mStatus; // status
166 QString mStatusString; // status string, for custom status
167 Secrecy mSecrecy; // secrecy
168 int mPriority; // priority: 1 = highest, 2 = less, etc.
169 QString mSchedulingID; // ID for scheduling mails
170
171 QMap<RelType,QString> mRelatedToUid;// incidence uid this is related to, for each relType
172 float mGeoLatitude; // Specifies latitude in decimal degrees
173 float mGeoLongitude; // Specifies longitude in decimal degrees
174 bool mHasGeo; // if incidence has geo data
175 QHash<Attachment::Ptr,QString> mTempFiles; // Temporary files for writing attachments to.
176 KDateTime mRecurrenceId; // recurrenceId
177 bool mThisAndFuture;
178 bool mLocalOnly; // allow changes that won't go to the server
179};
180//@endcond
181
182Incidence::Incidence()
183 : IncidenceBase(), d(new KCalCore::Incidence::Private)
184{
185 recreate();
186 resetDirtyFields();
187}
188
189Incidence::Incidence(const Incidence &i)
190 : IncidenceBase(i),
191 Recurrence::RecurrenceObserver(),
192 d(new KCalCore::Incidence::Private(*i.d))
193{
194 d->init(this, i);
195 resetDirtyFields();
196}
197
198Incidence::~Incidence()
199{
200 // Alarm has a raw incidence pointer, so we must set it to 0
201 // so Alarm doesn't use it after Incidence is destroyed
202 foreach(Alarm::Ptr alarm, d->mAlarms) {
203 alarm->setParent(0);
204 }
205
206 delete d->mRecurrence;
207 delete d;
208}
209
210//@cond PRIVATE
211// A string comparison that considers that null and empty are the same
212static bool stringCompare(const QString &s1, const QString &s2)
213{
214 return (s1.isEmpty() && s2.isEmpty()) || (s1 == s2);
215}
216
217//@endcond
218IncidenceBase &Incidence::assign(const IncidenceBase &other)
219{
220 if (&other != this) {
221 d->clear();
222 //TODO: should relations be cleared out, as in destructor???
223 IncidenceBase::assign(other);
224 const Incidence *i = static_cast<const Incidence*>(&other);
225 d->init(this, *i);
226 }
227
228 return *this;
229}
230
231bool Incidence::equals(const IncidenceBase &incidence) const
232{
233 if (!IncidenceBase::equals(incidence)) {
234 return false;
235 }
236
237 // If they weren't the same type IncidenceBase::equals would had returned false already
238 const Incidence *i2 = static_cast<const Incidence *>(&incidence);
239
240 if (alarms().count() != i2->alarms().count()) {
241 return false;
242 }
243
244 Alarm::List::ConstIterator a1 = alarms().constBegin();
245 Alarm::List::ConstIterator a1end = alarms().constEnd();
246 Alarm::List::ConstIterator a2 = i2->alarms().constBegin();
247 Alarm::List::ConstIterator a2end = i2->alarms().constEnd();
248 for (; a1 != a1end && a2 != a2end; ++a1, ++a2) {
249 if (**a1 == **a2) {
250 continue;
251 } else {
252 return false;
253 }
254 }
255
256 if (attachments().count() != i2->attachments().count()) {
257 return false;
258 }
259
260 Attachment::List::ConstIterator att1 = attachments().constBegin();
261 const Attachment::List::ConstIterator att1end = attachments().constEnd();
262 Attachment::List::ConstIterator att2 = i2->attachments().constBegin();
263 const Attachment::List::ConstIterator att2end = i2->attachments().constEnd();
264 for (; att1 != att1end && att2 != att2end; ++att1, ++att2) {
265 if (**att1 == **att2) {
266 continue;
267 } else {
268 return false;
269 }
270 }
271
272 bool recurrenceEqual = (d->mRecurrence == 0 && i2->d->mRecurrence == 0);
273 if (!recurrenceEqual) {
274 recurrence(); // create if doesn't exist
275 i2->recurrence(); // create if doesn't exist
276 recurrenceEqual = d->mRecurrence != 0 &&
277 i2->d->mRecurrence != 0 &&
278 *d->mRecurrence == *i2->d->mRecurrence;
279 }
280
281 return
282 recurrenceEqual &&
283 created() == i2->created() &&
284 stringCompare(description(), i2->description()) &&
285 stringCompare(summary(), i2->summary()) &&
286 categories() == i2->categories() &&
287 stringCompare(relatedTo(), i2->relatedTo()) &&
288 resources() == i2->resources() &&
289 d->mStatus == i2->d->mStatus &&
290 (d->mStatus == StatusNone ||
291 stringCompare(d->mStatusString, i2->d->mStatusString)) &&
292 secrecy() == i2->secrecy() &&
293 priority() == i2->priority() &&
294 stringCompare(location(), i2->location()) &&
295 stringCompare(schedulingID(), i2->schedulingID()) &&
296 recurrenceId() == i2->recurrenceId() &&
297 thisAndFuture() == i2->thisAndFuture();
298}
299
300QString Incidence::instanceIdentifier() const
301{
302 if (hasRecurrenceId()) {
303 return uid() + recurrenceId().toString();
304 }
305 return uid();
306}
307
308void Incidence::recreate()
309{
310 const KDateTime nowUTC = KDateTime::currentUtcDateTime();
311 setCreated(nowUTC);
312
313 setSchedulingID(QString(), CalFormat::createUniqueId());
314 setRevision(0);
315 setLastModified(nowUTC);
316}
317
318void Incidence::setLastModified(const KDateTime &lm)
319{
320 if (!d->mLocalOnly) {
321 IncidenceBase::setLastModified(lm);
322 }
323}
324
325void Incidence::setReadOnly(bool readOnly)
326{
327 IncidenceBase::setReadOnly(readOnly);
328 if (d->mRecurrence) {
329 d->mRecurrence->setRecurReadOnly(readOnly);
330 }
331}
332
333void Incidence::setLocalOnly(bool localOnly)
334{
335 if (mReadOnly) {
336 return;
337 }
338 d->mLocalOnly = localOnly;
339}
340
341bool Incidence::localOnly() const
342{
343 return d->mLocalOnly;
344}
345
346void Incidence::setAllDay(bool allDay)
347{
348 if (mReadOnly) {
349 return;
350 }
351 if (d->mRecurrence) {
352 d->mRecurrence->setAllDay(allDay);
353 }
354 IncidenceBase::setAllDay(allDay);
355}
356
357void Incidence::setCreated(const KDateTime &created)
358{
359 if (mReadOnly || d->mLocalOnly) {
360 return;
361 }
362
363 d->mCreated = created.toUtc();
364 setFieldDirty(FieldCreated);
365
366// FIXME: Shouldn't we call updated for the creation date, too?
367// updated();
368}
369
370KDateTime Incidence::created() const
371{
372 return d->mCreated;
373}
374
375void Incidence::setRevision(int rev)
376{
377 if (mReadOnly || d->mLocalOnly) {
378 return;
379 }
380
381 update();
382
383 d->mRevision = rev;
384 setFieldDirty(FieldRevision);
385 updated();
386}
387
388int Incidence::revision() const
389{
390 return d->mRevision;
391}
392
393void Incidence::setDtStart(const KDateTime &dt)
394{
395 if (d->mRecurrence) {
396 d->mRecurrence->setStartDateTime(dt);
397 }
398 IncidenceBase::setDtStart(dt);
399}
400
401void Incidence::shiftTimes(const KDateTime::Spec &oldSpec,
402 const KDateTime::Spec &newSpec)
403{
404 IncidenceBase::shiftTimes(oldSpec, newSpec);
405 if (d->mRecurrence) {
406 d->mRecurrence->shiftTimes(oldSpec, newSpec);
407 }
408 for (int i = 0, end = d->mAlarms.count(); i < end; ++i) {
409 d->mAlarms[i]->shiftTimes(oldSpec, newSpec);
410 }
411}
412
413void Incidence::setDescription(const QString &description, bool isRich)
414{
415 if (mReadOnly) {
416 return;
417 }
418 update();
419 d->mDescription = description;
420 d->mDescriptionIsRich = isRich;
421 setFieldDirty(FieldDescription);
422 updated();
423}
424
425void Incidence::setDescription(const QString &description)
426{
427 setDescription(description, Qt::mightBeRichText(description));
428}
429
430QString Incidence::description() const
431{
432 return d->mDescription;
433}
434
435QString Incidence::richDescription() const
436{
437 if (descriptionIsRich()) {
438 return d->mDescription;
439 } else {
440 return Qt::escape(d->mDescription).replace(QLatin1Char('\n'), QLatin1String("<br/>"));
441 }
442}
443
444bool Incidence::descriptionIsRich() const
445{
446 return d->mDescriptionIsRich;
447}
448
449void Incidence::setSummary(const QString &summary, bool isRich)
450{
451 if (mReadOnly) {
452 return;
453 }
454 update();
455 d->mSummary = summary;
456 d->mSummaryIsRich = isRich;
457 setFieldDirty(FieldSummary);
458 updated();
459}
460
461void Incidence::setSummary(const QString &summary)
462{
463 setSummary(summary, Qt::mightBeRichText(summary));
464}
465
466QString Incidence::summary() const
467{
468 return d->mSummary;
469}
470
471QString Incidence::richSummary() const
472{
473 if (summaryIsRich()) {
474 return d->mSummary;
475 } else {
476 return Qt::escape(d->mSummary).replace(QLatin1Char('\n'), QLatin1String("<br/>"));
477 }
478}
479
480bool Incidence::summaryIsRich() const
481{
482 return d->mSummaryIsRich;
483}
484
485void Incidence::setCategories(const QStringList &categories)
486{
487 if (mReadOnly) {
488 return;
489 }
490
491 update();
492 d->mCategories = categories;
493 updated();
494}
495
496void Incidence::setCategories(const QString &catStr)
497{
498 if (mReadOnly) {
499 return;
500 }
501 update();
502 setFieldDirty(FieldCategories);
503
504 d->mCategories.clear();
505
506 if (catStr.isEmpty()) {
507 updated();
508 return;
509 }
510
511 d->mCategories = catStr.split(QLatin1Char(','));
512
513 QStringList::Iterator it;
514 for (it = d->mCategories.begin(); it != d->mCategories.end(); ++it) {
515 *it = (*it).trimmed();
516 }
517
518 updated();
519}
520
521QStringList Incidence::categories() const
522{
523 return d->mCategories;
524}
525
526QString Incidence::categoriesStr() const
527{
528 return d->mCategories.join(QLatin1String(","));
529}
530
531void Incidence::setRelatedTo(const QString &relatedToUid, RelType relType)
532{
533 // TODO: RFC says that an incidence can have more than one related-to field
534 // even for the same relType.
535
536 if (d->mRelatedToUid[relType] != relatedToUid) {
537 update();
538 d->mRelatedToUid[relType] = relatedToUid;
539 setFieldDirty(FieldRelatedTo);
540 updated();
541 }
542}
543
544QString Incidence::relatedTo(RelType relType) const
545{
546 return d->mRelatedToUid.value(relType);
547}
548
549// %%%%%%%%%%%% Recurrence-related methods %%%%%%%%%%%%%%%%%%%%
550
551Recurrence *Incidence::recurrence() const
552{
553 if (!d->mRecurrence) {
554 d->mRecurrence = new Recurrence();
555 d->mRecurrence->setStartDateTime(dateTime(RoleRecurrenceStart));
556 d->mRecurrence->setAllDay(allDay());
557 d->mRecurrence->setRecurReadOnly(mReadOnly);
558 d->mRecurrence->addObserver(const_cast<KCalCore::Incidence*>(this));
559 }
560
561 return d->mRecurrence;
562}
563
564void Incidence::clearRecurrence()
565{
566 delete d->mRecurrence;
567 d->mRecurrence = 0;
568}
569
570ushort Incidence::recurrenceType() const
571{
572 if (d->mRecurrence) {
573 return d->mRecurrence->recurrenceType();
574 } else {
575 return Recurrence::rNone;
576 }
577}
578
579bool Incidence::recurs() const
580{
581 if (d->mRecurrence) {
582 return d->mRecurrence->recurs();
583 } else {
584 return false;
585 }
586}
587
588bool Incidence::recursOn(const QDate &date,
589 const KDateTime::Spec &timeSpec) const
590{
591 return d->mRecurrence && d->mRecurrence->recursOn(date, timeSpec);
592}
593
594bool Incidence::recursAt(const KDateTime &qdt) const
595{
596 return d->mRecurrence && d->mRecurrence->recursAt(qdt);
597}
598
599QList<KDateTime> Incidence::startDateTimesForDate(const QDate &date,
600 const KDateTime::Spec &timeSpec) const
601{
602 KDateTime start = dtStart();
603 KDateTime end = dateTime(RoleEndRecurrenceBase);
604
605 QList<KDateTime> result;
606
607 // TODO_Recurrence: Also work if only due date is given...
608 if (!start.isValid() && ! end.isValid()) {
609 return result;
610 }
611
612 // if the incidence doesn't recur,
613 KDateTime kdate(date, timeSpec);
614 if (!recurs()) {
615 if (!(start > kdate || end < kdate)) {
616 result << start;
617 }
618 return result;
619 }
620
621 int days = start.daysTo(end);
622 // Account for possible recurrences going over midnight, while the original event doesn't
623 QDate tmpday(date.addDays(-days - 1));
624 KDateTime tmp;
625 while (tmpday <= date) {
626 if (recurrence()->recursOn(tmpday, timeSpec)) {
627 QList<QTime> times = recurrence()->recurTimesOn(tmpday, timeSpec);
628 foreach(const QTime &time, times) {
629 tmp = KDateTime(tmpday, time, start.timeSpec());
630 if (endDateForStart(tmp) >= kdate) {
631 result << tmp;
632 }
633 }
634 }
635 tmpday = tmpday.addDays(1);
636 }
637 return result;
638}
639
640QList<KDateTime> Incidence::startDateTimesForDateTime(const KDateTime &datetime) const
641{
642 KDateTime start = dtStart();
643 KDateTime end = dateTime(RoleEndRecurrenceBase);
644
645 QList<KDateTime> result;
646
647 // TODO_Recurrence: Also work if only due date is given...
648 if (!start.isValid() && ! end.isValid()) {
649 return result;
650 }
651
652 // if the incidence doesn't recur,
653 if (!recurs()) {
654 if (!(start > datetime || end < datetime)) {
655 result << start;
656 }
657 return result;
658 }
659
660 int days = start.daysTo(end);
661 // Account for possible recurrences going over midnight, while the original event doesn't
662 QDate tmpday(datetime.date().addDays(-days - 1));
663 KDateTime tmp;
664 while (tmpday <= datetime.date()) {
665 if (recurrence()->recursOn(tmpday, datetime.timeSpec())) {
666 // Get the times during the day (in start date's time zone) when recurrences happen
667 QList<QTime> times = recurrence()->recurTimesOn(tmpday, start.timeSpec());
668 foreach(const QTime &time, times) {
669 tmp = KDateTime(tmpday, time, start.timeSpec());
670 if (!(tmp > datetime || endDateForStart(tmp) < datetime)) {
671 result << tmp;
672 }
673 }
674 }
675 tmpday = tmpday.addDays(1);
676 }
677 return result;
678}
679
680KDateTime Incidence::endDateForStart(const KDateTime &startDt) const
681{
682 KDateTime start = dtStart();
683 KDateTime end = dateTime(RoleEndRecurrenceBase);
684 if (!end.isValid()) {
685 return start;
686 }
687 if (!start.isValid()) {
688 return end;
689 }
690
691 return startDt.addSecs(start.secsTo(end));
692}
693
694void Incidence::addAttachment(const Attachment::Ptr &attachment)
695{
696 if (mReadOnly || !attachment) {
697 return;
698 }
699
700 Q_ASSERT(!d->mAttachments.contains(attachment));
701
702 update();
703 d->mAttachments.append(attachment);
704 setFieldDirty(FieldAttachment);
705 updated();
706}
707
708void Incidence::deleteAttachment(const Attachment::Ptr &attachment)
709{
710 int index = d->mAttachments.indexOf(attachment);
711 if (index > -1) {
712 setFieldDirty(FieldAttachment);
713 d->mAttachments.remove(index);
714 }
715}
716
717void Incidence::deleteAttachments(const QString &mime)
718{
719 Attachment::List result;
720 Attachment::List::Iterator it = d->mAttachments.begin();
721 while (it != d->mAttachments.end()) {
722 if ((*it)->mimeType() != mime) {
723 result += *it;
724 }
725 ++it;
726 }
727 d->mAttachments = result;
728 setFieldDirty(FieldAttachment);
729}
730
731Attachment::List Incidence::attachments() const
732{
733 return d->mAttachments;
734}
735
736Attachment::List Incidence::attachments(const QString &mime) const
737{
738 Attachment::List attachments;
739 foreach(Attachment::Ptr attachment, d->mAttachments) {
740 if (attachment->mimeType() == mime) {
741 attachments.append(attachment);
742 }
743 }
744 return attachments;
745}
746
747void Incidence::clearAttachments()
748{
749 setFieldDirty(FieldAttachment);
750 d->mAttachments.clear();
751}
752
753QString Incidence::writeAttachmentToTempFile(const Attachment::Ptr &attachment) const
754{
755 if (d->mTempFiles.contains(attachment)) {
756 return d->mTempFiles.value(attachment);
757 }
758 KTemporaryFile *file = new KTemporaryFile();
759
760 QStringList patterns = KMimeType::mimeType(attachment->mimeType())->patterns();
761
762 if (!patterns.empty()) {
763 file->setSuffix(QString(patterns.first()).remove(QLatin1Char('*')));
764 }
765 file->setAutoRemove(true);
766 file->open();
767 // read-only not to give the idea that it could be written to
768 file->setPermissions(QFile::ReadUser);
769 file->write(QByteArray::fromBase64(attachment->data()));
770 d->mTempFiles.insert(attachment, file->fileName());
771 file->close();
772 return d->mTempFiles.value(attachment);
773}
774
775void Incidence::clearTempFiles()
776{
777 QHash<Attachment::Ptr,QString>::const_iterator it = d->mTempFiles.constBegin();
778 const QHash<Attachment::Ptr,QString>::const_iterator end = d->mTempFiles.constEnd();
779 for (; it != end; ++it) {
780 QFile::remove(it.value());
781 }
782 d->mTempFiles.clear();
783}
784
785void Incidence::setResources(const QStringList &resources)
786{
787 if (mReadOnly) {
788 return;
789 }
790
791 update();
792 d->mResources = resources;
793 setFieldDirty(FieldResources);
794 updated();
795}
796
797QStringList Incidence::resources() const
798{
799 return d->mResources;
800}
801
802void Incidence::setPriority(int priority)
803{
804 if (mReadOnly) {
805 return;
806 }
807
808 update();
809 d->mPriority = priority;
810 setFieldDirty(FieldPriority);
811 updated();
812}
813
814int Incidence::priority() const
815{
816 return d->mPriority;
817}
818
819void Incidence::setStatus(Incidence::Status status)
820{
821 if (mReadOnly || status == StatusX) {
822 return;
823 }
824
825 update();
826 d->mStatus = status;
827 d->mStatusString.clear();
828 setFieldDirty(FieldStatus);
829 updated();
830}
831
832void Incidence::setCustomStatus(const QString &status)
833{
834 if (mReadOnly) {
835 return;
836 }
837
838 update();
839 d->mStatus = status.isEmpty() ? StatusNone : StatusX;
840 d->mStatusString = status;
841 setFieldDirty(FieldStatus);
842 updated();
843}
844
845Incidence::Status Incidence::status() const
846{
847 return d->mStatus;
848}
849
850QString Incidence::customStatus() const
851{
852 if (d->mStatus == StatusX) {
853 return d->mStatusString;
854 } else {
855 return QString();
856 }
857}
858
859void Incidence::setSecrecy(Incidence::Secrecy secrecy)
860{
861 if (mReadOnly) {
862 return;
863 }
864
865 update();
866 d->mSecrecy = secrecy;
867 setFieldDirty(FieldSecrecy);
868 updated();
869}
870
871Incidence::Secrecy Incidence::secrecy() const
872{
873 return d->mSecrecy;
874}
875
876Alarm::List Incidence::alarms() const
877{
878 return d->mAlarms;
879}
880
881Alarm::Ptr Incidence::newAlarm()
882{
883 Alarm::Ptr alarm(new Alarm(this));
884 d->mAlarms.append(alarm);
885 return alarm;
886}
887
888void Incidence::addAlarm(const Alarm::Ptr &alarm)
889{
890 update();
891 d->mAlarms.append(alarm);
892 setFieldDirty(FieldAlarms);
893 updated();
894}
895
896void Incidence::removeAlarm(const Alarm::Ptr &alarm)
897{
898 const int index = d->mAlarms.indexOf(alarm);
899 if (index > -1) {
900 update();
901 d->mAlarms.remove(index);
902 setFieldDirty(FieldAlarms);
903 updated();
904 }
905}
906
907void Incidence::clearAlarms()
908{
909 update();
910 d->mAlarms.clear();
911 setFieldDirty(FieldAlarms);
912 updated();
913}
914
915bool Incidence::hasEnabledAlarms() const
916{
917 foreach(Alarm::Ptr alarm, d->mAlarms) {
918 if (alarm->enabled()) {
919 return true;
920 }
921 }
922 return false;
923}
924
925void Incidence::setLocation(const QString &location, bool isRich)
926{
927 if (mReadOnly) {
928 return;
929 }
930
931 update();
932 d->mLocation = location;
933 d->mLocationIsRich = isRich;
934 setFieldDirty(FieldLocation);
935 updated();
936}
937
938void Incidence::setLocation(const QString &location)
939{
940 setLocation(location, Qt::mightBeRichText(location));
941}
942
943QString Incidence::location() const
944{
945 return d->mLocation;
946}
947
948QString Incidence::richLocation() const
949{
950 if (locationIsRich()) {
951 return d->mLocation;
952 } else {
953 return Qt::escape(d->mLocation).replace(QLatin1Char('\n'), QLatin1String("<br/>"));
954 }
955}
956
957bool Incidence::locationIsRich() const
958{
959 return d->mLocationIsRich;
960}
961
962void Incidence::setSchedulingID(const QString &sid, const QString &uid)
963{
964 d->mSchedulingID = sid;
965 if (!uid.isEmpty()) {
966 setUid(uid);
967 }
968 setFieldDirty(FieldSchedulingId);
969}
970
971QString Incidence::schedulingID() const
972{
973 if (d->mSchedulingID.isNull()) {
974 // Nothing set, so use the normal uid
975 return uid();
976 }
977 return d->mSchedulingID;
978}
979
980bool Incidence::hasGeo() const
981{
982 return d->mHasGeo;
983}
984
985void Incidence::setHasGeo(bool hasGeo)
986{
987 if (mReadOnly) {
988 return;
989 }
990
991 if (hasGeo == d->mHasGeo) {
992 return;
993 }
994
995 update();
996 d->mHasGeo = hasGeo;
997 setFieldDirty(FieldGeoLatitude);
998 setFieldDirty(FieldGeoLongitude);
999 updated();
1000}
1001
1002float Incidence::geoLatitude() const
1003{
1004 return d->mGeoLatitude;
1005}
1006
1007void Incidence::setGeoLatitude(float geolatitude)
1008{
1009 if (mReadOnly) {
1010 return;
1011 }
1012
1013 update();
1014 d->mGeoLatitude = geolatitude;
1015 setFieldDirty(FieldGeoLatitude);
1016 updated();
1017}
1018
1019float Incidence::geoLongitude() const
1020{
1021 return d->mGeoLongitude;
1022}
1023
1024void Incidence::setGeoLongitude(float geolongitude)
1025{
1026 if (!mReadOnly) {
1027 update();
1028 d->mGeoLongitude = geolongitude;
1029 setFieldDirty(FieldGeoLongitude);
1030 updated();
1031 }
1032}
1033
1034bool Incidence::hasRecurrenceId() const
1035{
1036 return d->mRecurrenceId.isValid();
1037}
1038
1039KDateTime Incidence::recurrenceId() const
1040{
1041 return d->mRecurrenceId;
1042}
1043
1044void Incidence::setThisAndFuture(bool thisAndFuture)
1045{
1046 d->mThisAndFuture = thisAndFuture;
1047}
1048
1049bool Incidence::thisAndFuture() const
1050{
1051 return d->mThisAndFuture;
1052}
1053
1054void Incidence::setRecurrenceId(const KDateTime &recurrenceId)
1055{
1056 if (!mReadOnly) {
1057 update();
1058 d->mRecurrenceId = recurrenceId;
1059 setFieldDirty(FieldRecurrenceId);
1060 updated();
1061 }
1062}
1063
1064/** Observer interface for the recurrence class. If the recurrence is changed,
1065 this method will be called for the incidence the recurrence object
1066 belongs to. */
1067void Incidence::recurrenceUpdated(Recurrence *recurrence)
1068{
1069 if (recurrence == d->mRecurrence) {
1070 update();
1071 setFieldDirty(FieldRecurrence);
1072 updated();
1073 }
1074}
1075
1076//@cond PRIVATE
1077#define ALT_DESC_FIELD "X-ALT-DESC"
1078#define ALT_DESC_PARAMETERS QLatin1String("FMTTYPE=text/html")
1079//@endcond
1080
1081bool Incidence::hasAltDescription() const
1082{
1083 const QString value = nonKDECustomProperty(ALT_DESC_FIELD);
1084 const QString parameter = nonKDECustomPropertyParameters(ALT_DESC_FIELD);
1085
1086 return parameter == ALT_DESC_PARAMETERS && !value.isEmpty();
1087}
1088
1089void Incidence::setAltDescription(const QString &altdescription)
1090{
1091 if (altdescription.isEmpty()) {
1092 removeNonKDECustomProperty(ALT_DESC_FIELD);
1093 } else {
1094 setNonKDECustomProperty(ALT_DESC_FIELD,
1095 altdescription,
1096 ALT_DESC_PARAMETERS);
1097 }
1098}
1099
1100QString Incidence::altDescription() const
1101{
1102 if (!hasAltDescription()) {
1103 return QString();
1104 } else {
1105 return nonKDECustomProperty(ALT_DESC_FIELD);
1106 }
1107}
1108
1109bool Incidence::supportsGroupwareCommunication() const
1110{
1111 return type() == TypeEvent || type() == TypeTodo;
1112}
1113
1114/** static */
1115QStringList Incidence::mimeTypes()
1116{
1117 return QStringList() << QLatin1String("text/calendar")
1118 << KCalCore::Event::eventMimeType()
1119 << KCalCore::Todo::todoMimeType()
1120 << KCalCore::Journal::journalMimeType();
1121}
1122
1123void Incidence::serialize(QDataStream &out)
1124{
1125 out << d->mCreated << d->mRevision << d->mDescription << d->mDescriptionIsRich << d->mSummary
1126 << d->mSummaryIsRich << d->mLocation << d->mLocationIsRich << d->mCategories
1127 << d->mResources << d->mStatusString << d->mPriority << d->mSchedulingID
1128 << d->mGeoLatitude << d->mGeoLongitude << d->mHasGeo << d->mRecurrenceId << d->mThisAndFuture
1129 << d->mLocalOnly << d->mStatus << d->mSecrecy << (d->mRecurrence ? true : false)
1130 << d->mAttachments.count() << d->mAlarms.count() << d->mRelatedToUid;
1131
1132 if (d->mRecurrence)
1133 out << d->mRecurrence;
1134
1135 foreach(const Attachment::Ptr &attachment, d->mAttachments) {
1136 out << attachment;
1137 }
1138
1139 foreach(const Alarm::Ptr &alarm, d->mAlarms) {
1140 out << alarm;
1141 }
1142}
1143
1144void Incidence::deserialize(QDataStream &in)
1145{
1146 quint32 status, secrecy;
1147 bool hasRecurrence;
1148 int attachmentCount, alarmCount;
1149 QMap<int,QString> relatedToUid;
1150 in >> d->mCreated >> d->mRevision >> d->mDescription >> d->mDescriptionIsRich >> d->mSummary
1151 >> d->mSummaryIsRich >> d->mLocation >> d->mLocationIsRich >> d->mCategories
1152 >> d->mResources >> d->mStatusString >> d->mPriority >> d->mSchedulingID
1153 >> d->mGeoLatitude >> d->mGeoLongitude >> d->mHasGeo >> d->mRecurrenceId >> d->mThisAndFuture
1154 >> d->mLocalOnly >> status >> secrecy >> hasRecurrence >> attachmentCount >> alarmCount
1155 >> relatedToUid;
1156
1157 if (hasRecurrence) {
1158 d->mRecurrence = new Recurrence();
1159 d->mRecurrence->addObserver(const_cast<KCalCore::Incidence*>(this));
1160 in >> d->mRecurrence;
1161 }
1162
1163 d->mAttachments.clear();
1164 d->mAlarms.clear();
1165
1166 for (int i=0; i<attachmentCount; ++i) {
1167 Attachment::Ptr attachment = Attachment::Ptr(new Attachment(QString()));
1168 in >> attachment;
1169 d->mAttachments.append(attachment);
1170 }
1171
1172 for (int i=0; i<alarmCount; ++i) {
1173 Alarm::Ptr alarm = Alarm::Ptr(new Alarm(this));
1174 in >> alarm;
1175 d->mAlarms.append(alarm);
1176 }
1177
1178 d->mStatus = static_cast<Incidence::Status>(status);
1179 d->mSecrecy = static_cast<Incidence::Secrecy>(secrecy);
1180
1181 d->mRelatedToUid.clear();
1182 foreach(int key, relatedToUid.keys()) { //krazy:exclude=foreach
1183 d->mRelatedToUid.insert(static_cast<Incidence::RelType>(key), relatedToUid.value(key));
1184 }
1185
1186
1187}
1188