1 | /* |
2 | This file is part of the kcalcore library. |
3 | |
4 | Copyright (c) 1998 Preston Brown <pbrown@kde.org> |
5 | Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> |
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 VCalFormat base class. |
26 | |
27 | This class implements the vCalendar format. It provides methods for |
28 | loading/saving/converting vCalendar format data into the internal |
29 | representation as Calendar and Incidences. |
30 | |
31 | @brief |
32 | vCalendar format implementation. |
33 | |
34 | @author Preston Brown \<pbrown@kde.org\> |
35 | @author Cornelius Schumacher \<schumacher@kde.org\> |
36 | */ |
37 | #include "vcalformat.h" |
38 | #include "calendar.h" |
39 | #include "event.h" |
40 | #include "exceptions.h" |
41 | #include "icaltimezones.h" |
42 | #include "todo.h" |
43 | #include "versit/vcc.h" |
44 | #include "versit/vobject.h" |
45 | |
46 | #include <KCodecs> |
47 | #include <KDebug> |
48 | |
49 | #include <QtCore/QBitArray> |
50 | #include <QtCore/QFile> |
51 | #include <QTextDocument> // for Qt::escape() and Qt::mightBeRichText() |
52 | |
53 | using namespace KCalCore; |
54 | |
55 | /** |
56 | Private class that helps to provide binary compatibility between releases. |
57 | @internal |
58 | */ |
59 | //@cond PRIVATE |
60 | template <typename K> |
61 | void removeAllVCal(QVector< QSharedPointer<K> > &c, const QSharedPointer<K> &x) |
62 | { |
63 | if (c.count() < 1) { |
64 | return; |
65 | } |
66 | |
67 | int cnt = c.count(x); |
68 | if (cnt != 1) { |
69 | qCritical() << "There number of relatedTos for this incidence is " |
70 | << cnt << " (there must be 1 relatedTo only)" ; |
71 | Q_ASSERT_X(false, "removeAllVCal" , "Count is not 1." ); |
72 | return; |
73 | } |
74 | |
75 | c.remove(c.indexOf(x)); |
76 | } |
77 | |
78 | class KCalCore::VCalFormat::Private |
79 | { |
80 | public: |
81 | Calendar::Ptr mCalendar; |
82 | Event::List mEventsRelate; // Events with relations |
83 | Todo::List mTodosRelate; // To-dos with relations |
84 | QSet<QByteArray> mManuallyWrittenExtensionFields; // X- fields that are manually dumped |
85 | }; |
86 | //@endcond |
87 | |
88 | VCalFormat::VCalFormat() : d(new KCalCore::VCalFormat::Private) |
89 | { |
90 | #if defined(KCALCORE_FOR_SYMBIAN) |
91 | d->mManuallyWrittenExtensionFields << VCRecurrenceIdProp; |
92 | d->mManuallyWrittenExtensionFields << EPOCAgendaEntryTypeProp; |
93 | #endif |
94 | d->mManuallyWrittenExtensionFields << KPilotIdProp; |
95 | d->mManuallyWrittenExtensionFields << KPilotStatusProp; |
96 | } |
97 | |
98 | VCalFormat::~VCalFormat() |
99 | { |
100 | delete d; |
101 | } |
102 | |
103 | bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName) |
104 | { |
105 | d->mCalendar = calendar; |
106 | |
107 | clearException(); |
108 | |
109 | VObject *vcal = 0; |
110 | |
111 | // this is not necessarily only 1 vcal. Could be many vcals, or include |
112 | // a vcard... |
113 | vcal = Parse_MIME_FromFileName(const_cast<char *>(QFile::encodeName(fileName).data())); |
114 | |
115 | if (!vcal) { |
116 | setException(new Exception(Exception::CalVersionUnknown)); |
117 | return false; |
118 | } |
119 | |
120 | // any other top-level calendar stuff should be added/initialized here |
121 | |
122 | // put all vobjects into their proper places |
123 | QString savedTimeZoneId = d->mCalendar->timeZoneId(); |
124 | populate(vcal, false, fileName); |
125 | d->mCalendar->setTimeZoneId(savedTimeZoneId); |
126 | |
127 | // clean up from vcal API stuff |
128 | cleanVObjects(vcal); |
129 | cleanStrTbl(); |
130 | |
131 | return true; |
132 | } |
133 | |
134 | bool VCalFormat::save(const Calendar::Ptr &calendar, const QString &fileName) |
135 | { |
136 | d->mCalendar = calendar; |
137 | |
138 | ICalTimeZones *tzlist = d->mCalendar->timeZones(); |
139 | |
140 | QString tmpStr; |
141 | VObject *vcal, *vo; |
142 | |
143 | vcal = newVObject(VCCalProp); |
144 | |
145 | // addPropValue(vcal,VCLocationProp, "0.0"); |
146 | addPropValue(vcal, VCProdIdProp, productId().toLatin1()); |
147 | addPropValue(vcal, VCVersionProp, _VCAL_VERSION); |
148 | |
149 | // TODO STUFF |
150 | Todo::List todoList = d->mCalendar->rawTodos(); |
151 | Todo::List::ConstIterator it; |
152 | for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) { |
153 | if ((*it)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL" )) { |
154 | ICalTimeZone zone = tzlist->zone((*it)->dtStart().timeZone().name()); |
155 | if (zone.isValid()) { |
156 | QByteArray timezone = zone.vtimezone(); |
157 | addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit()); |
158 | QString dst = parseDst(timezone); |
159 | while (!dst.isEmpty()) { |
160 | addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit()); |
161 | dst = parseDst(timezone); |
162 | } |
163 | } |
164 | } |
165 | vo = eventToVTodo(*it); |
166 | addVObjectProp(vcal, vo); |
167 | } |
168 | // EVENT STUFF |
169 | Event::List events = d->mCalendar->rawEvents(); |
170 | Event::List::ConstIterator it2; |
171 | for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) { |
172 | if ((*it2)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL" )) { |
173 | ICalTimeZone zone = tzlist->zone((*it2)->dtStart().timeZone().name()); |
174 | if (zone.isValid()) { |
175 | QByteArray timezone = zone.vtimezone(); |
176 | addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit()); |
177 | QString dst = parseDst(timezone); |
178 | while (!dst.isEmpty()) { |
179 | addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit()); |
180 | dst = parseDst(timezone); |
181 | } |
182 | } |
183 | } |
184 | vo = eventToVEvent(*it2); |
185 | addVObjectProp(vcal, vo); |
186 | } |
187 | writeVObjectToFile(QFile::encodeName(fileName).data(), vcal); |
188 | cleanVObjects(vcal); |
189 | cleanStrTbl(); |
190 | |
191 | if (QFile::exists(fileName)) { |
192 | return true; |
193 | } else { |
194 | return false; // error |
195 | } |
196 | |
197 | return false; |
198 | } |
199 | |
200 | bool VCalFormat::fromString(const Calendar::Ptr &calendar, const QString &string, |
201 | bool deleted, const QString ¬ebook) |
202 | { |
203 | return fromRawString(calendar, string.toUtf8(), deleted, notebook); |
204 | } |
205 | |
206 | bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string, |
207 | bool deleted, const QString ¬ebook) |
208 | { |
209 | d->mCalendar = calendar; |
210 | |
211 | if (!string.size()) { |
212 | return false; |
213 | } |
214 | |
215 | VObject *vcal = Parse_MIME(string.data(), string.size()); |
216 | if (!vcal) { |
217 | return false; |
218 | } |
219 | |
220 | VObjectIterator i; |
221 | initPropIterator(&i, vcal); |
222 | |
223 | // put all vobjects into their proper places |
224 | QString savedTimeZoneId = d->mCalendar->timeZoneId(); |
225 | populate(vcal, deleted, notebook); |
226 | d->mCalendar->setTimeZoneId(savedTimeZoneId); |
227 | |
228 | // clean up from vcal API stuff |
229 | cleanVObjects(vcal); |
230 | cleanStrTbl(); |
231 | |
232 | return true; |
233 | } |
234 | |
235 | QString VCalFormat::toString(const Calendar::Ptr &calendar, |
236 | const QString ¬ebook, bool deleted) |
237 | { |
238 | // TODO: Factor out VCalFormat::asString() |
239 | d->mCalendar = calendar; |
240 | |
241 | ICalTimeZones *tzlist = d->mCalendar->timeZones(); |
242 | |
243 | VObject *vo; |
244 | VObject *vcal = newVObject(VCCalProp); |
245 | |
246 | addPropValue(vcal, VCProdIdProp, CalFormat::productId().toLatin1()); |
247 | addPropValue(vcal, VCVersionProp, _VCAL_VERSION); |
248 | |
249 | // TODO STUFF |
250 | Todo::List todoList = deleted ? d->mCalendar->deletedTodos() : d->mCalendar->rawTodos(); |
251 | Todo::List::ConstIterator it; |
252 | for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) { |
253 | if (!deleted || !d->mCalendar->todo((*it)->uid(), (*it)->recurrenceId())) { |
254 | // use existing ones, or really deleted ones |
255 | if (notebook.isEmpty() || |
256 | (!calendar->notebook(*it).isEmpty() && |
257 | notebook.endsWith(calendar->notebook(*it)))) { |
258 | if ((*it)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL" )) { |
259 | ICalTimeZone zone = tzlist->zone((*it)->dtStart().timeZone().name()); |
260 | if (zone.isValid()) { |
261 | QByteArray timezone = zone.vtimezone(); |
262 | addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8()); |
263 | QString dst = parseDst(timezone); |
264 | while (!dst.isEmpty()) { |
265 | addPropValue(vcal, VCDayLightProp, dst.toUtf8()); |
266 | dst = parseDst(timezone); |
267 | } |
268 | } |
269 | } |
270 | vo = eventToVTodo(*it); |
271 | addVObjectProp(vcal, vo); |
272 | } |
273 | } |
274 | } |
275 | |
276 | // EVENT STUFF |
277 | Event::List events = deleted ? d->mCalendar->deletedEvents() : d->mCalendar->rawEvents(); |
278 | Event::List::ConstIterator it2; |
279 | for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) { |
280 | if (!deleted || !d->mCalendar->event((*it2)->uid(), (*it2)->recurrenceId())) { |
281 | // use existing ones, or really deleted ones |
282 | if (notebook.isEmpty() || |
283 | (!calendar->notebook(*it2).isEmpty() && |
284 | notebook.endsWith(calendar->notebook(*it2)))) { |
285 | if ((*it2)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL" )) { |
286 | ICalTimeZone zone = tzlist->zone((*it2)->dtStart().timeZone().name()); |
287 | if (zone.isValid()) { |
288 | QByteArray timezone = zone.vtimezone(); |
289 | addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8()); |
290 | QString dst = parseDst(timezone); |
291 | while (!dst.isEmpty()) { |
292 | addPropValue(vcal, VCDayLightProp, dst.toUtf8()); |
293 | dst = parseDst(timezone); |
294 | } |
295 | } |
296 | } |
297 | vo = eventToVEvent(*it2); |
298 | addVObjectProp(vcal, vo); |
299 | } |
300 | } |
301 | } |
302 | |
303 | char *buf = writeMemVObject(0, 0, vcal); |
304 | |
305 | QString result(QString::fromUtf8(buf)); |
306 | |
307 | deleteStr(buf); |
308 | |
309 | cleanVObject(vcal); |
310 | |
311 | return result; |
312 | } |
313 | |
314 | VObject *VCalFormat::eventToVTodo(const Todo::Ptr &anEvent) |
315 | { |
316 | VObject *vtodo; |
317 | QString tmpStr; |
318 | |
319 | vtodo = newVObject(VCTodoProp); |
320 | |
321 | // due date |
322 | if (anEvent->hasDueDate()) { |
323 | tmpStr = kDateTimeToISO(anEvent->dtDue(), !anEvent->allDay()); |
324 | addPropValue(vtodo, VCDueProp, tmpStr.toUtf8()); |
325 | } |
326 | |
327 | // start date |
328 | if (anEvent->hasStartDate()) { |
329 | tmpStr = kDateTimeToISO(anEvent->dtStart(), !anEvent->allDay()); |
330 | addPropValue(vtodo, VCDTstartProp, tmpStr.toUtf8()); |
331 | } |
332 | |
333 | // creation date |
334 | tmpStr = kDateTimeToISO(anEvent->created()); |
335 | addPropValue(vtodo, VCDCreatedProp, tmpStr.toUtf8()); |
336 | |
337 | // unique id |
338 | addPropValue(vtodo, VCUniqueStringProp, |
339 | anEvent->uid().toUtf8()); |
340 | |
341 | // revision |
342 | tmpStr.sprintf("%i" , anEvent->revision()); |
343 | addPropValue(vtodo, VCSequenceProp, tmpStr.toUtf8()); |
344 | |
345 | // last modification date |
346 | tmpStr = kDateTimeToISO(anEvent->lastModified()); |
347 | addPropValue(vtodo, VCLastModifiedProp, tmpStr.toUtf8()); |
348 | |
349 | // organizer stuff |
350 | // @TODO: How about the common name? |
351 | if (!anEvent->organizer()->email().isEmpty()) { |
352 | tmpStr = "MAILTO:" + anEvent->organizer()->email(); |
353 | addPropValue(vtodo, ICOrganizerProp, tmpStr.toUtf8()); |
354 | } |
355 | |
356 | // attendees |
357 | if (anEvent->attendeeCount() > 0) { |
358 | Attendee::List::ConstIterator it; |
359 | Attendee::Ptr curAttendee; |
360 | for (it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd(); |
361 | ++it) { |
362 | curAttendee = *it; |
363 | if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) { |
364 | tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>'; |
365 | } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) { |
366 | tmpStr = "MAILTO: " ; |
367 | kDebug() << "warning! this Event has an attendee w/o name or email!" ; |
368 | } else if (curAttendee->name().isEmpty()) { |
369 | tmpStr = "MAILTO: " + curAttendee->email(); |
370 | } else { |
371 | tmpStr = "MAILTO: " + curAttendee->name(); |
372 | } |
373 | VObject *aProp = addPropValue(vtodo, VCAttendeeProp, tmpStr.toUtf8()); |
374 | addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" ); |
375 | addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status())); |
376 | } |
377 | } |
378 | |
379 | // recurrence rule stuff |
380 | const Recurrence *recur = anEvent->recurrence(); |
381 | if (recur->recurs()) { |
382 | bool validRecur = true; |
383 | QString tmpStr2; |
384 | switch (recur->recurrenceType()) { |
385 | case Recurrence::rDaily: |
386 | tmpStr.sprintf("D%i " , recur->frequency()); |
387 | break; |
388 | case Recurrence::rWeekly: |
389 | tmpStr.sprintf("W%i " , recur->frequency()); |
390 | for (int i = 0; i < 7; ++i) { |
391 | QBitArray days(recur->days()); |
392 | if (days.testBit(i)) { |
393 | tmpStr += dayFromNum(i); |
394 | } |
395 | } |
396 | break; |
397 | case Recurrence::rMonthlyPos: |
398 | { |
399 | tmpStr.sprintf("MP%i " , recur->frequency()); |
400 | // write out all rMonthPos's |
401 | QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions(); |
402 | for (QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin(); |
403 | posit != tmpPositions.constEnd(); ++posit) { |
404 | int pos = (*posit).pos(); |
405 | tmpStr2.sprintf("%i" , (pos > 0) ? pos : (-pos)); |
406 | if (pos < 0) { |
407 | tmpStr2 += "- " ; |
408 | } else { |
409 | tmpStr2 += "+ " ; |
410 | } |
411 | tmpStr += tmpStr2; |
412 | tmpStr += dayFromNum((*posit).day() - 1); |
413 | } |
414 | break; |
415 | } |
416 | case Recurrence::rMonthlyDay: |
417 | { |
418 | tmpStr.sprintf("MD%i " , recur->frequency()); |
419 | // write out all rMonthDays; |
420 | const QList<int> tmpDays = recur->monthDays(); |
421 | for (QList<int>::ConstIterator tmpDay = tmpDays.constBegin(); |
422 | tmpDay != tmpDays.constEnd(); ++tmpDay) { |
423 | tmpStr2.sprintf("%i " , *tmpDay); |
424 | tmpStr += tmpStr2; |
425 | } |
426 | break; |
427 | } |
428 | case Recurrence::rYearlyMonth: |
429 | { |
430 | tmpStr.sprintf("YM%i " , recur->frequency()); |
431 | // write out all the months;' |
432 | // TODO: Any way to write out the day within the month??? |
433 | const QList<int> months = recur->yearMonths(); |
434 | for (QList<int>::ConstIterator mit = months.constBegin(); |
435 | mit != months.constEnd(); ++mit) { |
436 | tmpStr2.sprintf("%i " , *mit); |
437 | tmpStr += tmpStr2; |
438 | } |
439 | break; |
440 | } |
441 | case Recurrence::rYearlyDay: |
442 | { |
443 | tmpStr.sprintf("YD%i " , recur->frequency()); |
444 | // write out all the rYearNums; |
445 | const QList<int> tmpDays = recur->yearDays(); |
446 | for (QList<int>::ConstIterator tmpDay = tmpDays.begin(); |
447 | tmpDay != tmpDays.end(); ++tmpDay) { |
448 | tmpStr2.sprintf("%i " , *tmpDay); |
449 | tmpStr += tmpStr2; |
450 | } |
451 | break; |
452 | } |
453 | default: |
454 | // TODO: Write rYearlyPos and arbitrary rules! |
455 | kDebug() << "ERROR, it should never get here in eventToVTodo!" ; |
456 | validRecur = false; |
457 | break; |
458 | } // switch |
459 | |
460 | if (recur->duration() > 0) { |
461 | tmpStr2.sprintf("#%i" , recur->duration()); |
462 | tmpStr += tmpStr2; |
463 | } else if (recur->duration() == -1) { |
464 | tmpStr += "#0" ; // defined as repeat forever |
465 | } else { |
466 | tmpStr += kDateTimeToISO(recur->endDateTime(), false); |
467 | } |
468 | // Only write out the rrule if we have a valid recurrence (i.e. a known |
469 | // type in thee switch above) |
470 | if (validRecur) { |
471 | addPropValue(vtodo, VCRRuleProp, tmpStr.toUtf8()); |
472 | } |
473 | |
474 | } // event repeats |
475 | |
476 | // exceptions dates to recurrence |
477 | DateList dateList = recur->exDates(); |
478 | DateList::ConstIterator id; |
479 | QString tmpStr2; |
480 | |
481 | for (id = dateList.constBegin(); id != dateList.constEnd(); ++id) { |
482 | tmpStr = qDateToISO(*id) + QLatin1Char(';'); |
483 | tmpStr2 += tmpStr; |
484 | } |
485 | if (!tmpStr2.isEmpty()) { |
486 | tmpStr2.truncate(tmpStr2.length() - 1); |
487 | addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8()); |
488 | } |
489 | // exceptions datetimes to recurrence |
490 | DateTimeList dateTimeList = recur->exDateTimes(); |
491 | DateTimeList::ConstIterator idt; |
492 | tmpStr2.clear(); |
493 | |
494 | for (idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) { |
495 | tmpStr = kDateTimeToISO(*idt) + QLatin1Char(';'); |
496 | tmpStr2 += tmpStr; |
497 | } |
498 | if (!tmpStr2.isEmpty()) { |
499 | tmpStr2.truncate(tmpStr2.length() - 1); |
500 | addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8()); |
501 | } |
502 | |
503 | // description BL: |
504 | if (!anEvent->description().isEmpty()) { |
505 | QByteArray in = anEvent->description().toUtf8(); |
506 | QByteArray out; |
507 | KCodecs::quotedPrintableEncode(in, out, true); |
508 | if (out != in) { |
509 | VObject *d = addPropValue(vtodo, VCDescriptionProp, out); |
510 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
511 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
512 | } else { |
513 | addPropValue(vtodo, VCDescriptionProp, in); |
514 | } |
515 | } |
516 | |
517 | // summary |
518 | if (!anEvent->summary().isEmpty()) { |
519 | QByteArray in = anEvent->summary().toUtf8(); |
520 | QByteArray out; |
521 | KCodecs::quotedPrintableEncode(in, out, true); |
522 | if (out != in) { |
523 | VObject *d = addPropValue(vtodo, VCSummaryProp, out); |
524 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
525 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
526 | } else { |
527 | addPropValue(vtodo, VCSummaryProp, in); |
528 | } |
529 | } |
530 | |
531 | // location |
532 | if (!anEvent->location().isEmpty()) { |
533 | QByteArray in = anEvent->location().toUtf8(); |
534 | QByteArray out; |
535 | KCodecs::quotedPrintableEncode(in, out, true); |
536 | if (out != in) { |
537 | VObject *d = addPropValue(vtodo, VCLocationProp, out); |
538 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
539 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
540 | } else { |
541 | addPropValue(vtodo, VCLocationProp, in); |
542 | } |
543 | } |
544 | |
545 | // completed status |
546 | // backward compatibility, KOrganizer used to interpret only these two values |
547 | addPropValue(vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS ACTION" ); |
548 | |
549 | // completion date |
550 | if (anEvent->hasCompletedDate()) { |
551 | tmpStr = kDateTimeToISO(anEvent->completed()); |
552 | addPropValue(vtodo, VCCompletedProp, tmpStr.toUtf8()); |
553 | } |
554 | |
555 | // priority |
556 | tmpStr.sprintf("%i" , anEvent->priority()); |
557 | addPropValue(vtodo, VCPriorityProp, tmpStr.toUtf8()); |
558 | |
559 | // related event |
560 | if (!anEvent->relatedTo().isEmpty()) { |
561 | addPropValue(vtodo, VCRelatedToProp, |
562 | anEvent->relatedTo().toUtf8()); |
563 | } |
564 | |
565 | // secrecy |
566 | const char *text = 0; |
567 | switch (anEvent->secrecy()) { |
568 | case Incidence::SecrecyPublic: |
569 | text = "PUBLIC" ; |
570 | break; |
571 | case Incidence::SecrecyPrivate: |
572 | text = "PRIVATE" ; |
573 | break; |
574 | case Incidence::SecrecyConfidential: |
575 | text = "CONFIDENTIAL" ; |
576 | break; |
577 | } |
578 | if (text) { |
579 | addPropValue(vtodo, VCClassProp, text); |
580 | } |
581 | |
582 | // categories |
583 | const QStringList tmpStrList = anEvent->categories(); |
584 | tmpStr = "" ; |
585 | QString catStr; |
586 | QStringList::const_iterator its; |
587 | for (its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its) { |
588 | catStr = *its; |
589 | if (catStr[0] == QLatin1Char(' ')) { |
590 | tmpStr += catStr.mid(1); |
591 | } else { |
592 | tmpStr += catStr; |
593 | } |
594 | // this must be a ';' character as the vCalendar specification requires! |
595 | // vcc.y has been hacked to translate the ';' to a ',' when the vcal is |
596 | // read in. |
597 | tmpStr += QLatin1Char(';'); |
598 | } |
599 | if (!tmpStr.isEmpty()) { |
600 | tmpStr.truncate(tmpStr.length() - 1); |
601 | addPropValue(vtodo, VCCategoriesProp, tmpStr.toUtf8()); |
602 | } |
603 | |
604 | // alarm stuff |
605 | Alarm::List::ConstIterator it; |
606 | for (it = anEvent->alarms().constBegin(); it != anEvent->alarms().constEnd(); ++it) { |
607 | Alarm::Ptr alarm = *it; |
608 | if (alarm->enabled()) { |
609 | VObject *a; |
610 | if (alarm->type() == Alarm::Display) { |
611 | a = addProp(vtodo, VCDAlarmProp); |
612 | tmpStr = kDateTimeToISO(alarm->time()); |
613 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
614 | addPropValue(a, VCRepeatCountProp, "1" ); |
615 | if (alarm->text().isNull()) { |
616 | addPropValue(a, VCDisplayStringProp, "beep!" ); |
617 | } else { |
618 | addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data()); |
619 | } |
620 | } else if (alarm->type() == Alarm::Audio) { |
621 | a = addProp(vtodo, VCAAlarmProp); |
622 | tmpStr = kDateTimeToISO(alarm->time()); |
623 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
624 | addPropValue(a, VCRepeatCountProp, "1" ); |
625 | addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile())); |
626 | } else if (alarm->type() == Alarm::Procedure) { |
627 | a = addProp(vtodo, VCPAlarmProp); |
628 | tmpStr = kDateTimeToISO(alarm->time()); |
629 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
630 | addPropValue(a, VCRepeatCountProp, "1" ); |
631 | addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile())); |
632 | } |
633 | } |
634 | } |
635 | |
636 | QString pilotId = anEvent->nonKDECustomProperty(KPilotIdProp); |
637 | if (!pilotId.isEmpty()) { |
638 | // pilot sync stuff |
639 | addPropValue(vtodo, KPilotIdProp, pilotId.toUtf8()); |
640 | addPropValue(vtodo, KPilotStatusProp, |
641 | anEvent->nonKDECustomProperty(KPilotStatusProp).toUtf8()); |
642 | } |
643 | #if defined(KCALCORE_FOR_SYMBIAN) |
644 | if (anEvent->nonKDECustomProperty(EPOCAgendaEntryTypeProp).isEmpty()) { |
645 | // Propagate braindeath by setting this property also so that |
646 | // S60 is happy |
647 | addPropValue(vtodo, EPOCAgendaEntryTypeProp, "TODO" ); |
648 | } |
649 | |
650 | writeCustomProperties(vtodo, anEvent); |
651 | #endif |
652 | |
653 | return vtodo; |
654 | } |
655 | |
656 | VObject *VCalFormat::eventToVEvent(const Event::Ptr &anEvent) |
657 | { |
658 | VObject *vevent; |
659 | QString tmpStr; |
660 | |
661 | vevent = newVObject(VCEventProp); |
662 | |
663 | // start and end time |
664 | tmpStr = kDateTimeToISO(anEvent->dtStart(), !anEvent->allDay()); |
665 | addPropValue(vevent, VCDTstartProp, tmpStr.toUtf8()); |
666 | |
667 | #if !defined(KCALCORE_FOR_MEEGO) |
668 | // events that have time associated but take up no time should |
669 | // not have both DTSTART and DTEND. |
670 | if (anEvent->dtStart() != anEvent->dtEnd()) { |
671 | tmpStr = kDateTimeToISO(anEvent->dtEnd(), !anEvent->allDay()); |
672 | addPropValue(vevent, VCDTendProp, tmpStr.toUtf8()); |
673 | } |
674 | #else |
675 | // N900 and s60-phones need enddate |
676 | tmpStr = kDateTimeToISO(anEvent->dtEnd(), !anEvent->allDay()); |
677 | addPropValue(vevent, VCDTendProp, tmpStr.toUtf8()); |
678 | #endif |
679 | |
680 | // creation date |
681 | tmpStr = kDateTimeToISO(anEvent->created()); |
682 | addPropValue(vevent, VCDCreatedProp, tmpStr.toUtf8()); |
683 | |
684 | // unique id |
685 | addPropValue(vevent, VCUniqueStringProp, |
686 | anEvent->uid().toUtf8()); |
687 | |
688 | // revision |
689 | tmpStr.sprintf("%i" , anEvent->revision()); |
690 | addPropValue(vevent, VCSequenceProp, tmpStr.toUtf8()); |
691 | |
692 | // last modification date |
693 | tmpStr = kDateTimeToISO(anEvent->lastModified()); |
694 | addPropValue(vevent, VCLastModifiedProp, tmpStr.toUtf8()); |
695 | |
696 | // attendee and organizer stuff |
697 | // TODO: What to do with the common name? |
698 | if (!anEvent->organizer()->email().isEmpty()) { |
699 | tmpStr = QLatin1String("MAILTO:" ) + anEvent->organizer()->email(); |
700 | addPropValue(vevent, ICOrganizerProp, tmpStr.toUtf8()); |
701 | } |
702 | |
703 | // TODO: Put this functionality into Attendee class |
704 | if (anEvent->attendeeCount() > 0) { |
705 | Attendee::List::ConstIterator it; |
706 | for (it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd(); |
707 | ++it) { |
708 | Attendee::Ptr curAttendee = *it; |
709 | if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) { |
710 | tmpStr = QLatin1String("MAILTO:" ) + curAttendee->name() + QLatin1String(" <" ) + curAttendee->email() + QLatin1Char('>'); |
711 | } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) { |
712 | tmpStr = QLatin1String("MAILTO: " ); |
713 | kDebug() << "warning! this Event has an attendee w/o name or email!" ; |
714 | } else if (curAttendee->name().isEmpty()) { |
715 | tmpStr = QLatin1String("MAILTO: " ) + curAttendee->email(); |
716 | } else { |
717 | tmpStr = QLatin1String("MAILTO: " ) + curAttendee->name(); |
718 | } |
719 | VObject *aProp = addPropValue(vevent, VCAttendeeProp, tmpStr.toUtf8()); |
720 | addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" ); |
721 | addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status())); |
722 | } |
723 | } |
724 | |
725 | // recurrence rule stuff |
726 | const Recurrence *recur = anEvent->recurrence(); |
727 | if (recur->recurs()) { |
728 | bool validRecur = true; |
729 | QString tmpStr2; |
730 | switch (recur->recurrenceType()) { |
731 | case Recurrence::rDaily: |
732 | tmpStr.sprintf("D%i " , recur->frequency()); |
733 | break; |
734 | case Recurrence::rWeekly: |
735 | tmpStr.sprintf("W%i " , recur->frequency()); |
736 | for (int i = 0; i < 7; ++i) { |
737 | QBitArray days(recur->days()); |
738 | if (days.testBit(i)) { |
739 | tmpStr += dayFromNum(i); |
740 | } |
741 | } |
742 | break; |
743 | case Recurrence::rMonthlyPos: |
744 | { |
745 | tmpStr.sprintf("MP%i " , recur->frequency()); |
746 | // write out all rMonthPos's |
747 | QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions(); |
748 | for (QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin(); |
749 | posit != tmpPositions.constEnd(); ++posit) { |
750 | int pos = (*posit).pos(); |
751 | tmpStr2.sprintf("%i" , (pos > 0) ? pos : (-pos)); |
752 | if (pos < 0) { |
753 | tmpStr2 += "- " ; |
754 | } else { |
755 | tmpStr2 += "+ " ; |
756 | } |
757 | tmpStr += tmpStr2; |
758 | tmpStr += dayFromNum((*posit).day() - 1); |
759 | } |
760 | break; |
761 | } |
762 | case Recurrence::rMonthlyDay: |
763 | { |
764 | tmpStr.sprintf("MD%i " , recur->frequency()); |
765 | // write out all rMonthDays; |
766 | const QList<int> tmpDays = recur->monthDays(); |
767 | for (QList<int>::ConstIterator tmpDay = tmpDays.constBegin(); |
768 | tmpDay != tmpDays.constEnd(); ++tmpDay) { |
769 | tmpStr2.sprintf("%i " , *tmpDay); |
770 | tmpStr += tmpStr2; |
771 | } |
772 | break; |
773 | } |
774 | case Recurrence::rYearlyMonth: |
775 | { |
776 | tmpStr.sprintf("YM%i " , recur->frequency()); |
777 | // write out all the months;' |
778 | // TODO: Any way to write out the day within the month??? |
779 | const QList<int> months = recur->yearMonths(); |
780 | for (QList<int>::ConstIterator mit = months.constBegin(); |
781 | mit != months.constEnd(); ++mit) { |
782 | tmpStr2.sprintf("%i " , *mit); |
783 | tmpStr += tmpStr2; |
784 | } |
785 | break; |
786 | } |
787 | case Recurrence::rYearlyDay: |
788 | { |
789 | tmpStr.sprintf("YD%i " , recur->frequency()); |
790 | // write out all the rYearNums; |
791 | const QList<int> tmpDays = recur->yearDays(); |
792 | for (QList<int>::ConstIterator tmpDay = tmpDays.begin(); |
793 | tmpDay != tmpDays.end(); ++tmpDay) { |
794 | tmpStr2.sprintf("%i " , *tmpDay); |
795 | tmpStr += tmpStr2; |
796 | } |
797 | break; |
798 | } |
799 | default: |
800 | // TODO: Write rYearlyPos and arbitrary rules! |
801 | kDebug() << "ERROR, it should never get here in eventToVEvent!" ; |
802 | validRecur = false; |
803 | break; |
804 | } // switch |
805 | |
806 | if (recur->duration() > 0) { |
807 | tmpStr2.sprintf("#%i" , recur->duration()); |
808 | tmpStr += tmpStr2; |
809 | } else if (recur->duration() == -1) { |
810 | tmpStr += "#0" ; // defined as repeat forever |
811 | } else { |
812 | #if !defined(KCALCORE_FOR_MEEGO) |
813 | tmpStr += kDateTimeToISO(recur->endDateTime(), false); |
814 | #else |
815 | tmpStr += |
816 | kDateTimeToISO(recur->endDateTime().toTimeSpec(d->mCalendar->timeSpec()), false); |
817 | #endif |
818 | } |
819 | // Only write out the rrule if we have a valid recurrence (i.e. a known |
820 | // type in thee switch above) |
821 | if (validRecur) { |
822 | addPropValue(vevent, VCRRuleProp, tmpStr.toUtf8()); |
823 | } |
824 | |
825 | } // event repeats |
826 | |
827 | // exceptions dates to recurrence |
828 | DateList dateList = recur->exDates(); |
829 | DateList::ConstIterator it; |
830 | QString tmpStr2; |
831 | |
832 | for (it = dateList.constBegin(); it != dateList.constEnd(); ++it) { |
833 | tmpStr = qDateToISO(*it) + QLatin1Char(';'); |
834 | tmpStr2 += tmpStr; |
835 | } |
836 | if (!tmpStr2.isEmpty()) { |
837 | tmpStr2.truncate(tmpStr2.length() - 1); |
838 | addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8()); |
839 | } |
840 | // exceptions datetimes to recurrence |
841 | DateTimeList dateTimeList = recur->exDateTimes(); |
842 | DateTimeList::ConstIterator idt; |
843 | tmpStr2.clear(); |
844 | |
845 | for (idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) { |
846 | tmpStr = kDateTimeToISO(*idt) + QLatin1Char(';'); |
847 | tmpStr2 += tmpStr; |
848 | } |
849 | if (!tmpStr2.isEmpty()) { |
850 | tmpStr2.truncate(tmpStr2.length() - 1); |
851 | addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8()); |
852 | } |
853 | |
854 | // description |
855 | if (!anEvent->description().isEmpty()) { |
856 | QByteArray in = anEvent->description().toUtf8(); |
857 | QByteArray out; |
858 | KCodecs::quotedPrintableEncode(in, out, true); |
859 | if (out != in) { |
860 | VObject *d = addPropValue(vevent, VCDescriptionProp, out); |
861 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
862 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
863 | } else { |
864 | addPropValue(vevent, VCDescriptionProp, in); |
865 | } |
866 | } |
867 | |
868 | // summary |
869 | if (!anEvent->summary().isEmpty()) { |
870 | QByteArray in = anEvent->summary().toUtf8(); |
871 | QByteArray out; |
872 | KCodecs::quotedPrintableEncode(in, out, true); |
873 | if (out != in) { |
874 | VObject *d = addPropValue(vevent, VCSummaryProp, out); |
875 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
876 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
877 | } else { |
878 | addPropValue(vevent, VCSummaryProp, in); |
879 | } |
880 | } |
881 | |
882 | // location |
883 | if (!anEvent->location().isEmpty()) { |
884 | QByteArray in = anEvent->location().toUtf8(); |
885 | QByteArray out; |
886 | KCodecs::quotedPrintableEncode(in, out, true); |
887 | if (out != in) { |
888 | VObject *d = addPropValue(vevent, VCLocationProp, out); |
889 | addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); |
890 | addPropValue(d, VCCharSetProp, VCUtf8Prop); |
891 | } else { |
892 | addPropValue(vevent, VCLocationProp, in); |
893 | } |
894 | } |
895 | |
896 | // status |
897 | // TODO: define Event status |
898 | // addPropValue( vevent, VCStatusProp, anEvent->statusStr().toUtf8() ); |
899 | |
900 | // secrecy |
901 | const char *text = 0; |
902 | switch (anEvent->secrecy()) { |
903 | case Incidence::SecrecyPublic: |
904 | text = "PUBLIC" ; |
905 | break; |
906 | case Incidence::SecrecyPrivate: |
907 | text = "PRIVATE" ; |
908 | break; |
909 | case Incidence::SecrecyConfidential: |
910 | text = "CONFIDENTIAL" ; |
911 | break; |
912 | } |
913 | if (text) { |
914 | addPropValue(vevent, VCClassProp, text); |
915 | } |
916 | |
917 | // categories |
918 | QStringList tmpStrList = anEvent->categories(); |
919 | tmpStr = "" ; |
920 | QString catStr; |
921 | for (QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd(); |
922 | ++it) { |
923 | catStr = *it; |
924 | if (catStr[0] == ' ') { |
925 | tmpStr += catStr.mid(1); |
926 | } else { |
927 | tmpStr += catStr; |
928 | } |
929 | // this must be a ';' character as the vCalendar specification requires! |
930 | // vcc.y has been hacked to translate the ';' to a ',' when the vcal is |
931 | // read in. |
932 | tmpStr += ';'; |
933 | } |
934 | if (!tmpStr.isEmpty()) { |
935 | tmpStr.truncate(tmpStr.length() - 1); |
936 | addPropValue(vevent, VCCategoriesProp, tmpStr.toUtf8()); |
937 | } |
938 | |
939 | // attachments |
940 | // TODO: handle binary attachments! |
941 | Attachment::List attachments = anEvent->attachments(); |
942 | Attachment::List::ConstIterator atIt; |
943 | for (atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt) { |
944 | addPropValue(vevent, VCAttachProp, (*atIt)->uri().toUtf8()); |
945 | } |
946 | |
947 | // resources |
948 | tmpStrList = anEvent->resources(); |
949 | tmpStr = tmpStrList.join(";" ); |
950 | if (!tmpStr.isEmpty()) { |
951 | addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8()); |
952 | } |
953 | |
954 | // alarm stuff |
955 | Alarm::List::ConstIterator it2; |
956 | for (it2 = anEvent->alarms().constBegin(); it2 != anEvent->alarms().constEnd(); ++it2) { |
957 | Alarm::Ptr alarm = *it2; |
958 | if (alarm->enabled()) { |
959 | VObject *a; |
960 | if (alarm->type() == Alarm::Display) { |
961 | a = addProp(vevent, VCDAlarmProp); |
962 | tmpStr = kDateTimeToISO(alarm->time()); |
963 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
964 | addPropValue(a, VCRepeatCountProp, "1" ); |
965 | if (alarm->text().isNull()) { |
966 | addPropValue(a, VCDisplayStringProp, "beep!" ); |
967 | } else { |
968 | addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data()); |
969 | } |
970 | } else if (alarm->type() == Alarm::Audio) { |
971 | a = addProp(vevent, VCAAlarmProp); |
972 | tmpStr = kDateTimeToISO(alarm->time()); |
973 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
974 | addPropValue(a, VCRepeatCountProp, "1" ); |
975 | addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile())); |
976 | } |
977 | if (alarm->type() == Alarm::Procedure) { |
978 | a = addProp(vevent, VCPAlarmProp); |
979 | tmpStr = kDateTimeToISO(alarm->time()); |
980 | addPropValue(a, VCRunTimeProp, tmpStr.toUtf8()); |
981 | addPropValue(a, VCRepeatCountProp, "1" ); |
982 | addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile())); |
983 | } |
984 | } |
985 | } |
986 | |
987 | // priority |
988 | tmpStr.sprintf("%i" , anEvent->priority()); |
989 | addPropValue(vevent, VCPriorityProp, tmpStr.toUtf8()); |
990 | |
991 | // transparency |
992 | tmpStr.sprintf("%i" , anEvent->transparency()); |
993 | addPropValue(vevent, VCTranspProp, tmpStr.toUtf8()); |
994 | |
995 | // related event |
996 | if (!anEvent->relatedTo().isEmpty()) { |
997 | addPropValue(vevent, VCRelatedToProp, anEvent->relatedTo().toUtf8()); |
998 | } |
999 | |
1000 | QString pilotId = anEvent->nonKDECustomProperty(KPilotIdProp); |
1001 | if (!pilotId.isEmpty()) { |
1002 | // pilot sync stuff |
1003 | addPropValue(vevent, KPilotIdProp, pilotId.toUtf8()); |
1004 | addPropValue(vevent, KPilotStatusProp, |
1005 | anEvent->nonKDECustomProperty(KPilotStatusProp).toUtf8()); |
1006 | } |
1007 | |
1008 | #if defined(KCALCORE_FOR_SYMBIAN) |
1009 | if (anEvent->nonKDECustomProperty(EPOCAgendaEntryTypeProp).isEmpty()) { |
1010 | // Propagate braindeath by setting this property also so that |
1011 | // S60 is happy |
1012 | if (anEvent->allDay()) { |
1013 | addPropValue(vevent, EPOCAgendaEntryTypeProp, "EVENT" ); |
1014 | } else { |
1015 | addPropValue(vevent, EPOCAgendaEntryTypeProp, "APPOINTMENT" ); |
1016 | } |
1017 | } |
1018 | |
1019 | if (anEvent->hasRecurrenceId()) { |
1020 | tmpStr = kDateTimeToISO(anEvent->recurrenceId(), true); |
1021 | addPropValue(vevent, VCRecurrenceIdProp, tmpStr.toUtf8()); |
1022 | } |
1023 | writeCustomProperties(vevent, anEvent); |
1024 | #endif |
1025 | |
1026 | return vevent; |
1027 | } |
1028 | |
1029 | Todo::Ptr VCalFormat::VTodoToEvent(VObject *vtodo) |
1030 | { |
1031 | VObject *vo; |
1032 | VObjectIterator voi; |
1033 | char *s; |
1034 | |
1035 | Todo::Ptr anEvent(new Todo); |
1036 | |
1037 | // creation date |
1038 | if ((vo = isAPropertyOf(vtodo, VCDCreatedProp)) != 0) { |
1039 | anEvent->setCreated(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1040 | deleteStr(s); |
1041 | } |
1042 | |
1043 | // unique id |
1044 | vo = isAPropertyOf(vtodo, VCUniqueStringProp); |
1045 | // while the UID property is preferred, it is not required. We'll use the |
1046 | // default Event UID if none is given. |
1047 | if (vo) { |
1048 | anEvent->setUid(s = fakeCString(vObjectUStringZValue(vo))); |
1049 | deleteStr(s); |
1050 | } |
1051 | |
1052 | // last modification date |
1053 | if ((vo = isAPropertyOf(vtodo, VCLastModifiedProp)) != 0) { |
1054 | anEvent->setLastModified(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1055 | deleteStr(s); |
1056 | } else { |
1057 | anEvent->setLastModified(KDateTime::currentUtcDateTime()); |
1058 | } |
1059 | |
1060 | // organizer |
1061 | // if our extension property for the event's ORGANIZER exists, add it. |
1062 | if ((vo = isAPropertyOf(vtodo, ICOrganizerProp)) != 0) { |
1063 | anEvent->setOrganizer(s = fakeCString(vObjectUStringZValue(vo))); |
1064 | deleteStr(s); |
1065 | } else { |
1066 | if (d->mCalendar->owner()->name() != QLatin1String("Unknown Name" )) { |
1067 | anEvent->setOrganizer(d->mCalendar->owner()); |
1068 | } |
1069 | } |
1070 | |
1071 | // attendees. |
1072 | initPropIterator(&voi, vtodo); |
1073 | while (moreIteration(&voi)) { |
1074 | vo = nextVObject(&voi); |
1075 | if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) { |
1076 | Attendee::Ptr a; |
1077 | VObject *vp; |
1078 | s = fakeCString(vObjectUStringZValue(vo)); |
1079 | QString tmpStr = QString::fromUtf8(s); |
1080 | deleteStr(s); |
1081 | tmpStr = tmpStr.simplified(); |
1082 | int emailPos1, emailPos2; |
1083 | if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) { |
1084 | // both email address and name |
1085 | emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>')); |
1086 | a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1), |
1087 | tmpStr.mid(emailPos1 + 1, |
1088 | emailPos2 - (emailPos1 + 1)))); |
1089 | } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) { |
1090 | // just an email address |
1091 | a = Attendee::Ptr(new Attendee(0, tmpStr)); |
1092 | } else { |
1093 | // just a name |
1094 | // WTF??? Replacing the spaces of a name and using this as email? |
1095 | QString email = tmpStr.replace(' ', '.'); |
1096 | a = Attendee::Ptr(new Attendee(tmpStr, email)); |
1097 | } |
1098 | |
1099 | // is there an RSVP property? |
1100 | if ((vp = isAPropertyOf(vo, VCRSVPProp)) != 0) { |
1101 | a->setRSVP(vObjectStringZValue(vp)); |
1102 | } |
1103 | // is there a status property? |
1104 | if ((vp = isAPropertyOf(vo, VCStatusProp)) != 0) { |
1105 | a->setStatus(readStatus(vObjectStringZValue(vp))); |
1106 | } |
1107 | // add the attendee |
1108 | anEvent->addAttendee(a); |
1109 | } |
1110 | } |
1111 | |
1112 | // description for todo |
1113 | if ((vo = isAPropertyOf(vtodo, VCDescriptionProp)) != 0) { |
1114 | s = fakeCString(vObjectUStringZValue(vo)); |
1115 | anEvent->setDescription(QString::fromUtf8(s), Qt::mightBeRichText(s)); |
1116 | deleteStr(s); |
1117 | } |
1118 | |
1119 | // summary |
1120 | if ((vo = isAPropertyOf(vtodo, VCSummaryProp))) { |
1121 | s = fakeCString(vObjectUStringZValue(vo)); |
1122 | anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(s)); |
1123 | deleteStr(s); |
1124 | } |
1125 | |
1126 | // location |
1127 | if ((vo = isAPropertyOf(vtodo, VCLocationProp)) != 0) { |
1128 | s = fakeCString(vObjectUStringZValue(vo)); |
1129 | anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(s)); |
1130 | deleteStr(s); |
1131 | } |
1132 | |
1133 | // completed |
1134 | // was: status |
1135 | if ((vo = isAPropertyOf(vtodo, VCStatusProp)) != 0) { |
1136 | s = fakeCString(vObjectUStringZValue(vo)); |
1137 | if (s && strcmp(s, "COMPLETED" ) == 0) { |
1138 | anEvent->setCompleted(true); |
1139 | } else { |
1140 | anEvent->setCompleted(false); |
1141 | } |
1142 | deleteStr(s); |
1143 | } else { |
1144 | anEvent->setCompleted(false); |
1145 | } |
1146 | |
1147 | // completion date |
1148 | if ((vo = isAPropertyOf(vtodo, VCCompletedProp)) != 0) { |
1149 | anEvent->setCompleted(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1150 | deleteStr(s); |
1151 | } |
1152 | |
1153 | // priority |
1154 | if ((vo = isAPropertyOf(vtodo, VCPriorityProp))) { |
1155 | s = fakeCString(vObjectUStringZValue(vo)); |
1156 | if (s) { |
1157 | anEvent->setPriority(atoi(s)); |
1158 | deleteStr(s); |
1159 | } |
1160 | } |
1161 | |
1162 | anEvent->setAllDay(false); |
1163 | |
1164 | // due date |
1165 | if ((vo = isAPropertyOf(vtodo, VCDueProp)) != 0) { |
1166 | anEvent->setDtDue(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1167 | deleteStr(s); |
1168 | if (anEvent->dtDue().time().hour() == 0 && |
1169 | anEvent->dtDue().time().minute() == 0 && |
1170 | anEvent->dtDue().time().second() == 0) { |
1171 | #if defined(KCALCORE_FOR_MEEGO) |
1172 | QDate dueDate = anEvent->dtDue().date(); |
1173 | anEvent->setDtDue(KDateTime(dueDate, KDateTime::ClockTime)); |
1174 | #endif |
1175 | anEvent->setAllDay(true); |
1176 | } |
1177 | } else { |
1178 | anEvent->setDtDue(KDateTime()); |
1179 | } |
1180 | |
1181 | // start time |
1182 | if ((vo = isAPropertyOf(vtodo, VCDTstartProp)) != 0) { |
1183 | anEvent->setDtStart(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1184 | deleteStr(s); |
1185 | if (anEvent->dtStart().time().hour() == 0 && |
1186 | anEvent->dtStart().time().minute() == 0 && |
1187 | anEvent->dtStart().time().second() == 0) { |
1188 | #if defined(KCALCORE_FOR_MEEGO) |
1189 | QDate startDate = anEvent->dtStart().date(); |
1190 | anEvent->setDtStart(KDateTime(startDate, KDateTime::ClockTime)); |
1191 | #endif |
1192 | anEvent->setAllDay(true); |
1193 | } |
1194 | } else { |
1195 | anEvent->setDtStart(KDateTime()); |
1196 | } |
1197 | |
1198 | // repeat stuff |
1199 | if ((vo = isAPropertyOf(vtodo, VCRRuleProp)) != 0) { |
1200 | QString tmpStr = (s = fakeCString(vObjectUStringZValue(vo))); |
1201 | deleteStr(s); |
1202 | tmpStr = tmpStr.simplified(); |
1203 | tmpStr = tmpStr.toUpper(); |
1204 | // first, read the type of the recurrence |
1205 | int typelen = 1; |
1206 | uint type = Recurrence::rNone; |
1207 | if (tmpStr.left(1) == "D" ) { |
1208 | type = Recurrence::rDaily; |
1209 | } else if (tmpStr.left(1) == "W" ) { |
1210 | type = Recurrence::rWeekly; |
1211 | } else { |
1212 | typelen = 2; |
1213 | if (tmpStr.left(2) == "MP" ) { |
1214 | type = Recurrence::rMonthlyPos; |
1215 | } else if (tmpStr.left(2) == "MD" ) { |
1216 | type = Recurrence::rMonthlyDay; |
1217 | } else if (tmpStr.left(2) == "YM" ) { |
1218 | type = Recurrence::rYearlyMonth; |
1219 | } else if (tmpStr.left(2) == "YD" ) { |
1220 | type = Recurrence::rYearlyDay; |
1221 | } |
1222 | } |
1223 | |
1224 | if (type != Recurrence::rNone) { |
1225 | |
1226 | // Immediately after the type is the frequency |
1227 | int index = tmpStr.indexOf(' '); |
1228 | int last = tmpStr.lastIndexOf(' ') + 1; // find last entry |
1229 | int rFreq = tmpStr.mid(typelen, (index - 1)).toInt(); |
1230 | ++index; // advance to beginning of stuff after freq |
1231 | |
1232 | // Read the type-specific settings |
1233 | switch (type) { |
1234 | case Recurrence::rDaily: |
1235 | anEvent->recurrence()->setDaily(rFreq); |
1236 | break; |
1237 | |
1238 | case Recurrence::rWeekly: |
1239 | { |
1240 | QBitArray qba(7); |
1241 | QString dayStr; |
1242 | if (index == last) { |
1243 | // e.g. W1 #0 |
1244 | qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); |
1245 | } else { |
1246 | // e.g. W1 SU #0 |
1247 | while (index < last) { |
1248 | dayStr = tmpStr.mid(index, 3); |
1249 | int dayNum = numFromDay(dayStr); |
1250 | if (dayNum >= 0) { |
1251 | qba.setBit(dayNum); |
1252 | } |
1253 | index += 3; // advance to next day, or possibly "#" |
1254 | } |
1255 | } |
1256 | anEvent->recurrence()->setWeekly(rFreq, qba); |
1257 | break; |
1258 | } |
1259 | |
1260 | case Recurrence::rMonthlyPos: |
1261 | { |
1262 | anEvent->recurrence()->setMonthly(rFreq); |
1263 | |
1264 | QBitArray qba(7); |
1265 | short tmpPos; |
1266 | if (index == last) { |
1267 | // e.g. MP1 #0 |
1268 | tmpPos = anEvent->dtStart().date().day() / 7 + 1; |
1269 | if (tmpPos == 5) { |
1270 | tmpPos = -1; |
1271 | } |
1272 | qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); |
1273 | anEvent->recurrence()->addMonthlyPos(tmpPos, qba); |
1274 | } else { |
1275 | // e.g. MP1 1+ SU #0 |
1276 | while (index < last) { |
1277 | tmpPos = tmpStr.mid(index, 1).toShort(); |
1278 | index += 1; |
1279 | if (tmpStr.mid(index, 1) == "-" ) { |
1280 | // convert tmpPos to negative |
1281 | tmpPos = 0 - tmpPos; |
1282 | } |
1283 | index += 2; // advance to day(s) |
1284 | while (numFromDay(tmpStr.mid(index, 3)) >= 0) { |
1285 | int dayNum = numFromDay(tmpStr.mid(index, 3)); |
1286 | qba.setBit(dayNum); |
1287 | index += 3; // advance to next day, or possibly pos or "#" |
1288 | } |
1289 | anEvent->recurrence()->addMonthlyPos(tmpPos, qba); |
1290 | qba.detach(); |
1291 | qba.fill(false); // clear out |
1292 | } // while != "#" |
1293 | } |
1294 | break; |
1295 | } |
1296 | |
1297 | case Recurrence::rMonthlyDay: |
1298 | anEvent->recurrence()->setMonthly(rFreq); |
1299 | if (index == last) { |
1300 | // e.g. MD1 #0 |
1301 | short tmpDay = anEvent->dtStart().date().day(); |
1302 | anEvent->recurrence()->addMonthlyDate(tmpDay); |
1303 | } else { |
1304 | // e.g. MD1 3 #0 |
1305 | while (index < last) { |
1306 | int index2 = tmpStr.indexOf(' ', index); |
1307 | if ((tmpStr.mid((index2 - 1), 1) == "-" ) || |
1308 | (tmpStr.mid((index2 - 1), 1) == "+" )) { |
1309 | index2 = index2 - 1; |
1310 | } |
1311 | short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); |
1312 | index = index2; |
1313 | if (tmpStr.mid(index, 1) == "-" ) { |
1314 | tmpDay = 0 - tmpDay; |
1315 | } |
1316 | index += 2; // advance the index; |
1317 | anEvent->recurrence()->addMonthlyDate(tmpDay); |
1318 | } // while != # |
1319 | } |
1320 | break; |
1321 | |
1322 | case Recurrence::rYearlyMonth: |
1323 | anEvent->recurrence()->setYearly(rFreq); |
1324 | |
1325 | if (index == last) { |
1326 | // e.g. YM1 #0 |
1327 | short tmpMonth = anEvent->dtStart().date().month(); |
1328 | anEvent->recurrence()->addYearlyMonth(tmpMonth); |
1329 | } else { |
1330 | // e.g. YM1 3 #0 |
1331 | while (index < last) { |
1332 | int index2 = tmpStr.indexOf(' ', index); |
1333 | short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort(); |
1334 | index = index2 + 1; |
1335 | anEvent->recurrence()->addYearlyMonth(tmpMonth); |
1336 | } // while != # |
1337 | } |
1338 | break; |
1339 | |
1340 | case Recurrence::rYearlyDay: |
1341 | anEvent->recurrence()->setYearly(rFreq); |
1342 | |
1343 | if (index == last) { |
1344 | // e.g. YD1 #0 |
1345 | short tmpDay = anEvent->dtStart().date().dayOfYear(); |
1346 | anEvent->recurrence()->addYearlyDay(tmpDay); |
1347 | } else { |
1348 | // e.g. YD1 123 #0 |
1349 | while (index < last) { |
1350 | int index2 = tmpStr.indexOf(' ', index); |
1351 | short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); |
1352 | index = index2 + 1; |
1353 | anEvent->recurrence()->addYearlyDay(tmpDay); |
1354 | } // while != # |
1355 | } |
1356 | break; |
1357 | |
1358 | default: |
1359 | break; |
1360 | } |
1361 | |
1362 | // find the last field, which is either the duration or the end date |
1363 | index = last; |
1364 | if (tmpStr.mid(index, 1) == "#" ) { |
1365 | // Nr of occurrences |
1366 | index++; |
1367 | int rDuration = tmpStr.mid(index, tmpStr.length() - index).toInt(); |
1368 | if (rDuration > 0) { |
1369 | anEvent->recurrence()->setDuration(rDuration); |
1370 | } |
1371 | } else if (tmpStr.indexOf('T', index) != -1) { |
1372 | KDateTime rEndDate = ISOToKDateTime(tmpStr.mid(index, tmpStr.length() - index)); |
1373 | anEvent->recurrence()->setEndDateTime(rEndDate); |
1374 | } |
1375 | } else { |
1376 | kDebug() << "we don't understand this type of recurrence!" ; |
1377 | } // if known recurrence type |
1378 | } // repeats |
1379 | |
1380 | // recurrence exceptions |
1381 | if ((vo = isAPropertyOf(vtodo, VCExpDateProp)) != 0) { |
1382 | s = fakeCString(vObjectUStringZValue(vo)); |
1383 | QStringList exDates = QString::fromUtf8(s).split(','); |
1384 | QStringList::ConstIterator it; |
1385 | for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) { |
1386 | KDateTime exDate = ISOToKDateTime(*it); |
1387 | if (exDate.time().hour() == 0 && |
1388 | exDate.time().minute() == 0 && |
1389 | exDate.time().second() == 0) { |
1390 | anEvent->recurrence()->addExDate(ISOToQDate(*it)); |
1391 | } else { |
1392 | anEvent->recurrence()->addExDateTime(exDate); |
1393 | } |
1394 | } |
1395 | deleteStr(s); |
1396 | } |
1397 | |
1398 | // alarm stuff |
1399 | if ((vo = isAPropertyOf(vtodo, VCDAlarmProp))) { |
1400 | Alarm::Ptr alarm; |
1401 | VObject *a; |
1402 | VObject *b; |
1403 | a = isAPropertyOf(vo, VCRunTimeProp); |
1404 | b = isAPropertyOf(vo, VCDisplayStringProp); |
1405 | |
1406 | if (a || b) { |
1407 | alarm = anEvent->newAlarm(); |
1408 | if (a) { |
1409 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
1410 | deleteStr(s); |
1411 | } |
1412 | alarm->setEnabled(true); |
1413 | if (b) { |
1414 | s = fakeCString(vObjectUStringZValue(b)); |
1415 | alarm->setDisplayAlarm(QString(s)); |
1416 | deleteStr(s); |
1417 | } else { |
1418 | alarm->setDisplayAlarm(QString()); |
1419 | } |
1420 | } |
1421 | } |
1422 | |
1423 | if ((vo = isAPropertyOf(vtodo, VCAAlarmProp))) { |
1424 | Alarm::Ptr alarm; |
1425 | VObject *a; |
1426 | VObject *b; |
1427 | a = isAPropertyOf(vo, VCRunTimeProp); |
1428 | b = isAPropertyOf(vo, VCAudioContentProp); |
1429 | |
1430 | if (a || b) { |
1431 | alarm = anEvent->newAlarm(); |
1432 | if (a) { |
1433 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
1434 | deleteStr(s); |
1435 | } |
1436 | alarm->setEnabled(true); |
1437 | if (b) { |
1438 | s = fakeCString(vObjectUStringZValue(b)); |
1439 | alarm->setAudioAlarm(QFile::decodeName(s)); |
1440 | deleteStr(s); |
1441 | } else { |
1442 | alarm->setAudioAlarm(QString()); |
1443 | } |
1444 | } |
1445 | } |
1446 | |
1447 | if ((vo = isAPropertyOf(vtodo, VCPAlarmProp))) { |
1448 | Alarm::Ptr alarm; |
1449 | VObject *a; |
1450 | VObject *b; |
1451 | a = isAPropertyOf(vo, VCRunTimeProp); |
1452 | b = isAPropertyOf(vo, VCProcedureNameProp); |
1453 | |
1454 | if (a || b) { |
1455 | alarm = anEvent->newAlarm(); |
1456 | if (a) { |
1457 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
1458 | deleteStr(s); |
1459 | } |
1460 | alarm->setEnabled(true); |
1461 | |
1462 | if (b) { |
1463 | s = fakeCString(vObjectUStringZValue(b)); |
1464 | alarm->setProcedureAlarm(QFile::decodeName(s)); |
1465 | deleteStr(s); |
1466 | } else { |
1467 | alarm->setProcedureAlarm(QString()); |
1468 | } |
1469 | } |
1470 | } |
1471 | |
1472 | // related todo |
1473 | if ((vo = isAPropertyOf(vtodo, VCRelatedToProp)) != 0) { |
1474 | anEvent->setRelatedTo(s = fakeCString(vObjectUStringZValue(vo))); |
1475 | deleteStr(s); |
1476 | d->mTodosRelate.append(anEvent); |
1477 | } |
1478 | |
1479 | // secrecy |
1480 | Incidence::Secrecy secrecy = Incidence::SecrecyPublic; |
1481 | if ((vo = isAPropertyOf(vtodo, VCClassProp)) != 0) { |
1482 | s = fakeCString(vObjectUStringZValue(vo)); |
1483 | if (s && strcmp(s, "PRIVATE" ) == 0) { |
1484 | secrecy = Incidence::SecrecyPrivate; |
1485 | } else if (s && strcmp(s, "CONFIDENTIAL" ) == 0) { |
1486 | secrecy = Incidence::SecrecyConfidential; |
1487 | } |
1488 | deleteStr(s); |
1489 | } |
1490 | anEvent->setSecrecy(secrecy); |
1491 | |
1492 | // categories |
1493 | if ((vo = isAPropertyOf(vtodo, VCCategoriesProp)) != 0) { |
1494 | s = fakeCString(vObjectUStringZValue(vo)); |
1495 | QString categories = QString::fromUtf8(s); |
1496 | deleteStr(s); |
1497 | QStringList tmpStrList = categories.split(';'); |
1498 | anEvent->setCategories(tmpStrList); |
1499 | } |
1500 | |
1501 | /* PILOT SYNC STUFF */ |
1502 | if ((vo = isAPropertyOf(vtodo, KPilotIdProp))) { |
1503 | anEvent->setNonKDECustomProperty( |
1504 | KPilotIdProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); |
1505 | deleteStr(s); |
1506 | if ((vo = isAPropertyOf(vtodo, KPilotStatusProp))) { |
1507 | anEvent->setNonKDECustomProperty( |
1508 | KPilotStatusProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); |
1509 | deleteStr(s); |
1510 | } else { |
1511 | anEvent->setNonKDECustomProperty(KPilotStatusProp, QString::number(int(SYNCMOD))); |
1512 | } |
1513 | } |
1514 | |
1515 | return anEvent; |
1516 | } |
1517 | |
1518 | Event::Ptr VCalFormat::VEventToEvent(VObject *vevent) |
1519 | { |
1520 | VObject *vo; |
1521 | VObjectIterator voi; |
1522 | char *s; |
1523 | |
1524 | Event::Ptr anEvent(new Event); |
1525 | |
1526 | // creation date |
1527 | if ((vo = isAPropertyOf(vevent, VCDCreatedProp)) != 0) { |
1528 | anEvent->setCreated(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1529 | deleteStr(s); |
1530 | } |
1531 | |
1532 | // unique id |
1533 | vo = isAPropertyOf(vevent, VCUniqueStringProp); |
1534 | // while the UID property is preferred, it is not required. We'll use the |
1535 | // default Event UID if none is given. |
1536 | if (vo) { |
1537 | anEvent->setUid(s = fakeCString(vObjectUStringZValue(vo))); |
1538 | deleteStr(s); |
1539 | } |
1540 | |
1541 | #if defined(KCALCORE_FOR_SYMBIAN) |
1542 | // recurrence id |
1543 | vo = isAPropertyOf(vevent, VCRecurrenceIdProp); |
1544 | if (vo) { |
1545 | anEvent->setRecurrenceId(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1546 | deleteStr(s); |
1547 | } |
1548 | #endif |
1549 | |
1550 | // revision |
1551 | // again NSCAL doesn't give us much to work with, so we improvise... |
1552 | anEvent->setRevision(0); |
1553 | if ((vo = isAPropertyOf(vevent, VCSequenceProp)) != 0) { |
1554 | s = fakeCString(vObjectUStringZValue(vo)); |
1555 | if (s) { |
1556 | anEvent->setRevision(atoi(s)); |
1557 | deleteStr(s); |
1558 | } |
1559 | } |
1560 | |
1561 | // last modification date |
1562 | if ((vo = isAPropertyOf(vevent, VCLastModifiedProp)) != 0) { |
1563 | anEvent->setLastModified(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1564 | deleteStr(s); |
1565 | } else { |
1566 | anEvent->setLastModified(KDateTime::currentUtcDateTime()); |
1567 | } |
1568 | |
1569 | // organizer |
1570 | // if our extension property for the event's ORGANIZER exists, add it. |
1571 | if ((vo = isAPropertyOf(vevent, ICOrganizerProp)) != 0) { |
1572 | // FIXME: Also use the full name, not just the email address |
1573 | anEvent->setOrganizer(s = fakeCString(vObjectUStringZValue(vo))); |
1574 | deleteStr(s); |
1575 | } else { |
1576 | if (d->mCalendar->owner()->name() != "Unknown Name" ) { |
1577 | anEvent->setOrganizer(d->mCalendar->owner()); |
1578 | } |
1579 | } |
1580 | |
1581 | // deal with attendees. |
1582 | initPropIterator(&voi, vevent); |
1583 | while (moreIteration(&voi)) { |
1584 | vo = nextVObject(&voi); |
1585 | if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) { |
1586 | Attendee::Ptr a; |
1587 | VObject *vp; |
1588 | s = fakeCString(vObjectUStringZValue(vo)); |
1589 | QString tmpStr = QString::fromUtf8(s); |
1590 | deleteStr(s); |
1591 | tmpStr = tmpStr.simplified(); |
1592 | int emailPos1, emailPos2; |
1593 | if ((emailPos1 = tmpStr.indexOf('<')) > 0) { |
1594 | // both email address and name |
1595 | emailPos2 = tmpStr.lastIndexOf('>'); |
1596 | a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1), |
1597 | tmpStr.mid(emailPos1 + 1, |
1598 | emailPos2 - (emailPos1 + 1)))); |
1599 | } else if (tmpStr.indexOf('@') > 0) { |
1600 | // just an email address |
1601 | a = Attendee::Ptr(new Attendee(0, tmpStr)); |
1602 | } else { |
1603 | // just a name |
1604 | QString email = tmpStr.replace(' ', '.'); |
1605 | a = Attendee::Ptr(new Attendee(tmpStr, email)); |
1606 | } |
1607 | |
1608 | // is there an RSVP property? |
1609 | if ((vp = isAPropertyOf(vo, VCRSVPProp)) != 0) { |
1610 | a->setRSVP(vObjectStringZValue(vp)); |
1611 | } |
1612 | // is there a status property? |
1613 | if ((vp = isAPropertyOf(vo, VCStatusProp)) != 0) { |
1614 | a->setStatus(readStatus(vObjectStringZValue(vp))); |
1615 | } |
1616 | // add the attendee |
1617 | anEvent->addAttendee(a); |
1618 | } |
1619 | } |
1620 | |
1621 | // This isn't strictly true. An event that doesn't have a start time |
1622 | // or an end time isn't all-day, it has an anchor in time but it doesn't |
1623 | // "take up" any time. |
1624 | /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) || |
1625 | (isAPropertyOf(vevent, VCDTendProp) == 0)) { |
1626 | anEvent->setAllDay(true); |
1627 | } else { |
1628 | }*/ |
1629 | |
1630 | anEvent->setAllDay(false); |
1631 | |
1632 | // start time |
1633 | if ((vo = isAPropertyOf(vevent, VCDTstartProp)) != 0) { |
1634 | anEvent->setDtStart(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1635 | deleteStr(s); |
1636 | |
1637 | if (anEvent->dtStart().time().hour() == 0 && |
1638 | anEvent->dtStart().time().minute() == 0 && |
1639 | anEvent->dtStart().time().second() == 0) { |
1640 | #if defined(KCALCORE_FOR_MEEGO) |
1641 | QDate startDate = anEvent->dtStart().date(); |
1642 | anEvent->setDtStart(KDateTime(startDate, KDateTime::ClockTime)); |
1643 | #endif |
1644 | anEvent->setAllDay(true); |
1645 | } |
1646 | } |
1647 | |
1648 | // stop time |
1649 | if ((vo = isAPropertyOf(vevent, VCDTendProp)) != 0) { |
1650 | anEvent->setDtEnd(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo)))); |
1651 | deleteStr(s); |
1652 | |
1653 | if (anEvent->dtEnd().time().hour() == 0 && |
1654 | anEvent->dtEnd().time().minute() == 0 && |
1655 | anEvent->dtEnd().time().second() == 0) { |
1656 | #if defined(KCALCORE_FOR_MEEGO) |
1657 | QDate endDate = anEvent->dtEnd().date(); |
1658 | anEvent->setDtEnd(KDateTime(endDate, KDateTime::ClockTime)); |
1659 | #endif |
1660 | anEvent->setAllDay(true); |
1661 | } |
1662 | } |
1663 | #if defined(KCALCORE_FOR_MEEGO) |
1664 | if (anEvent->allDay()) { |
1665 | if (anEvent->dtEnd() == anEvent->dtStart()) { |
1666 | anEvent->setDtEnd(anEvent->dtEnd().addDays(1)); |
1667 | } |
1668 | } |
1669 | #endif |
1670 | |
1671 | // at this point, there should be at least a start or end time. |
1672 | // fix up for events that take up no time but have a time associated |
1673 | if (!isAPropertyOf(vevent, VCDTstartProp)) { |
1674 | anEvent->setDtStart(anEvent->dtEnd()); |
1675 | } |
1676 | if (! isAPropertyOf(vevent, VCDTendProp)) { |
1677 | anEvent->setDtEnd(anEvent->dtStart()); |
1678 | } |
1679 | |
1680 | /////////////////////////////////////////////////////////////////////////// |
1681 | |
1682 | // repeat stuff |
1683 | if ((vo = isAPropertyOf(vevent, VCRRuleProp)) != 0) { |
1684 | QString tmpStr = (s = fakeCString(vObjectUStringZValue(vo))); |
1685 | deleteStr(s); |
1686 | tmpStr = tmpStr.simplified(); |
1687 | tmpStr = tmpStr.toUpper(); |
1688 | // first, read the type of the recurrence |
1689 | int typelen = 1; |
1690 | uint type = Recurrence::rNone; |
1691 | if (tmpStr.left(1) == "D" ) { |
1692 | type = Recurrence::rDaily; |
1693 | } else if (tmpStr.left(1) == "W" ) { |
1694 | type = Recurrence::rWeekly; |
1695 | } else { |
1696 | typelen = 2; |
1697 | if (tmpStr.left(2) == "MP" ) { |
1698 | type = Recurrence::rMonthlyPos; |
1699 | } else if (tmpStr.left(2) == "MD" ) { |
1700 | type = Recurrence::rMonthlyDay; |
1701 | } else if (tmpStr.left(2) == "YM" ) { |
1702 | type = Recurrence::rYearlyMonth; |
1703 | } else if (tmpStr.left(2) == "YD" ) { |
1704 | type = Recurrence::rYearlyDay; |
1705 | } |
1706 | } |
1707 | |
1708 | if (type != Recurrence::rNone) { |
1709 | |
1710 | // Immediately after the type is the frequency |
1711 | int index = tmpStr.indexOf(' '); |
1712 | int last = tmpStr.lastIndexOf(' ') + 1; // find last entry |
1713 | int rFreq = tmpStr.mid(typelen, (index - 1)).toInt(); |
1714 | ++index; // advance to beginning of stuff after freq |
1715 | |
1716 | // Read the type-specific settings |
1717 | switch (type) { |
1718 | case Recurrence::rDaily: |
1719 | anEvent->recurrence()->setDaily(rFreq); |
1720 | break; |
1721 | |
1722 | case Recurrence::rWeekly: |
1723 | { |
1724 | QBitArray qba(7); |
1725 | QString dayStr; |
1726 | if (index == last) { |
1727 | // e.g. W1 #0 |
1728 | qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); |
1729 | } else { |
1730 | // e.g. W1 SU #0 |
1731 | while (index < last) { |
1732 | dayStr = tmpStr.mid(index, 3); |
1733 | int dayNum = numFromDay(dayStr); |
1734 | if (dayNum >= 0) { |
1735 | qba.setBit(dayNum); |
1736 | } |
1737 | index += 3; // advance to next day, or possibly "#" |
1738 | } |
1739 | } |
1740 | anEvent->recurrence()->setWeekly(rFreq, qba); |
1741 | break; |
1742 | } |
1743 | |
1744 | case Recurrence::rMonthlyPos: |
1745 | { |
1746 | anEvent->recurrence()->setMonthly(rFreq); |
1747 | |
1748 | QBitArray qba(7); |
1749 | short tmpPos; |
1750 | if (index == last) { |
1751 | // e.g. MP1 #0 |
1752 | tmpPos = anEvent->dtStart().date().day() / 7 + 1; |
1753 | if (tmpPos == 5) { |
1754 | tmpPos = -1; |
1755 | } |
1756 | qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); |
1757 | anEvent->recurrence()->addMonthlyPos(tmpPos, qba); |
1758 | } else { |
1759 | // e.g. MP1 1+ SU #0 |
1760 | while (index < last) { |
1761 | tmpPos = tmpStr.mid(index, 1).toShort(); |
1762 | index += 1; |
1763 | if (tmpStr.mid(index, 1) == "-" ) { |
1764 | // convert tmpPos to negative |
1765 | tmpPos = 0 - tmpPos; |
1766 | } |
1767 | index += 2; // advance to day(s) |
1768 | while (numFromDay(tmpStr.mid(index, 3)) >= 0) { |
1769 | int dayNum = numFromDay(tmpStr.mid(index, 3)); |
1770 | qba.setBit(dayNum); |
1771 | index += 3; // advance to next day, or possibly pos or "#" |
1772 | } |
1773 | anEvent->recurrence()->addMonthlyPos(tmpPos, qba); |
1774 | qba.detach(); |
1775 | qba.fill(false); // clear out |
1776 | } // while != "#" |
1777 | } |
1778 | break; |
1779 | } |
1780 | |
1781 | case Recurrence::rMonthlyDay: |
1782 | anEvent->recurrence()->setMonthly(rFreq); |
1783 | if (index == last) { |
1784 | // e.g. MD1 #0 |
1785 | short tmpDay = anEvent->dtStart().date().day(); |
1786 | anEvent->recurrence()->addMonthlyDate(tmpDay); |
1787 | } else { |
1788 | // e.g. MD1 3 #0 |
1789 | while (index < last) { |
1790 | int index2 = tmpStr.indexOf(' ', index); |
1791 | if ((tmpStr.mid((index2 - 1), 1) == "-" ) || |
1792 | (tmpStr.mid((index2 - 1), 1) == "+" )) { |
1793 | index2 = index2 - 1; |
1794 | } |
1795 | short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); |
1796 | index = index2; |
1797 | if (tmpStr.mid(index, 1) == "-" ) { |
1798 | tmpDay = 0 - tmpDay; |
1799 | } |
1800 | index += 2; // advance the index; |
1801 | anEvent->recurrence()->addMonthlyDate(tmpDay); |
1802 | } // while != # |
1803 | } |
1804 | break; |
1805 | |
1806 | case Recurrence::rYearlyMonth: |
1807 | anEvent->recurrence()->setYearly(rFreq); |
1808 | |
1809 | if (index == last) { |
1810 | // e.g. YM1 #0 |
1811 | short tmpMonth = anEvent->dtStart().date().month(); |
1812 | anEvent->recurrence()->addYearlyMonth(tmpMonth); |
1813 | } else { |
1814 | // e.g. YM1 3 #0 |
1815 | while (index < last) { |
1816 | int index2 = tmpStr.indexOf(' ', index); |
1817 | short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort(); |
1818 | index = index2 + 1; |
1819 | anEvent->recurrence()->addYearlyMonth(tmpMonth); |
1820 | } // while != # |
1821 | } |
1822 | break; |
1823 | |
1824 | case Recurrence::rYearlyDay: |
1825 | anEvent->recurrence()->setYearly(rFreq); |
1826 | |
1827 | if (index == last) { |
1828 | // e.g. YD1 #0 |
1829 | short tmpDay = anEvent->dtStart().date().dayOfYear(); |
1830 | anEvent->recurrence()->addYearlyDay(tmpDay); |
1831 | } else { |
1832 | // e.g. YD1 123 #0 |
1833 | while (index < last) { |
1834 | int index2 = tmpStr.indexOf(' ', index); |
1835 | short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); |
1836 | index = index2 + 1; |
1837 | anEvent->recurrence()->addYearlyDay(tmpDay); |
1838 | } // while != # |
1839 | } |
1840 | break; |
1841 | |
1842 | default: |
1843 | break; |
1844 | } |
1845 | |
1846 | // find the last field, which is either the duration or the end date |
1847 | index = last; |
1848 | if (tmpStr.mid(index, 1) == "#" ) { |
1849 | // Nr of occurrences |
1850 | index++; |
1851 | int rDuration = tmpStr.mid(index, tmpStr.length() - index).toInt(); |
1852 | if (rDuration > 0) { |
1853 | anEvent->recurrence()->setDuration(rDuration); |
1854 | } |
1855 | } else if (tmpStr.indexOf('T', index) != -1) { |
1856 | KDateTime rEndDate = ISOToKDateTime(tmpStr.mid(index, tmpStr.length() - index)); |
1857 | anEvent->recurrence()->setEndDateTime(rEndDate); |
1858 | } |
1859 | // anEvent->recurrence()->dump(); |
1860 | |
1861 | } else { |
1862 | kDebug() << "we don't understand this type of recurrence!" ; |
1863 | } // if known recurrence type |
1864 | } // repeats |
1865 | |
1866 | // recurrence exceptions |
1867 | if ((vo = isAPropertyOf(vevent, VCExpDateProp)) != 0) { |
1868 | s = fakeCString(vObjectUStringZValue(vo)); |
1869 | QStringList exDates = QString::fromUtf8(s).split(','); |
1870 | QStringList::ConstIterator it; |
1871 | for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) { |
1872 | KDateTime exDate = ISOToKDateTime(*it); |
1873 | if (exDate.time().hour() == 0 && |
1874 | exDate.time().minute() == 0 && |
1875 | exDate.time().second() == 0) { |
1876 | anEvent->recurrence()->addExDate(ISOToQDate(*it)); |
1877 | } else { |
1878 | anEvent->recurrence()->addExDateTime(exDate); |
1879 | } |
1880 | } |
1881 | deleteStr(s); |
1882 | } |
1883 | |
1884 | // summary |
1885 | if ((vo = isAPropertyOf(vevent, VCSummaryProp))) { |
1886 | s = fakeCString(vObjectUStringZValue(vo)); |
1887 | anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(s)); |
1888 | deleteStr(s); |
1889 | } |
1890 | |
1891 | // description |
1892 | if ((vo = isAPropertyOf(vevent, VCDescriptionProp)) != 0) { |
1893 | s = fakeCString(vObjectUStringZValue(vo)); |
1894 | bool isRich = Qt::mightBeRichText(s); |
1895 | if (!anEvent->description().isEmpty()) { |
1896 | anEvent->setDescription( |
1897 | anEvent->description() + '\n' + QString::fromUtf8(s), isRich); |
1898 | } else { |
1899 | anEvent->setDescription(QString::fromUtf8(s), isRich); |
1900 | } |
1901 | deleteStr(s); |
1902 | } |
1903 | |
1904 | // location |
1905 | if ((vo = isAPropertyOf(vevent, VCLocationProp)) != 0) { |
1906 | s = fakeCString(vObjectUStringZValue(vo)); |
1907 | anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(s)); |
1908 | deleteStr(s); |
1909 | } |
1910 | |
1911 | // some stupid vCal exporters ignore the standard and use Description |
1912 | // instead of Summary for the default field. Correct for this. |
1913 | if (anEvent->summary().isEmpty() && !(anEvent->description().isEmpty())) { |
1914 | QString tmpStr = anEvent->description().simplified(); |
1915 | anEvent->setDescription("" ); |
1916 | anEvent->setSummary(tmpStr); |
1917 | } |
1918 | |
1919 | #if 0 |
1920 | // status |
1921 | if ((vo = isAPropertyOf(vevent, VCStatusProp)) != 0) { |
1922 | QString tmpStr(s = fakeCString(vObjectUStringZValue(vo))); |
1923 | deleteStr(s); |
1924 | // TODO: Define Event status |
1925 | // anEvent->setStatus( tmpStr ); |
1926 | } else { |
1927 | // anEvent->setStatus( "NEEDS ACTION" ); |
1928 | } |
1929 | #endif |
1930 | |
1931 | // secrecy |
1932 | Incidence::Secrecy secrecy = Incidence::SecrecyPublic; |
1933 | if ((vo = isAPropertyOf(vevent, VCClassProp)) != 0) { |
1934 | s = fakeCString(vObjectUStringZValue(vo)); |
1935 | if (s && strcmp(s, "PRIVATE" ) == 0) { |
1936 | secrecy = Incidence::SecrecyPrivate; |
1937 | } else if (s && strcmp(s, "CONFIDENTIAL" ) == 0) { |
1938 | secrecy = Incidence::SecrecyConfidential; |
1939 | } |
1940 | deleteStr(s); |
1941 | } |
1942 | anEvent->setSecrecy(secrecy); |
1943 | |
1944 | // categories |
1945 | if ((vo = isAPropertyOf(vevent, VCCategoriesProp)) != 0) { |
1946 | s = fakeCString(vObjectUStringZValue(vo)); |
1947 | QString categories = QString::fromUtf8(s); |
1948 | deleteStr(s); |
1949 | QStringList tmpStrList = categories.split(','); |
1950 | anEvent->setCategories(tmpStrList); |
1951 | } |
1952 | |
1953 | // attachments |
1954 | initPropIterator(&voi, vevent); |
1955 | while (moreIteration(&voi)) { |
1956 | vo = nextVObject(&voi); |
1957 | if (strcmp(vObjectName(vo), VCAttachProp) == 0) { |
1958 | s = fakeCString(vObjectUStringZValue(vo)); |
1959 | anEvent->addAttachment(Attachment::Ptr(new Attachment(QString(s)))); |
1960 | deleteStr(s); |
1961 | } |
1962 | } |
1963 | |
1964 | // resources |
1965 | if ((vo = isAPropertyOf(vevent, VCResourcesProp)) != 0) { |
1966 | QString resources = (s = fakeCString(vObjectUStringZValue(vo))); |
1967 | deleteStr(s); |
1968 | QStringList tmpStrList = resources.split(';'); |
1969 | anEvent->setResources(tmpStrList); |
1970 | } |
1971 | |
1972 | // alarm stuff |
1973 | if ((vo = isAPropertyOf(vevent, VCDAlarmProp))) { |
1974 | Alarm::Ptr alarm; |
1975 | VObject *a; |
1976 | VObject *b; |
1977 | a = isAPropertyOf(vo, VCRunTimeProp); |
1978 | b = isAPropertyOf(vo, VCDisplayStringProp); |
1979 | |
1980 | if (a || b) { |
1981 | alarm = anEvent->newAlarm(); |
1982 | if (a) { |
1983 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
1984 | deleteStr(s); |
1985 | } |
1986 | alarm->setEnabled(true); |
1987 | |
1988 | if (b) { |
1989 | s = fakeCString(vObjectUStringZValue(b)); |
1990 | alarm->setDisplayAlarm(QString(s)); |
1991 | deleteStr(s); |
1992 | } else { |
1993 | alarm->setDisplayAlarm(QString()); |
1994 | } |
1995 | } |
1996 | } |
1997 | |
1998 | if ((vo = isAPropertyOf(vevent, VCAAlarmProp))) { |
1999 | Alarm::Ptr alarm; |
2000 | VObject *a; |
2001 | VObject *b; |
2002 | a = isAPropertyOf(vo, VCRunTimeProp); |
2003 | b = isAPropertyOf(vo, VCAudioContentProp); |
2004 | |
2005 | if (a || b) { |
2006 | alarm = anEvent->newAlarm(); |
2007 | if (a) { |
2008 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
2009 | deleteStr(s); |
2010 | } |
2011 | alarm->setEnabled(true); |
2012 | |
2013 | if (b) { |
2014 | s = fakeCString(vObjectUStringZValue(b)); |
2015 | alarm->setAudioAlarm(QFile::decodeName(s)); |
2016 | deleteStr(s); |
2017 | } else { |
2018 | alarm->setAudioAlarm(QString()); |
2019 | } |
2020 | } |
2021 | } |
2022 | |
2023 | if ((vo = isAPropertyOf(vevent, VCPAlarmProp))) { |
2024 | Alarm::Ptr alarm; |
2025 | VObject *a; |
2026 | VObject *b; |
2027 | a = isAPropertyOf(vo, VCRunTimeProp); |
2028 | b = isAPropertyOf(vo, VCProcedureNameProp); |
2029 | |
2030 | if (a || b) { |
2031 | alarm = anEvent->newAlarm(); |
2032 | if (a) { |
2033 | alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a)))); |
2034 | deleteStr(s); |
2035 | } |
2036 | alarm->setEnabled(true); |
2037 | |
2038 | if (b) { |
2039 | s = fakeCString(vObjectUStringZValue(b)); |
2040 | alarm->setProcedureAlarm(QFile::decodeName(s)); |
2041 | deleteStr(s); |
2042 | } else { |
2043 | alarm->setProcedureAlarm(QString()); |
2044 | } |
2045 | } |
2046 | } |
2047 | |
2048 | // priority |
2049 | if ((vo = isAPropertyOf(vevent, VCPriorityProp))) { |
2050 | s = fakeCString(vObjectUStringZValue(vo)); |
2051 | if (s) { |
2052 | anEvent->setPriority(atoi(s)); |
2053 | deleteStr(s); |
2054 | } |
2055 | } |
2056 | |
2057 | // transparency |
2058 | if ((vo = isAPropertyOf(vevent, VCTranspProp)) != 0) { |
2059 | s = fakeCString(vObjectUStringZValue(vo)); |
2060 | if (s) { |
2061 | int i = atoi(s); |
2062 | anEvent->setTransparency(i == 1 ? Event::Transparent : Event::Opaque); |
2063 | deleteStr(s); |
2064 | } |
2065 | } |
2066 | |
2067 | // related event |
2068 | if ((vo = isAPropertyOf(vevent, VCRelatedToProp)) != 0) { |
2069 | anEvent->setRelatedTo(s = fakeCString(vObjectUStringZValue(vo))); |
2070 | deleteStr(s); |
2071 | d->mEventsRelate.append(anEvent); |
2072 | } |
2073 | |
2074 | /* PILOT SYNC STUFF */ |
2075 | if ((vo = isAPropertyOf(vevent, KPilotIdProp))) { |
2076 | anEvent->setNonKDECustomProperty( |
2077 | KPilotIdProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); |
2078 | deleteStr(s); |
2079 | if ((vo = isAPropertyOf(vevent, KPilotStatusProp))) { |
2080 | anEvent->setNonKDECustomProperty( |
2081 | KPilotStatusProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); |
2082 | deleteStr(s); |
2083 | } else { |
2084 | anEvent->setNonKDECustomProperty(KPilotStatusProp, QString::number(int(SYNCMOD))); |
2085 | } |
2086 | } |
2087 | |
2088 | /* Rest of the custom properties */ |
2089 | readCustomProperties(vevent, anEvent); |
2090 | |
2091 | return anEvent; |
2092 | } |
2093 | |
2094 | QString VCalFormat::parseTZ(const QByteArray &timezone) const |
2095 | { |
2096 | // kDebug() << timezone; |
2097 | QString pZone = timezone.mid(timezone.indexOf("TZID:VCAL" ) + 9); |
2098 | return pZone.mid(0, pZone.indexOf(QChar(QLatin1Char('\n')))); |
2099 | } |
2100 | |
2101 | QString VCalFormat::parseDst(QByteArray &timezone) const |
2102 | { |
2103 | if (!timezone.contains("BEGIN:DAYLIGHT" )) { |
2104 | return QString(); |
2105 | } |
2106 | |
2107 | timezone = timezone.mid(timezone.indexOf("BEGIN:DAYLIGHT" )); |
2108 | timezone = timezone.mid(timezone.indexOf("TZNAME:" ) + 7); |
2109 | QString sStart = timezone.mid(0, (timezone.indexOf("COMMENT:" ))); |
2110 | sStart.chop(2); |
2111 | timezone = timezone.mid(timezone.indexOf("TZOFFSETTO:" ) + 11); |
2112 | QString sOffset = timezone.mid(0, (timezone.indexOf("DTSTART:" ))); |
2113 | sOffset.chop(2); |
2114 | sOffset.insert(3, QString(":" )); |
2115 | timezone = timezone.mid(timezone.indexOf("TZNAME:" ) + 7); |
2116 | QString sEnd = timezone.mid(0, (timezone.indexOf("COMMENT:" ))); |
2117 | sEnd.chop(2); |
2118 | |
2119 | return "TRUE;" + sOffset + ';' + sStart + ';' + sEnd + ";;" ; |
2120 | } |
2121 | |
2122 | QString VCalFormat::qDateToISO(const QDate &qd) |
2123 | { |
2124 | QString tmpStr; |
2125 | |
2126 | if (!qd.isValid()) { |
2127 | return QString(); |
2128 | } |
2129 | |
2130 | tmpStr.sprintf("%.2d%.2d%.2d" , qd.year(), qd.month(), qd.day()); |
2131 | return tmpStr; |
2132 | |
2133 | } |
2134 | |
2135 | QString VCalFormat::kDateTimeToISO(const KDateTime &dt, bool zulu) |
2136 | { |
2137 | QString tmpStr; |
2138 | |
2139 | if (!dt.isValid()) { |
2140 | return QString(); |
2141 | } |
2142 | |
2143 | QDateTime tmpDT; |
2144 | if (zulu) { |
2145 | tmpDT = dt.toUtc().dateTime(); |
2146 | } else { |
2147 | #if !defined(KCALCORE_FOR_MEEGO) |
2148 | tmpDT = dt.toTimeSpec(d->mCalendar->timeSpec()).dateTime(); |
2149 | #else |
2150 | tmpDT = dt.dateTime(); |
2151 | #endif |
2152 | } |
2153 | tmpStr.sprintf("%.2d%.2d%.2dT%.2d%.2d%.2d" , |
2154 | tmpDT.date().year(), tmpDT.date().month(), |
2155 | tmpDT.date().day(), tmpDT.time().hour(), |
2156 | tmpDT.time().minute(), tmpDT.time().second()); |
2157 | if (zulu || dt.isUtc()) { |
2158 | tmpStr += 'Z'; |
2159 | } |
2160 | return tmpStr; |
2161 | } |
2162 | |
2163 | KDateTime VCalFormat::ISOToKDateTime(const QString &dtStr) |
2164 | { |
2165 | QDate tmpDate; |
2166 | QTime tmpTime; |
2167 | QString tmpStr; |
2168 | int year, month, day, hour, minute, second; |
2169 | |
2170 | tmpStr = dtStr; |
2171 | year = tmpStr.left(4).toInt(); |
2172 | month = tmpStr.mid(4, 2).toInt(); |
2173 | day = tmpStr.mid(6, 2).toInt(); |
2174 | hour = tmpStr.mid(9, 2).toInt(); |
2175 | minute = tmpStr.mid(11, 2).toInt(); |
2176 | second = tmpStr.mid(13, 2).toInt(); |
2177 | tmpDate.setYMD(year, month, day); |
2178 | tmpTime.setHMS(hour, minute, second); |
2179 | |
2180 | if (tmpDate.isValid() && tmpTime.isValid()) { |
2181 | // correct for GMT if string is in Zulu format |
2182 | if (dtStr.at(dtStr.length() - 1) == 'Z') { |
2183 | return KDateTime(tmpDate, tmpTime, KDateTime::UTC); |
2184 | } else { |
2185 | return KDateTime(tmpDate, tmpTime, d->mCalendar->timeSpec()); |
2186 | } |
2187 | } else { |
2188 | return KDateTime(); |
2189 | } |
2190 | } |
2191 | |
2192 | QDate VCalFormat::ISOToQDate(const QString &dateStr) |
2193 | { |
2194 | int year, month, day; |
2195 | |
2196 | year = dateStr.left(4).toInt(); |
2197 | month = dateStr.mid(4, 2).toInt(); |
2198 | day = dateStr.mid(6, 2).toInt(); |
2199 | |
2200 | return QDate(year, month, day); |
2201 | } |
2202 | |
2203 | bool VCalFormat::parseTZOffsetISO8601(const QString &s, int &result) |
2204 | { |
2205 | // ISO8601 format(s): |
2206 | // +- hh : mm |
2207 | // +- hh mm |
2208 | // +- hh |
2209 | |
2210 | // We also accept broken one without + |
2211 | int mod = 1; |
2212 | int v = 0; |
2213 | QString str = s.trimmed(); |
2214 | int ofs = 0; |
2215 | result = 0; |
2216 | |
2217 | // Check for end |
2218 | if (str.size() <= ofs) { |
2219 | return false; |
2220 | } |
2221 | if (str[ofs] == '-') { |
2222 | mod = -1; |
2223 | ofs++; |
2224 | } else if (str[ofs] == '+') { |
2225 | ofs++; |
2226 | } |
2227 | if (str.size() <= ofs) { |
2228 | return false; |
2229 | } |
2230 | |
2231 | // Make sure next two values are numbers |
2232 | bool ok; |
2233 | |
2234 | if (str.size() < (ofs + 2)) { |
2235 | return false; |
2236 | } |
2237 | |
2238 | v = str.mid(ofs, 2).toInt(&ok) * 60; |
2239 | if (!ok) { |
2240 | return false; |
2241 | } |
2242 | ofs += 2; |
2243 | |
2244 | if (str.size() > ofs) { |
2245 | if (str[ofs] == ':') { |
2246 | ofs++; |
2247 | } |
2248 | if (str.size() > ofs) { |
2249 | if (str.size() < (ofs + 2)) { |
2250 | return false; |
2251 | } |
2252 | v += str.mid(ofs, 2).toInt(&ok); |
2253 | if (!ok) { |
2254 | return false; |
2255 | } |
2256 | } |
2257 | } |
2258 | result = v * mod * 60; |
2259 | return true; |
2260 | } |
2261 | |
2262 | // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. |
2263 | // and break it down from it's tree-like format into the dictionary format |
2264 | // that is used internally in the VCalFormat. |
2265 | void VCalFormat::populate(VObject *vcal, bool deleted, const QString ¬ebook) |
2266 | { |
2267 | Q_UNUSED(notebook); |
2268 | // this function will populate the caldict dictionary and other event |
2269 | // lists. It turns vevents into Events and then inserts them. |
2270 | |
2271 | VObjectIterator i; |
2272 | VObject *curVO, *curVOProp; |
2273 | Event::Ptr anEvent; |
2274 | bool hasTimeZone = false; //The calendar came with a TZ and not UTC |
2275 | KDateTime::Spec previousSpec; //If we add a new TZ we should leave the spec as it was before |
2276 | |
2277 | if ((curVO = isAPropertyOf(vcal, ICMethodProp)) != 0) { |
2278 | char *methodType = 0; |
2279 | methodType = fakeCString(vObjectUStringZValue(curVO)); |
2280 | // kDebug() << "This calendar is an iTIP transaction of type '" << methodType << "'"; |
2281 | deleteStr(methodType); |
2282 | } |
2283 | |
2284 | // warn the user that we might have trouble reading non-known calendar. |
2285 | if ((curVO = isAPropertyOf(vcal, VCProdIdProp)) != 0) { |
2286 | char *s = fakeCString(vObjectUStringZValue(curVO)); |
2287 | if (!s || strcmp(productId().toUtf8(), s) != 0) { |
2288 | kDebug() << "This vCalendar file was not created by KOrganizer or" |
2289 | << "any other product we support. Loading anyway..." ; |
2290 | } |
2291 | setLoadedProductId(s); |
2292 | deleteStr(s); |
2293 | } |
2294 | |
2295 | // warn the user we might have trouble reading this unknown version. |
2296 | if ((curVO = isAPropertyOf(vcal, VCVersionProp)) != 0) { |
2297 | char *s = fakeCString(vObjectUStringZValue(curVO)); |
2298 | if (!s || strcmp(_VCAL_VERSION, s) != 0) { |
2299 | kDebug() << "This vCalendar file has version" << s |
2300 | << "We only support" << _VCAL_VERSION; |
2301 | } |
2302 | deleteStr(s); |
2303 | } |
2304 | |
2305 | // set the time zone (this is a property of the view, so just discard!) |
2306 | if ((curVO = isAPropertyOf(vcal, VCTimeZoneProp)) != 0) { |
2307 | char *s = fakeCString(vObjectUStringZValue(curVO)); |
2308 | QString ts(s); |
2309 | QString name = QLatin1String("VCAL" ) + ts; |
2310 | deleteStr(s); |
2311 | |
2312 | // TODO: While using the timezone-offset + vcal as timezone is is |
2313 | // most likely unique, we should REALLY actually create something |
2314 | // like vcal-tzoffset-daylightoffsets, or better yet, |
2315 | // vcal-hash<the former> |
2316 | |
2317 | QStringList tzList; |
2318 | QString tz; |
2319 | int utcOffset; |
2320 | int utcOffsetDst; |
2321 | if (parseTZOffsetISO8601(ts, utcOffset)) { |
2322 | // kDebug() << "got standard offset" << ts << utcOffset; |
2323 | // standard from tz |
2324 | // starting date for now 01011900 |
2325 | KDateTime dt = KDateTime(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0))); |
2326 | tz = QString("STD;%1;false;%2" ).arg(QString::number(utcOffset)).arg(dt.toString()); |
2327 | tzList.append(tz); |
2328 | |
2329 | // go through all the daylight tags |
2330 | initPropIterator(&i, vcal); |
2331 | while (moreIteration(&i)) { |
2332 | curVO = nextVObject(&i); |
2333 | if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) { |
2334 | char *s = fakeCString(vObjectUStringZValue(curVO)); |
2335 | QString dst = QLatin1String(s); |
2336 | QStringList argl = dst.split(QLatin1Char(',')); |
2337 | deleteStr(s); |
2338 | |
2339 | // Too short -> not interesting |
2340 | if (argl.size() < 4) { |
2341 | continue; |
2342 | } |
2343 | |
2344 | // We don't care about the non-DST periods |
2345 | if (argl[0] != QLatin1String("TRUE" )) { |
2346 | continue; |
2347 | } |
2348 | |
2349 | if (parseTZOffsetISO8601(argl[1], utcOffsetDst)) { |
2350 | |
2351 | // kDebug() << "got DST offset" << argl[1] << utcOffsetDst; |
2352 | // standard |
2353 | QString strEndDate = argl[3]; |
2354 | KDateTime endDate = ISOToKDateTime(strEndDate); |
2355 | // daylight |
2356 | QString strStartDate = argl[2]; |
2357 | KDateTime startDate = ISOToKDateTime(strStartDate); |
2358 | |
2359 | QString strRealEndDate = strEndDate; |
2360 | QString strRealStartDate = strStartDate; |
2361 | KDateTime realEndDate = endDate; |
2362 | KDateTime realStartDate = startDate; |
2363 | // if we get dates for some reason in wrong order, earlier is used for dst |
2364 | if (endDate < startDate) { |
2365 | strRealEndDate = strStartDate; |
2366 | strRealStartDate = strEndDate; |
2367 | realEndDate = startDate; |
2368 | realStartDate = endDate; |
2369 | } |
2370 | tz = QString::fromLatin1("%1;%2;false;%3" ). |
2371 | arg(strRealEndDate). |
2372 | arg(QString::number(utcOffset)). |
2373 | arg(realEndDate.toString()); |
2374 | tzList.append(tz); |
2375 | |
2376 | tz = QString::fromLatin1("%1;%2;true;%3" ). |
2377 | arg(strRealStartDate). |
2378 | arg(QString::number(utcOffsetDst)). |
2379 | arg(realStartDate.toString()); |
2380 | tzList.append(tz); |
2381 | } else { |
2382 | kDebug() << "unable to parse dst" << argl[1]; |
2383 | } |
2384 | } |
2385 | } |
2386 | ICalTimeZones *tzlist = d->mCalendar->timeZones(); |
2387 | ICalTimeZoneSource tzs; |
2388 | ICalTimeZone zone = tzs.parse(name, tzList, *tzlist); |
2389 | if (!zone.isValid()) { |
2390 | kDebug() << "zone is not valid, parsing error" << tzList; |
2391 | } else { |
2392 | previousSpec = d->mCalendar->timeSpec(); |
2393 | d->mCalendar->setTimeZoneId(name); |
2394 | hasTimeZone = true; |
2395 | } |
2396 | } else { |
2397 | kDebug() << "unable to parse tzoffset" << ts; |
2398 | } |
2399 | } |
2400 | |
2401 | // Store all events with a relatedTo property in a list for post-processing |
2402 | d->mEventsRelate.clear(); |
2403 | d->mTodosRelate.clear(); |
2404 | |
2405 | initPropIterator(&i, vcal); |
2406 | |
2407 | // go through all the vobjects in the vcal |
2408 | while (moreIteration(&i)) { |
2409 | curVO = nextVObject(&i); |
2410 | |
2411 | /************************************************************************/ |
2412 | |
2413 | // now, check to see that the object is an event or todo. |
2414 | if (strcmp(vObjectName(curVO), VCEventProp) == 0) { |
2415 | |
2416 | if ((curVOProp = isAPropertyOf(curVO, KPilotStatusProp)) != 0) { |
2417 | char *s; |
2418 | s = fakeCString(vObjectUStringZValue(curVOProp)); |
2419 | // check to see if event was deleted by the kpilot conduit |
2420 | if (s) { |
2421 | if (atoi(s) == SYNCDEL) { |
2422 | deleteStr(s); |
2423 | kDebug() << "skipping pilot-deleted event" ; |
2424 | goto SKIP; |
2425 | } |
2426 | deleteStr(s); |
2427 | } |
2428 | } |
2429 | |
2430 | if (!isAPropertyOf(curVO, VCDTstartProp) && |
2431 | !isAPropertyOf(curVO, VCDTendProp)) { |
2432 | kDebug() << "found a VEvent with no DTSTART and no DTEND! Skipping..." ; |
2433 | goto SKIP; |
2434 | } |
2435 | |
2436 | anEvent = VEventToEvent(curVO); |
2437 | if (anEvent) { |
2438 | if (hasTimeZone && !anEvent->allDay() && anEvent->dtStart().isUtc()) { |
2439 | //This sounds stupid but is how others are doing it, so here |
2440 | //we go. If there is a TZ in the VCALENDAR even if the dtStart |
2441 | //and dtend are in UTC, clients interpret it using also the TZ defined |
2442 | //in the Calendar. I know it sounds braindead but oh well |
2443 | int utcOffSet = anEvent->dtStart().utcOffset(); |
2444 | KDateTime dtStart(anEvent->dtStart().dateTime().addSecs(utcOffSet), |
2445 | d->mCalendar->timeSpec()); |
2446 | KDateTime dtEnd(anEvent->dtEnd().dateTime().addSecs(utcOffSet), |
2447 | d->mCalendar->timeSpec()); |
2448 | anEvent->setDtStart(dtStart); |
2449 | anEvent->setDtEnd(dtEnd); |
2450 | } |
2451 | Event::Ptr old = !anEvent->hasRecurrenceId() ? |
2452 | d->mCalendar->event(anEvent->uid()) : |
2453 | d->mCalendar->event(anEvent->uid(), anEvent->recurrenceId()); |
2454 | |
2455 | if (old) { |
2456 | if (deleted) { |
2457 | d->mCalendar->deleteEvent(old); // move old to deleted |
2458 | removeAllVCal(d->mEventsRelate, old); |
2459 | } else if (anEvent->revision() > old->revision()) { |
2460 | d->mCalendar->deleteEvent(old); // move old to deleted |
2461 | removeAllVCal(d->mEventsRelate, old); |
2462 | d->mCalendar->addEvent(anEvent); // and replace it with this one |
2463 | } |
2464 | } else if (deleted) { |
2465 | old = !anEvent->hasRecurrenceId() ? |
2466 | d->mCalendar->deletedEvent(anEvent->uid()) : |
2467 | d->mCalendar->deletedEvent(anEvent->uid(), anEvent->recurrenceId()); |
2468 | if (!old) { |
2469 | d->mCalendar->addEvent(anEvent); // add this one |
2470 | d->mCalendar->deleteEvent(anEvent); // and move it to deleted |
2471 | } |
2472 | } else { |
2473 | d->mCalendar->addEvent(anEvent); // just add this one |
2474 | } |
2475 | } |
2476 | } else if (strcmp(vObjectName(curVO), VCTodoProp) == 0) { |
2477 | Todo::Ptr aTodo = VTodoToEvent(curVO); |
2478 | if (aTodo) { |
2479 | if (hasTimeZone && !aTodo->allDay() && aTodo->dtStart().isUtc()) { |
2480 | //This sounds stupid but is how others are doing it, so here |
2481 | //we go. If there is a TZ in the VCALENDAR even if the dtStart |
2482 | //and dtend are in UTC, clients interpret it usint alse the TZ defined |
2483 | //in the Calendar. I know it sounds braindead but oh well |
2484 | int utcOffSet = aTodo->dtStart().utcOffset(); |
2485 | KDateTime dtStart(aTodo->dtStart().dateTime().addSecs(utcOffSet), |
2486 | d->mCalendar->timeSpec()); |
2487 | aTodo->setDtStart(dtStart); |
2488 | if (aTodo->hasDueDate()) { |
2489 | KDateTime dtDue(aTodo->dtDue().dateTime().addSecs(utcOffSet), |
2490 | d->mCalendar->timeSpec()); |
2491 | aTodo->setDtDue(dtDue); |
2492 | } |
2493 | } |
2494 | Todo::Ptr old = !aTodo->hasRecurrenceId() ? |
2495 | d->mCalendar->todo(aTodo->uid()) : |
2496 | d->mCalendar->todo(aTodo->uid(), aTodo->recurrenceId()); |
2497 | if (old) { |
2498 | if (deleted) { |
2499 | d->mCalendar->deleteTodo(old); // move old to deleted |
2500 | removeAllVCal(d->mTodosRelate, old); |
2501 | } else if (aTodo->revision() > old->revision()) { |
2502 | d->mCalendar->deleteTodo(old); // move old to deleted |
2503 | removeAllVCal(d->mTodosRelate, old); |
2504 | d->mCalendar->addTodo(aTodo); // and replace it with this one |
2505 | } |
2506 | } else if (deleted) { |
2507 | old = d->mCalendar->deletedTodo(aTodo->uid(), aTodo->recurrenceId()); |
2508 | if (!old) { |
2509 | d->mCalendar->addTodo(aTodo); // add this one |
2510 | d->mCalendar->deleteTodo(aTodo); // and move it to deleted |
2511 | } |
2512 | } else { |
2513 | d->mCalendar->addTodo(aTodo); // just add this one |
2514 | } |
2515 | } |
2516 | } else if ((strcmp(vObjectName(curVO), VCVersionProp) == 0) || |
2517 | (strcmp(vObjectName(curVO), VCProdIdProp) == 0) || |
2518 | (strcmp(vObjectName(curVO), VCTimeZoneProp) == 0)) { |
2519 | // do nothing, we know these properties and we want to skip them. |
2520 | // we have either already processed them or are ignoring them. |
2521 | ; |
2522 | } else if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) { |
2523 | // do nothing daylights are already processed |
2524 | ; |
2525 | } else { |
2526 | kDebug() << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"" ; |
2527 | } |
2528 | SKIP: |
2529 | ; |
2530 | } // while |
2531 | |
2532 | // Post-Process list of events with relations, put Event objects in relation |
2533 | Event::List::ConstIterator eIt; |
2534 | for (eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt) { |
2535 | (*eIt)->setRelatedTo((*eIt)->relatedTo()); |
2536 | } |
2537 | Todo::List::ConstIterator tIt; |
2538 | for (tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt) { |
2539 | (*tIt)->setRelatedTo((*tIt)->relatedTo()); |
2540 | } |
2541 | |
2542 | //Now lets put the TZ back as it was if we have changed it. |
2543 | if (hasTimeZone) { |
2544 | d->mCalendar->setTimeSpec(previousSpec); |
2545 | } |
2546 | |
2547 | } |
2548 | |
2549 | const char *VCalFormat::dayFromNum(int day) |
2550 | { |
2551 | const char *days[7] = { "MO " , "TU " , "WE " , "TH " , "FR " , "SA " , "SU " }; |
2552 | |
2553 | return days[day]; |
2554 | } |
2555 | |
2556 | int VCalFormat::numFromDay(const QString &day) |
2557 | { |
2558 | if (day == QLatin1String("MO " )) { |
2559 | return 0; |
2560 | } |
2561 | if (day == QLatin1String("TU " )) { |
2562 | return 1; |
2563 | } |
2564 | if (day == QLatin1String("WE " )) { |
2565 | return 2; |
2566 | } |
2567 | if (day == QLatin1String("TH " )) { |
2568 | return 3; |
2569 | } |
2570 | if (day == QLatin1String("FR " )) { |
2571 | return 4; |
2572 | } |
2573 | if (day == QLatin1String("SA " )) { |
2574 | return 5; |
2575 | } |
2576 | if (day == QLatin1String("SU " )) { |
2577 | return 6; |
2578 | } |
2579 | |
2580 | return -1; // something bad happened. :) |
2581 | } |
2582 | |
2583 | Attendee::PartStat VCalFormat::readStatus(const char *s) const |
2584 | { |
2585 | QString statStr = s; |
2586 | statStr = statStr.toUpper(); |
2587 | Attendee::PartStat status; |
2588 | |
2589 | if (statStr == QLatin1String("X-ACTION" )) { |
2590 | status = Attendee::NeedsAction; |
2591 | } else if (statStr == QLatin1String("NEEDS ACTION" )) { |
2592 | status = Attendee::NeedsAction; |
2593 | } else if (statStr == QLatin1String("ACCEPTED" )) { |
2594 | status = Attendee::Accepted; |
2595 | } else if (statStr == QLatin1String("SENT" )) { |
2596 | status = Attendee::NeedsAction; |
2597 | } else if (statStr == QLatin1String("TENTATIVE" )) { |
2598 | status = Attendee::Tentative; |
2599 | } else if (statStr == QLatin1String("CONFIRMED" )) { |
2600 | status = Attendee::Accepted; |
2601 | } else if (statStr == QLatin1String("DECLINED" )) { |
2602 | status = Attendee::Declined; |
2603 | } else if (statStr == QLatin1String("COMPLETED" )) { |
2604 | status = Attendee::Completed; |
2605 | } else if (statStr == QLatin1String("DELEGATED" )) { |
2606 | status = Attendee::Delegated; |
2607 | } else { |
2608 | kDebug() << "error setting attendee mStatus, unknown mStatus!" ; |
2609 | status = Attendee::NeedsAction; |
2610 | } |
2611 | |
2612 | return status; |
2613 | } |
2614 | |
2615 | QByteArray VCalFormat::writeStatus(Attendee::PartStat status) const |
2616 | { |
2617 | switch (status) { |
2618 | default: |
2619 | case Attendee::NeedsAction: |
2620 | return "NEEDS ACTION" ; |
2621 | break; |
2622 | case Attendee::Accepted: |
2623 | return "ACCEPTED" ; |
2624 | break; |
2625 | case Attendee::Declined: |
2626 | return "DECLINED" ; |
2627 | break; |
2628 | case Attendee::Tentative: |
2629 | return "TENTATIVE" ; |
2630 | break; |
2631 | case Attendee::Delegated: |
2632 | return "DELEGATED" ; |
2633 | break; |
2634 | case Attendee::Completed: |
2635 | return "COMPLETED" ; |
2636 | break; |
2637 | case Attendee::InProcess: |
2638 | return "NEEDS ACTION" ; |
2639 | break; |
2640 | } |
2641 | } |
2642 | |
2643 | void VCalFormat::readCustomProperties(VObject *o, const Incidence::Ptr &i) |
2644 | { |
2645 | VObjectIterator iter; |
2646 | VObject *cur; |
2647 | const char *curname; |
2648 | char *s; |
2649 | |
2650 | initPropIterator(&iter, o); |
2651 | while (moreIteration(&iter)) { |
2652 | cur = nextVObject(&iter); |
2653 | curname = vObjectName(cur); |
2654 | Q_ASSERT(curname); |
2655 | if ((curname[0] == 'X' && curname[1] == '-') && |
2656 | strcmp(curname, ICOrganizerProp) != 0) { |
2657 | // TODO - for the time being, we ignore the parameters part |
2658 | // and just do the value handling here |
2659 | i->setNonKDECustomProperty( |
2660 | curname, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(cur)))); |
2661 | deleteStr(s); |
2662 | } |
2663 | } |
2664 | } |
2665 | |
2666 | void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i) |
2667 | { |
2668 | const QMap<QByteArray, QString> custom = i->customProperties(); |
2669 | for (QMap<QByteArray, QString>::ConstIterator c = custom.begin(); |
2670 | c != custom.end(); ++c) { |
2671 | if (d->mManuallyWrittenExtensionFields.contains(c.key()) || |
2672 | c.key().startsWith("X-KDE-VOLATILE" )) { //krazy:exclude=strings |
2673 | continue; |
2674 | } |
2675 | |
2676 | addPropValue(o, c.key(), c.value().toUtf8()); |
2677 | } |
2678 | } |
2679 | |
2680 | void VCalFormat::virtual_hook(int id, void *data) |
2681 | { |
2682 | Q_UNUSED(id); |
2683 | Q_UNUSED(data); |
2684 | Q_ASSERT(false); |
2685 | } |
2686 | |