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 | |
46 | using namespace KCalCore; |
47 | |
48 | /** |
49 | Private class that helps to provide binary compatibility between releases. |
50 | @internal |
51 | */ |
52 | //@cond PRIVATE |
53 | class KCalCore::Incidence::Private |
54 | { |
55 | public: |
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 | |
182 | Incidence::Incidence() |
183 | : IncidenceBase(), d(new KCalCore::Incidence::Private) |
184 | { |
185 | recreate(); |
186 | resetDirtyFields(); |
187 | } |
188 | |
189 | Incidence::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 | |
198 | Incidence::~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 |
212 | static bool stringCompare(const QString &s1, const QString &s2) |
213 | { |
214 | return (s1.isEmpty() && s2.isEmpty()) || (s1 == s2); |
215 | } |
216 | |
217 | //@endcond |
218 | IncidenceBase &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 | |
231 | bool 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 | |
300 | QString Incidence::instanceIdentifier() const |
301 | { |
302 | if (hasRecurrenceId()) { |
303 | return uid() + recurrenceId().toString(); |
304 | } |
305 | return uid(); |
306 | } |
307 | |
308 | void 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 | |
318 | void Incidence::setLastModified(const KDateTime &lm) |
319 | { |
320 | if (!d->mLocalOnly) { |
321 | IncidenceBase::setLastModified(lm); |
322 | } |
323 | } |
324 | |
325 | void Incidence::setReadOnly(bool readOnly) |
326 | { |
327 | IncidenceBase::setReadOnly(readOnly); |
328 | if (d->mRecurrence) { |
329 | d->mRecurrence->setRecurReadOnly(readOnly); |
330 | } |
331 | } |
332 | |
333 | void Incidence::setLocalOnly(bool localOnly) |
334 | { |
335 | if (mReadOnly) { |
336 | return; |
337 | } |
338 | d->mLocalOnly = localOnly; |
339 | } |
340 | |
341 | bool Incidence::localOnly() const |
342 | { |
343 | return d->mLocalOnly; |
344 | } |
345 | |
346 | void 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 | |
357 | void 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 | |
370 | KDateTime Incidence::created() const |
371 | { |
372 | return d->mCreated; |
373 | } |
374 | |
375 | void 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 | |
388 | int Incidence::revision() const |
389 | { |
390 | return d->mRevision; |
391 | } |
392 | |
393 | void Incidence::setDtStart(const KDateTime &dt) |
394 | { |
395 | if (d->mRecurrence) { |
396 | d->mRecurrence->setStartDateTime(dt); |
397 | } |
398 | IncidenceBase::setDtStart(dt); |
399 | } |
400 | |
401 | void 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 | |
413 | void 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 | |
425 | void Incidence::setDescription(const QString &description) |
426 | { |
427 | setDescription(description, Qt::mightBeRichText(description)); |
428 | } |
429 | |
430 | QString Incidence::description() const |
431 | { |
432 | return d->mDescription; |
433 | } |
434 | |
435 | QString 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 | |
444 | bool Incidence::descriptionIsRich() const |
445 | { |
446 | return d->mDescriptionIsRich; |
447 | } |
448 | |
449 | void 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 | |
461 | void Incidence::setSummary(const QString &summary) |
462 | { |
463 | setSummary(summary, Qt::mightBeRichText(summary)); |
464 | } |
465 | |
466 | QString Incidence::summary() const |
467 | { |
468 | return d->mSummary; |
469 | } |
470 | |
471 | QString 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 | |
480 | bool Incidence::summaryIsRich() const |
481 | { |
482 | return d->mSummaryIsRich; |
483 | } |
484 | |
485 | void Incidence::setCategories(const QStringList &categories) |
486 | { |
487 | if (mReadOnly) { |
488 | return; |
489 | } |
490 | |
491 | update(); |
492 | d->mCategories = categories; |
493 | updated(); |
494 | } |
495 | |
496 | void 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 | |
521 | QStringList Incidence::categories() const |
522 | { |
523 | return d->mCategories; |
524 | } |
525 | |
526 | QString Incidence::categoriesStr() const |
527 | { |
528 | return d->mCategories.join(QLatin1String("," )); |
529 | } |
530 | |
531 | void 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 | |
544 | QString Incidence::relatedTo(RelType relType) const |
545 | { |
546 | return d->mRelatedToUid.value(relType); |
547 | } |
548 | |
549 | // %%%%%%%%%%%% Recurrence-related methods %%%%%%%%%%%%%%%%%%%% |
550 | |
551 | Recurrence *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 | |
564 | void Incidence::clearRecurrence() |
565 | { |
566 | delete d->mRecurrence; |
567 | d->mRecurrence = 0; |
568 | } |
569 | |
570 | ushort Incidence::recurrenceType() const |
571 | { |
572 | if (d->mRecurrence) { |
573 | return d->mRecurrence->recurrenceType(); |
574 | } else { |
575 | return Recurrence::rNone; |
576 | } |
577 | } |
578 | |
579 | bool Incidence::recurs() const |
580 | { |
581 | if (d->mRecurrence) { |
582 | return d->mRecurrence->recurs(); |
583 | } else { |
584 | return false; |
585 | } |
586 | } |
587 | |
588 | bool Incidence::recursOn(const QDate &date, |
589 | const KDateTime::Spec &timeSpec) const |
590 | { |
591 | return d->mRecurrence && d->mRecurrence->recursOn(date, timeSpec); |
592 | } |
593 | |
594 | bool Incidence::recursAt(const KDateTime &qdt) const |
595 | { |
596 | return d->mRecurrence && d->mRecurrence->recursAt(qdt); |
597 | } |
598 | |
599 | QList<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 | |
640 | QList<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 | |
680 | KDateTime 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 | |
694 | void 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 | |
708 | void 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 | |
717 | void 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 | |
731 | Attachment::List Incidence::attachments() const |
732 | { |
733 | return d->mAttachments; |
734 | } |
735 | |
736 | Attachment::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 | |
747 | void Incidence::clearAttachments() |
748 | { |
749 | setFieldDirty(FieldAttachment); |
750 | d->mAttachments.clear(); |
751 | } |
752 | |
753 | QString 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 | |
775 | void 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 | |
785 | void 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 | |
797 | QStringList Incidence::resources() const |
798 | { |
799 | return d->mResources; |
800 | } |
801 | |
802 | void 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 | |
814 | int Incidence::priority() const |
815 | { |
816 | return d->mPriority; |
817 | } |
818 | |
819 | void 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 | |
832 | void 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 | |
845 | Incidence::Status Incidence::status() const |
846 | { |
847 | return d->mStatus; |
848 | } |
849 | |
850 | QString Incidence::customStatus() const |
851 | { |
852 | if (d->mStatus == StatusX) { |
853 | return d->mStatusString; |
854 | } else { |
855 | return QString(); |
856 | } |
857 | } |
858 | |
859 | void 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 | |
871 | Incidence::Secrecy Incidence::secrecy() const |
872 | { |
873 | return d->mSecrecy; |
874 | } |
875 | |
876 | Alarm::List Incidence::alarms() const |
877 | { |
878 | return d->mAlarms; |
879 | } |
880 | |
881 | Alarm::Ptr Incidence::newAlarm() |
882 | { |
883 | Alarm::Ptr alarm(new Alarm(this)); |
884 | d->mAlarms.append(alarm); |
885 | return alarm; |
886 | } |
887 | |
888 | void Incidence::addAlarm(const Alarm::Ptr &alarm) |
889 | { |
890 | update(); |
891 | d->mAlarms.append(alarm); |
892 | setFieldDirty(FieldAlarms); |
893 | updated(); |
894 | } |
895 | |
896 | void 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 | |
907 | void Incidence::clearAlarms() |
908 | { |
909 | update(); |
910 | d->mAlarms.clear(); |
911 | setFieldDirty(FieldAlarms); |
912 | updated(); |
913 | } |
914 | |
915 | bool 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 | |
925 | void 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 | |
938 | void Incidence::setLocation(const QString &location) |
939 | { |
940 | setLocation(location, Qt::mightBeRichText(location)); |
941 | } |
942 | |
943 | QString Incidence::location() const |
944 | { |
945 | return d->mLocation; |
946 | } |
947 | |
948 | QString 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 | |
957 | bool Incidence::locationIsRich() const |
958 | { |
959 | return d->mLocationIsRich; |
960 | } |
961 | |
962 | void 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 | |
971 | QString 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 | |
980 | bool Incidence::hasGeo() const |
981 | { |
982 | return d->mHasGeo; |
983 | } |
984 | |
985 | void 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 | |
1002 | float Incidence::geoLatitude() const |
1003 | { |
1004 | return d->mGeoLatitude; |
1005 | } |
1006 | |
1007 | void 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 | |
1019 | float Incidence::geoLongitude() const |
1020 | { |
1021 | return d->mGeoLongitude; |
1022 | } |
1023 | |
1024 | void Incidence::setGeoLongitude(float geolongitude) |
1025 | { |
1026 | if (!mReadOnly) { |
1027 | update(); |
1028 | d->mGeoLongitude = geolongitude; |
1029 | setFieldDirty(FieldGeoLongitude); |
1030 | updated(); |
1031 | } |
1032 | } |
1033 | |
1034 | bool Incidence::hasRecurrenceId() const |
1035 | { |
1036 | return d->mRecurrenceId.isValid(); |
1037 | } |
1038 | |
1039 | KDateTime Incidence::recurrenceId() const |
1040 | { |
1041 | return d->mRecurrenceId; |
1042 | } |
1043 | |
1044 | void Incidence::setThisAndFuture(bool thisAndFuture) |
1045 | { |
1046 | d->mThisAndFuture = thisAndFuture; |
1047 | } |
1048 | |
1049 | bool Incidence::thisAndFuture() const |
1050 | { |
1051 | return d->mThisAndFuture; |
1052 | } |
1053 | |
1054 | void 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. */ |
1067 | void 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 | |
1081 | bool 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 | |
1089 | void 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 | |
1100 | QString Incidence::altDescription() const |
1101 | { |
1102 | if (!hasAltDescription()) { |
1103 | return QString(); |
1104 | } else { |
1105 | return nonKDECustomProperty(ALT_DESC_FIELD); |
1106 | } |
1107 | } |
1108 | |
1109 | bool Incidence::supportsGroupwareCommunication() const |
1110 | { |
1111 | return type() == TypeEvent || type() == TypeTodo; |
1112 | } |
1113 | |
1114 | /** static */ |
1115 | QStringList Incidence::mimeTypes() |
1116 | { |
1117 | return QStringList() << QLatin1String("text/calendar" ) |
1118 | << KCalCore::Event::eventMimeType() |
1119 | << KCalCore::Todo::todoMimeType() |
1120 | << KCalCore::Journal::journalMimeType(); |
1121 | } |
1122 | |
1123 | void 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 | |
1144 | void 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 | |