1/*
2 This file is part of the kcal 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
38#include "vcalformat.h"
39#include "calendar.h"
40#include "versit/vcc.h"
41#include "versit/vobject.h"
42
43#include <kdebug.h>
44#include <kdatetime.h>
45#include <klocalizedstring.h>
46
47#include <QtCore/QString>
48#include <QtCore/QRegExp>
49#include <QtCore/QFile>
50#include <QtCore/QByteArray>
51#include <QTextDocument>
52
53using namespace KCal;
54
55/**
56 Private class that helps to provide binary compatibility between releases.
57 @internal
58*/
59//@cond PRIVATE
60class KCal::VCalFormat::Private
61{
62 public:
63 Calendar *mCalendar;
64 Event::List mEventsRelate; // Events with relations
65 Todo::List mTodosRelate; // To-dos with relations
66};
67//@endcond
68
69VCalFormat::VCalFormat() : d( new KCal::VCalFormat::Private )
70{
71}
72
73VCalFormat::~VCalFormat()
74{
75 delete d;
76}
77
78bool VCalFormat::load( Calendar *calendar, const QString &fileName )
79{
80 d->mCalendar = calendar;
81
82 clearException();
83
84 kDebug() << fileName;
85
86 VObject *vcal = 0;
87
88 // this is not necessarily only 1 vcal. Could be many vcals, or include
89 // a vcard...
90 vcal = Parse_MIME_FromFileName( const_cast<char *>( QFile::encodeName( fileName ).data() ) );
91
92 if ( !vcal ) {
93 setException( new ErrorFormat( ErrorFormat::CalVersionUnknown ) );
94 return false;
95 }
96
97 // any other top-level calendar stuff should be added/initialized here
98
99 // put all vobjects into their proper places
100 populate( vcal );
101
102 // clean up from vcal API stuff
103 cleanVObjects( vcal );
104 cleanStrTbl();
105
106 return true;
107}
108
109bool VCalFormat::save( Calendar *calendar, const QString &fileName )
110{
111 d->mCalendar = calendar;
112
113 QString tmpStr;
114 VObject *vcal, *vo;
115
116 kDebug() << fileName;
117
118 vcal = newVObject( VCCalProp );
119
120 // addPropValue(vcal,VCLocationProp, "0.0");
121 addPropValue( vcal, VCProdIdProp, productId().toLatin1() );
122 addPropValue( vcal, VCVersionProp, _VCAL_VERSION );
123
124 // TODO STUFF
125 Todo::List todoList = d->mCalendar->rawTodos();
126 Todo::List::ConstIterator it;
127 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
128 vo = eventToVTodo( *it );
129 addVObjectProp( vcal, vo );
130 }
131
132 // EVENT STUFF
133 Event::List events = d->mCalendar->rawEvents();
134 Event::List::ConstIterator it2;
135 for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
136 vo = eventToVEvent( *it2 );
137 addVObjectProp( vcal, vo );
138 }
139
140 writeVObjectToFile( QFile::encodeName( fileName ).data(), vcal );
141 cleanVObjects( vcal );
142 cleanStrTbl();
143
144 if ( QFile::exists( fileName ) ) {
145 return true;
146 } else {
147 return false; // error
148 }
149
150 return false;
151}
152
153bool VCalFormat::fromString( Calendar *calendar, const QString &string )
154{
155 return fromRawString( calendar, string.toUtf8() );
156}
157
158bool VCalFormat::fromRawString( Calendar *calendar, const QByteArray &string )
159{
160 d->mCalendar = calendar;
161
162 if ( !string.size() ) {
163 return false;
164 }
165
166 VObject *vcal = Parse_MIME( string.data(), string.size() );
167 if ( !vcal ) {
168 return false;
169 }
170
171 VObjectIterator i;
172 VObject *curvo;
173 initPropIterator( &i, vcal );
174
175 // we only take the first object. TODO: parse all incidences.
176 do {
177 curvo = nextVObject( &i );
178 } while ( strcmp( vObjectName( curvo ), VCEventProp ) &&
179 strcmp( vObjectName( curvo ), VCTodoProp ) );
180
181 if ( strcmp( vObjectName( curvo ), VCEventProp ) == 0 ) {
182 Event *event = VEventToEvent( curvo );
183 calendar->addEvent( event );
184 } else {
185 kDebug() << "Unknown object type.";
186 deleteVObject( vcal );
187 return false;
188 }
189
190 deleteVObject( vcal );
191
192 return true;
193}
194
195QString VCalFormat::toString( Calendar *calendar )
196{
197 // TODO: Factor out VCalFormat::asString()
198 d->mCalendar = calendar;
199
200 VObject *vcal = newVObject( VCCalProp );
201
202 addPropValue( vcal, VCProdIdProp, CalFormat::productId().toLatin1() );
203 addPropValue( vcal, VCVersionProp, _VCAL_VERSION );
204
205 // TODO: Use all data.
206 Event::List events = calendar->events();
207 if( events.isEmpty() ) {
208 cleanVObject ( vcal );
209 return QString();
210 }
211 Event *event = events.first();
212 if ( !event ) {
213 cleanVObject ( vcal );
214 return QString();
215 }
216 VObject *vevent = eventToVEvent( event );
217
218 addVObjectProp( vcal, vevent );
219
220 char *buf = writeMemVObject( 0, 0, vcal );
221
222 QString result( buf );
223
224 cleanVObject( vcal );
225
226 return result;
227}
228
229VObject *VCalFormat::eventToVTodo( const Todo *anEvent )
230{
231 VObject *vtodo;
232 QString tmpStr;
233
234 vtodo = newVObject( VCTodoProp );
235
236 // due date
237 if ( anEvent->hasDueDate() ) {
238 tmpStr = kDateTimeToISO( anEvent->dtDue(), !anEvent->allDay() );
239 addPropValue( vtodo, VCDueProp, tmpStr.toLocal8Bit() );
240 }
241
242 // start date
243 if ( anEvent->hasStartDate() ) {
244 tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
245 addPropValue( vtodo, VCDTstartProp, tmpStr.toLocal8Bit() );
246 }
247
248 // creation date
249 tmpStr = kDateTimeToISO( anEvent->created() );
250 addPropValue( vtodo, VCDCreatedProp, tmpStr.toLocal8Bit() );
251
252 // unique id
253 addPropValue( vtodo, VCUniqueStringProp,
254 anEvent->uid().toLocal8Bit() );
255
256 // revision
257 tmpStr.sprintf( "%i", anEvent->revision() );
258 addPropValue( vtodo, VCSequenceProp, tmpStr.toLocal8Bit() );
259
260 // last modification date
261 tmpStr = kDateTimeToISO( anEvent->lastModified() );
262 addPropValue( vtodo, VCLastModifiedProp, tmpStr.toLocal8Bit() );
263
264 // organizer stuff
265 // @TODO: How about the common name?
266 if ( !anEvent->organizer().email().isEmpty() ) {
267 tmpStr = "MAILTO:" + anEvent->organizer().email();
268 addPropValue( vtodo, ICOrganizerProp, tmpStr.toLocal8Bit() );
269 }
270
271 // attendees
272 if ( anEvent->attendeeCount() > 0 ) {
273 Attendee::List::ConstIterator it;
274 Attendee *curAttendee;
275 for ( it = anEvent->attendees().begin(); it != anEvent->attendees().end();
276 ++it ) {
277 curAttendee = *it;
278 if ( !curAttendee->email().isEmpty() &&
279 !curAttendee->name().isEmpty() ) {
280 tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
281 } else if ( curAttendee->name().isEmpty() ) {
282 tmpStr = "MAILTO: " + curAttendee->email();
283 } else if ( curAttendee->email().isEmpty() ) {
284 tmpStr = "MAILTO: " + curAttendee->name();
285 } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
286 kDebug() << "warning! this Event has an attendee w/o name or email!";
287 }
288 VObject *aProp = addPropValue( vtodo, VCAttendeeProp, tmpStr.toLocal8Bit() );
289 addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
290 addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
291 }
292 }
293
294 // description BL:
295 if ( !anEvent->description().isEmpty() ) {
296 VObject *dObject = addPropValue( vtodo, VCDescriptionProp,
297 anEvent->description().toLocal8Bit() );
298 if ( anEvent->description().indexOf( '\n' ) != -1 ) {
299 addPropValue( dObject, VCEncodingProp, VCQuotedPrintableProp );
300 }
301 }
302
303 // summary
304 if ( !anEvent->summary().isEmpty() ) {
305 addPropValue( vtodo, VCSummaryProp, anEvent->summary().toLocal8Bit() );
306 }
307
308 // location
309 if ( !anEvent->location().isEmpty() ) {
310 addPropValue( vtodo, VCLocationProp, anEvent->location().toLocal8Bit() );
311 }
312
313 // completed status
314 // backward compatibility, KOrganizer used to interpret only these two values
315 addPropValue( vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS_ACTION" );
316
317 // completion date
318 if ( anEvent->hasCompletedDate() ) {
319 tmpStr = kDateTimeToISO( anEvent->completed() );
320 addPropValue( vtodo, VCCompletedProp, tmpStr.toLocal8Bit() );
321 }
322
323 // priority
324 tmpStr.sprintf( "%i", anEvent->priority() );
325 addPropValue( vtodo, VCPriorityProp, tmpStr.toLocal8Bit() );
326
327 // related event
328 if ( anEvent->relatedTo() ) {
329 addPropValue( vtodo, VCRelatedToProp,
330 anEvent->relatedTo()->uid().toLocal8Bit() );
331 }
332
333 // categories
334 const QStringList tmpStrList = anEvent->categories();
335 tmpStr = "";
336 QString catStr;
337 QStringList::const_iterator its;
338 for ( its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its ) {
339 catStr = *its;
340 if ( catStr[0] == ' ' ) {
341 tmpStr += catStr.mid( 1 );
342 } else {
343 tmpStr += catStr;
344 }
345 // this must be a ';' character as the vCalendar specification requires!
346 // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
347 // read in.
348 tmpStr += ';';
349 }
350 if ( !tmpStr.isEmpty() ) {
351 tmpStr.truncate( tmpStr.length() - 1 );
352 addPropValue( vtodo, VCCategoriesProp, tmpStr.toLocal8Bit() );
353 }
354
355 // alarm stuff
356 Alarm::List::ConstIterator it;
357 for ( it = anEvent->alarms().begin(); it != anEvent->alarms().end(); ++it ) {
358 Alarm *alarm = *it;
359 if ( alarm->enabled() ) {
360 VObject *a = addProp( vtodo, VCDAlarmProp );
361 tmpStr = kDateTimeToISO( alarm->time() );
362 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
363 addPropValue( a, VCRepeatCountProp, "1" );
364 addPropValue( a, VCDisplayStringProp, "beep!" );
365 if ( alarm->type() == Alarm::Audio ) {
366 a = addProp( vtodo, VCAAlarmProp );
367 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
368 addPropValue( a, VCRepeatCountProp, "1" );
369 addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
370 } else if ( alarm->type() == Alarm::Procedure ) {
371 a = addProp( vtodo, VCPAlarmProp );
372 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
373 addPropValue( a, VCRepeatCountProp, "1" );
374 addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
375 }
376 }
377 }
378
379 QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
380 if ( !pilotId.isEmpty() ) {
381 // pilot sync stuff
382 addPropValue( vtodo, KPilotIdProp, pilotId.toLocal8Bit() );
383 addPropValue( vtodo, KPilotStatusProp,
384 anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
385 }
386
387 return vtodo;
388}
389
390VObject *VCalFormat::eventToVEvent( const Event *anEvent )
391{
392 VObject *vevent;
393 QString tmpStr;
394
395 vevent = newVObject( VCEventProp );
396
397 // start and end time
398 tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
399 addPropValue( vevent, VCDTstartProp, tmpStr.toLocal8Bit() );
400
401 // events that have time associated but take up no time should
402 // not have both DTSTART and DTEND.
403 if ( anEvent->dtStart() != anEvent->dtEnd() ) {
404 tmpStr = kDateTimeToISO( anEvent->dtEnd(), !anEvent->allDay() );
405 addPropValue( vevent, VCDTendProp, tmpStr.toLocal8Bit() );
406 }
407
408 // creation date
409 tmpStr = kDateTimeToISO( anEvent->created() );
410 addPropValue( vevent, VCDCreatedProp, tmpStr.toLocal8Bit() );
411
412 // unique id
413 addPropValue( vevent, VCUniqueStringProp,
414 anEvent->uid().toLocal8Bit() );
415
416 // revision
417 tmpStr.sprintf( "%i", anEvent->revision() );
418 addPropValue( vevent, VCSequenceProp, tmpStr.toLocal8Bit() );
419
420 // last modification date
421 tmpStr = kDateTimeToISO( anEvent->lastModified() );
422 addPropValue( vevent, VCLastModifiedProp, tmpStr.toLocal8Bit() );
423
424 // attendee and organizer stuff
425 // TODO: What to do with the common name?
426 if ( !anEvent->organizer().email().isEmpty() ) {
427 tmpStr = "MAILTO:" + anEvent->organizer().email();
428 addPropValue( vevent, ICOrganizerProp, tmpStr.toLocal8Bit() );
429 }
430
431 // TODO: Put this functionality into Attendee class
432 if ( anEvent->attendeeCount() > 0 ) {
433 Attendee::List::ConstIterator it;
434 for ( it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
435 ++it ) {
436 Attendee *curAttendee = *it;
437 if ( !curAttendee->email().isEmpty() && !curAttendee->name().isEmpty() ) {
438 tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
439 } else if ( curAttendee->name().isEmpty() ) {
440 tmpStr = "MAILTO: " + curAttendee->email();
441 } else if ( curAttendee->email().isEmpty() ) {
442 tmpStr = "MAILTO: " + curAttendee->name();
443 } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
444 kDebug() << "warning! this Event has an attendee w/o name or email!";
445 }
446 VObject *aProp = addPropValue( vevent, VCAttendeeProp, tmpStr.toLocal8Bit() );
447 addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
448 addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
449 }
450 }
451
452 // recurrence rule stuff
453 const Recurrence *recur = anEvent->recurrence();
454 if ( recur->recurs() ) {
455 bool validRecur = true;
456 QString tmpStr2;
457 switch ( recur->recurrenceType() ) {
458 case Recurrence::rDaily:
459 tmpStr.sprintf( "D%i ", recur->frequency() );
460 break;
461 case Recurrence::rWeekly:
462 tmpStr.sprintf( "W%i ", recur->frequency() );
463 for ( int i = 0; i < 7; ++i ) {
464 QBitArray days ( recur->days() );
465 if ( days.testBit(i) ) {
466 tmpStr += dayFromNum( i );
467 }
468 }
469 break;
470 case Recurrence::rMonthlyPos:
471 {
472 tmpStr.sprintf( "MP%i ", recur->frequency() );
473 // write out all rMonthPos's
474 QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions();
475 for ( QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin();
476 posit != tmpPositions.constEnd(); ++posit ) {
477 int pos = (*posit).pos();
478 tmpStr2.sprintf( "%i", ( pos > 0 ) ? pos : (-pos) );
479 if ( pos < 0 ) {
480 tmpStr2 += "- ";
481 } else {
482 tmpStr2 += "+ ";
483 }
484 tmpStr += tmpStr2;
485 tmpStr += dayFromNum( (*posit).day() - 1 );
486 }
487 break;
488 }
489 case Recurrence::rMonthlyDay:
490 {
491 tmpStr.sprintf( "MD%i ", recur->frequency() );
492 // write out all rMonthDays;
493 const QList<int> tmpDays = recur->monthDays();
494 for ( QList<int>::ConstIterator tmpDay = tmpDays.constBegin();
495 tmpDay != tmpDays.constEnd(); ++tmpDay ) {
496 tmpStr2.sprintf( "%i ", *tmpDay );
497 tmpStr += tmpStr2;
498 }
499 break;
500 }
501 case Recurrence::rYearlyMonth:
502 {
503 tmpStr.sprintf( "YM%i ", recur->frequency() );
504 // write out all the months;'
505 // TODO: Any way to write out the day within the month???
506 const QList<int> months = recur->yearMonths();
507 for ( QList<int>::ConstIterator mit = months.constBegin();
508 mit != months.constEnd(); ++mit ) {
509 tmpStr2.sprintf( "%i ", *mit );
510 tmpStr += tmpStr2;
511 }
512 break;
513 }
514 case Recurrence::rYearlyDay:
515 {
516 tmpStr.sprintf( "YD%i ", recur->frequency() );
517 // write out all the rYearNums;
518 const QList<int> tmpDays = recur->yearDays();
519 for ( QList<int>::ConstIterator tmpDay = tmpDays.begin();
520 tmpDay != tmpDays.end(); ++tmpDay ) {
521 tmpStr2.sprintf( "%i ", *tmpDay );
522 tmpStr += tmpStr2;
523 }
524 break;
525 }
526 default:
527 // TODO: Write rYearlyPos and arbitrary rules!
528 kDebug() << "ERROR, it should never get here in eventToVEvent!";
529 validRecur = false;
530 break;
531 } // switch
532
533 if ( recur->duration() > 0 ) {
534 tmpStr2.sprintf( "#%i", recur->duration() );
535 tmpStr += tmpStr2;
536 } else if ( recur->duration() == -1 ) {
537 tmpStr += "#0"; // defined as repeat forever
538 } else {
539 tmpStr += kDateTimeToISO( recur->endDateTime(), false );
540 }
541 // Only write out the rrule if we have a valid recurrence (i.e. a known
542 // type in thee switch above)
543 if ( validRecur ) {
544 addPropValue( vevent, VCRRuleProp, tmpStr.toLocal8Bit() );
545 }
546
547 } // event repeats
548
549 // exceptions to recurrence
550 DateList dateList = recur->exDates();
551 DateList::ConstIterator it;
552 QString tmpStr2;
553
554 for ( it = dateList.constBegin(); it != dateList.constEnd(); ++it ) {
555 tmpStr = qDateToISO(*it) + ';';
556 tmpStr2 += tmpStr;
557 }
558 if ( !tmpStr2.isEmpty() ) {
559 tmpStr2.truncate( tmpStr2.length() - 1 );
560 addPropValue( vevent, VCExpDateProp, tmpStr2.toLocal8Bit() );
561 }
562
563 // description
564 if ( !anEvent->description().isEmpty() ) {
565 VObject *dObject = addPropValue( vevent, VCDescriptionProp,
566 anEvent->description().toLocal8Bit() );
567 if ( anEvent->description().indexOf( '\n' ) != -1 ) {
568 addPropValue( dObject, VCEncodingProp, VCQuotedPrintableProp );
569 }
570 }
571
572 // summary
573 if ( !anEvent->summary().isEmpty() ) {
574 addPropValue( vevent, VCSummaryProp, anEvent->summary().toLocal8Bit() );
575 }
576
577 // location
578 if ( !anEvent->location().isEmpty() ) {
579 addPropValue( vevent, VCLocationProp, anEvent->location().toLocal8Bit() );
580 }
581
582 // status
583// TODO: define Event status
584// addPropValue( vevent, VCStatusProp, anEvent->statusStr().toLocal8Bit() );
585
586 // secrecy
587 const char *text = 0;
588 switch ( anEvent->secrecy() ) {
589 case Incidence::SecrecyPublic:
590 text = "PUBLIC";
591 break;
592 case Incidence::SecrecyPrivate:
593 text = "PRIVATE";
594 break;
595 case Incidence::SecrecyConfidential:
596 text = "CONFIDENTIAL";
597 break;
598 }
599 if ( text ) {
600 addPropValue( vevent, VCClassProp, text );
601 }
602
603 // categories
604 QStringList tmpStrList = anEvent->categories();
605 tmpStr = "";
606 QString catStr;
607 for ( QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd();
608 ++it ) {
609 catStr = *it;
610 if ( catStr[0] == ' ' ) {
611 tmpStr += catStr.mid( 1 );
612 } else {
613 tmpStr += catStr;
614 }
615 // this must be a ';' character as the vCalendar specification requires!
616 // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
617 // read in.
618 tmpStr += ';';
619 }
620 if ( !tmpStr.isEmpty() ) {
621 tmpStr.truncate( tmpStr.length() - 1 );
622 addPropValue( vevent, VCCategoriesProp, tmpStr.toLocal8Bit() );
623 }
624
625 // attachments
626 // TODO: handle binary attachments!
627 Attachment::List attachments = anEvent->attachments();
628 Attachment::List::ConstIterator atIt;
629 for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) {
630 addPropValue( vevent, VCAttachProp, (*atIt)->uri().toLocal8Bit() );
631 }
632
633 // resources
634 tmpStrList = anEvent->resources();
635 tmpStr = tmpStrList.join( ";" );
636 if ( !tmpStr.isEmpty() ) {
637 addPropValue( vevent, VCResourcesProp, tmpStr.toLocal8Bit() );
638 }
639
640 // alarm stuff
641 Alarm::List::ConstIterator it2;
642 for ( it2 = anEvent->alarms().constBegin(); it2 != anEvent->alarms().constEnd(); ++it2 ) {
643 Alarm *alarm = *it2;
644 if ( alarm->enabled() ) {
645 VObject *a = addProp( vevent, VCDAlarmProp );
646 tmpStr = kDateTimeToISO( alarm->time() );
647 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
648 addPropValue( a, VCRepeatCountProp, "1" );
649 addPropValue( a, VCDisplayStringProp, "beep!" );
650 if ( alarm->type() == Alarm::Audio ) {
651 a = addProp( vevent, VCAAlarmProp );
652 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
653 addPropValue( a, VCRepeatCountProp, "1" );
654 addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
655 }
656 if ( alarm->type() == Alarm::Procedure ) {
657 a = addProp( vevent, VCPAlarmProp );
658 addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
659 addPropValue( a, VCRepeatCountProp, "1" );
660 addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
661 }
662 }
663 }
664
665 // priority
666 tmpStr.sprintf( "%i", anEvent->priority() );
667 addPropValue( vevent, VCPriorityProp, tmpStr.toLocal8Bit() );
668
669 // transparency
670 tmpStr.sprintf( "%i", anEvent->transparency() );
671 addPropValue( vevent, VCTranspProp, tmpStr.toLocal8Bit() );
672
673 // related event
674 if ( anEvent->relatedTo() ) {
675 addPropValue( vevent, VCRelatedToProp, anEvent->relatedTo()->uid().toLocal8Bit() );
676 }
677
678 QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
679 if ( !pilotId.isEmpty() ) {
680 // pilot sync stuff
681 addPropValue( vevent, KPilotIdProp, pilotId.toLocal8Bit() );
682 addPropValue( vevent, KPilotStatusProp,
683 anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
684 }
685
686 return vevent;
687}
688
689Todo *VCalFormat::VTodoToEvent( VObject *vtodo )
690{
691 VObject *vo;
692 VObjectIterator voi;
693 char *s;
694
695 Todo *anEvent = new Todo;
696
697 // creation date
698 if ( ( vo = isAPropertyOf( vtodo, VCDCreatedProp ) ) != 0 ) {
699 anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
700 deleteStr( s );
701 }
702
703 // unique id
704 vo = isAPropertyOf( vtodo, VCUniqueStringProp );
705 // while the UID property is preferred, it is not required. We'll use the
706 // default Event UID if none is given.
707 if ( vo ) {
708 anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
709 deleteStr( s );
710 }
711
712 // last modification date
713 if ( ( vo = isAPropertyOf( vtodo, VCLastModifiedProp ) ) != 0 ) {
714 anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
715 deleteStr( s );
716 } else {
717 anEvent->setLastModified( KDateTime::currentUtcDateTime() );
718 }
719
720 // organizer
721 // if our extension property for the event's ORGANIZER exists, add it.
722 if ( ( vo = isAPropertyOf( vtodo, ICOrganizerProp ) ) != 0 ) {
723 anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
724 deleteStr( s );
725 } else {
726 anEvent->setOrganizer( d->mCalendar->owner() );
727 }
728
729 // attendees.
730 initPropIterator( &voi, vtodo );
731 while ( moreIteration( &voi ) ) {
732 vo = nextVObject( &voi );
733 if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
734 Attendee *a;
735 VObject *vp;
736 s = fakeCString( vObjectUStringZValue( vo ) );
737 QString tmpStr = QString::fromLocal8Bit( s );
738 deleteStr( s );
739 tmpStr = tmpStr.simplified();
740 int emailPos1, emailPos2;
741 if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
742 // both email address and name
743 emailPos2 = tmpStr.lastIndexOf( '>' );
744 a = new Attendee( tmpStr.left( emailPos1 - 1 ),
745 tmpStr.mid( emailPos1 + 1,
746 emailPos2 - ( emailPos1 + 1 ) ) );
747 } else if ( tmpStr.indexOf( '@' ) > 0 ) {
748 // just an email address
749 a = new Attendee( 0, tmpStr );
750 } else {
751 // just a name
752 // WTF??? Replacing the spaces of a name and using this as email?
753 QString email = tmpStr.replace( ' ', '.' );
754 a = new Attendee( tmpStr, email );
755 }
756
757 // is there an RSVP property?
758 if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
759 a->setRSVP( vObjectStringZValue( vp ) );
760 }
761 // is there a status property?
762 if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
763 a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
764 }
765 // add the attendee
766 anEvent->addAttendee( a );
767 }
768 }
769
770 // description for todo
771 if ( ( vo = isAPropertyOf( vtodo, VCDescriptionProp ) ) != 0 ) {
772 s = fakeCString( vObjectUStringZValue( vo ) );
773 anEvent->setDescription( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
774 deleteStr( s );
775 }
776
777 // summary
778 if ( ( vo = isAPropertyOf( vtodo, VCSummaryProp ) ) ) {
779 s = fakeCString( vObjectUStringZValue( vo ) );
780 anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
781 deleteStr( s );
782 }
783
784 // location
785 if ( ( vo = isAPropertyOf( vtodo, VCLocationProp ) ) != 0 ) {
786 s = fakeCString( vObjectUStringZValue( vo ) );
787 anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
788 deleteStr( s );
789 }
790
791 // completed
792 // was: status
793 if ( ( vo = isAPropertyOf( vtodo, VCStatusProp ) ) != 0 ) {
794 s = fakeCString( vObjectUStringZValue( vo ) );
795 if ( s && strcmp( s, "COMPLETED" ) == 0 ) {
796 anEvent->setCompleted( true );
797 } else {
798 anEvent->setCompleted( false );
799 }
800 deleteStr( s );
801 } else {
802 anEvent->setCompleted( false );
803 }
804
805 // completion date
806 if ( ( vo = isAPropertyOf( vtodo, VCCompletedProp ) ) != 0 ) {
807 anEvent->setCompleted( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
808 deleteStr( s );
809 }
810
811 // priority
812 if ( ( vo = isAPropertyOf( vtodo, VCPriorityProp ) ) ) {
813 s = fakeCString( vObjectUStringZValue( vo ) );
814 if ( s ) {
815 anEvent->setPriority( atoi( s ) );
816 deleteStr( s );
817 }
818 }
819
820 // due date
821 if ( ( vo = isAPropertyOf( vtodo, VCDueProp ) ) != 0 ) {
822 anEvent->setDtDue( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
823 deleteStr( s );
824 anEvent->setHasDueDate( true );
825 } else {
826 anEvent->setHasDueDate( false );
827 }
828
829 // start time
830 if ( ( vo = isAPropertyOf( vtodo, VCDTstartProp ) ) != 0 ) {
831 anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
832 deleteStr( s );
833 anEvent->setHasStartDate( true );
834 } else {
835 anEvent->setHasStartDate( false );
836 }
837
838 // alarm stuff
839 if ( ( vo = isAPropertyOf( vtodo, VCDAlarmProp ) ) ) {
840 Alarm *alarm = anEvent->newAlarm();
841 VObject *a;
842 if ( ( a = isAPropertyOf( vo, VCRunTimeProp ) ) ) {
843 alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
844 deleteStr( s );
845 }
846 alarm->setEnabled( true );
847 if ( ( vo = isAPropertyOf( vtodo, VCPAlarmProp ) ) ) {
848 if ( ( a = isAPropertyOf( vo, VCProcedureNameProp ) ) ) {
849 s = fakeCString( vObjectUStringZValue( a ) );
850 alarm->setProcedureAlarm( QFile::decodeName( s ) );
851 deleteStr( s );
852 }
853 }
854 if ( ( vo = isAPropertyOf( vtodo, VCAAlarmProp ) ) ) {
855 if ( ( a = isAPropertyOf( vo, VCAudioContentProp ) ) ) {
856 s = fakeCString( vObjectUStringZValue( a ) );
857 alarm->setAudioAlarm( QFile::decodeName( s ) );
858 deleteStr( s );
859 }
860 }
861 }
862
863 // related todo
864 if ( ( vo = isAPropertyOf( vtodo, VCRelatedToProp ) ) != 0 ) {
865 anEvent->setRelatedToUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
866 deleteStr( s );
867 d->mTodosRelate.append( anEvent );
868 }
869
870 // categories
871 if ( ( vo = isAPropertyOf( vtodo, VCCategoriesProp ) ) != 0 ) {
872 s = fakeCString( vObjectUStringZValue( vo ) );
873 QString categories = QString::fromLocal8Bit( s );
874 deleteStr( s );
875 QStringList tmpStrList = categories.split( ';' );
876 anEvent->setCategories( tmpStrList );
877 }
878
879 /* PILOT SYNC STUFF */
880 if ( ( vo = isAPropertyOf( vtodo, KPilotIdProp ) ) ) {
881 anEvent->setNonKDECustomProperty(
882 KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
883 deleteStr( s );
884 if ( ( vo = isAPropertyOf( vtodo, KPilotStatusProp ) ) ) {
885 anEvent->setNonKDECustomProperty(
886 KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
887 deleteStr( s );
888 } else {
889 anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( SYNCMOD ) );
890 }
891 }
892
893 return anEvent;
894}
895
896Event *VCalFormat::VEventToEvent( VObject *vevent )
897{
898 VObject *vo;
899 VObjectIterator voi;
900 char *s;
901
902 Event *anEvent = new Event;
903
904 // creation date
905 if ( ( vo = isAPropertyOf( vevent, VCDCreatedProp ) ) != 0 ) {
906 anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
907 deleteStr( s );
908 }
909
910 // unique id
911 vo = isAPropertyOf( vevent, VCUniqueStringProp );
912 // while the UID property is preferred, it is not required. We'll use the
913 // default Event UID if none is given.
914 if ( vo ) {
915 anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
916 deleteStr( s );
917 }
918
919 // revision
920 // again NSCAL doesn't give us much to work with, so we improvise...
921 anEvent->setRevision( 0 );
922 if ( ( vo = isAPropertyOf( vevent, VCSequenceProp ) ) != 0 ) {
923 s = fakeCString( vObjectUStringZValue( vo ) );
924 if ( s ) {
925 anEvent->setRevision( atoi( s ) );
926 deleteStr( s );
927 }
928 }
929
930 // last modification date
931 if ( ( vo = isAPropertyOf( vevent, VCLastModifiedProp ) ) != 0 ) {
932 anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
933 deleteStr( s );
934 } else {
935 anEvent->setLastModified( KDateTime::currentUtcDateTime() );
936 }
937
938 // organizer
939 // if our extension property for the event's ORGANIZER exists, add it.
940 if ( ( vo = isAPropertyOf( vevent, ICOrganizerProp ) ) != 0 ) {
941 // FIXME: Also use the full name, not just the email address
942 anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
943 deleteStr( s );
944 } else {
945 anEvent->setOrganizer( d->mCalendar->owner() );
946 }
947
948 // deal with attendees.
949 initPropIterator( &voi, vevent );
950 while ( moreIteration( &voi ) ) {
951 vo = nextVObject( &voi );
952 if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
953 Attendee *a;
954 VObject *vp;
955 s = fakeCString( vObjectUStringZValue( vo ) );
956 QString tmpStr = QString::fromLocal8Bit( s );
957 deleteStr( s );
958 tmpStr = tmpStr.simplified();
959 int emailPos1, emailPos2;
960 if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
961 // both email address and name
962 emailPos2 = tmpStr.lastIndexOf( '>' );
963 a = new Attendee( tmpStr.left( emailPos1 - 1 ),
964 tmpStr.mid( emailPos1 + 1,
965 emailPos2 - ( emailPos1 + 1 ) ) );
966 } else if ( tmpStr.indexOf( '@' ) > 0 ) {
967 // just an email address
968 a = new Attendee( 0, tmpStr );
969 } else {
970 // just a name
971 QString email = tmpStr.replace( ' ', '.' );
972 a = new Attendee( tmpStr, email );
973 }
974
975 // is there an RSVP property?
976 if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
977 a->setRSVP( vObjectStringZValue( vp ) );
978 }
979 // is there a status property?
980 if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
981 a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
982 }
983 // add the attendee
984 anEvent->addAttendee( a );
985 }
986 }
987
988 // This isn't strictly true. An event that doesn't have a start time
989 // or an end time isn't all-day, it has an anchor in time but it doesn't
990 // "take up" any time.
991 /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
992 (isAPropertyOf(vevent, VCDTendProp) == 0)) {
993 anEvent->setAllDay(true);
994 } else {
995 }*/
996
997 anEvent->setAllDay( false );
998
999 // start time
1000 if ( ( vo = isAPropertyOf( vevent, VCDTstartProp ) ) != 0 ) {
1001 anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
1002 deleteStr( s );
1003 if ( anEvent->dtStart().time().isNull() ) {
1004 anEvent->setAllDay( true );
1005 }
1006 }
1007
1008 // stop time
1009 if ( ( vo = isAPropertyOf( vevent, VCDTendProp ) ) != 0 ) {
1010 anEvent->setDtEnd( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
1011 deleteStr( s );
1012 if ( anEvent->dtEnd().time().isNull() ) {
1013 anEvent->setAllDay( true );
1014 }
1015 }
1016
1017 // at this point, there should be at least a start or end time.
1018 // fix up for events that take up no time but have a time associated
1019 if ( !( vo = isAPropertyOf( vevent, VCDTstartProp ) ) ) {
1020 anEvent->setDtStart( anEvent->dtEnd() );
1021 }
1022 if ( !( vo = isAPropertyOf( vevent, VCDTendProp ) ) ) {
1023 anEvent->setDtEnd( anEvent->dtStart() );
1024 }
1025
1026 ///////////////////////////////////////////////////////////////////////////
1027
1028 // repeat stuff
1029 if ( ( vo = isAPropertyOf( vevent, VCRRuleProp ) ) != 0 ) {
1030 QString tmpStr = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
1031 deleteStr( s );
1032 tmpStr = tmpStr.simplified();
1033 tmpStr = tmpStr.toUpper();
1034
1035 // first, read the type of the recurrence
1036 int typelen = 1;
1037 uint type = Recurrence::rNone;
1038 if ( tmpStr.left(1) == "D" ) {
1039 type = Recurrence::rDaily;
1040 } else if ( tmpStr.left(1) == "W" ) {
1041 type = Recurrence::rWeekly;
1042 } else {
1043 typelen = 2;
1044 if ( tmpStr.left(2) == "MP" ) {
1045 type = Recurrence::rMonthlyPos;
1046 } else if ( tmpStr.left(2) == "MD" ) {
1047 type = Recurrence::rMonthlyDay;
1048 } else if ( tmpStr.left(2) == "YM" ) {
1049 type = Recurrence::rYearlyMonth;
1050 } else if ( tmpStr.left(2) == "YD" ) {
1051 type = Recurrence::rYearlyDay;
1052 }
1053 }
1054
1055 if ( type != Recurrence::rNone ) {
1056
1057 // Immediately after the type is the frequency
1058 int index = tmpStr.indexOf( ' ' );
1059 int last = tmpStr.lastIndexOf( ' ' ) + 1; // find last entry
1060 int rFreq = tmpStr.mid( typelen, ( index - 1 ) ).toInt();
1061 ++index; // advance to beginning of stuff after freq
1062
1063 // Read the type-specific settings
1064 switch ( type ) {
1065 case Recurrence::rDaily:
1066 anEvent->recurrence()->setDaily(rFreq);
1067 break;
1068
1069 case Recurrence::rWeekly:
1070 {
1071 QBitArray qba(7);
1072 QString dayStr;
1073 if ( index == last ) {
1074 // e.g. W1 #0
1075 qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
1076 } else {
1077 // e.g. W1 SU #0
1078 while ( index < last ) {
1079 dayStr = tmpStr.mid( index, 3 );
1080 int dayNum = numFromDay( dayStr );
1081 if ( dayNum >= 0 ) {
1082 qba.setBit( dayNum );
1083 }
1084 index += 3; // advance to next day, or possibly "#"
1085 }
1086 }
1087 anEvent->recurrence()->setWeekly( rFreq, qba );
1088 break;
1089 }
1090
1091 case Recurrence::rMonthlyPos:
1092 {
1093 anEvent->recurrence()->setMonthly( rFreq );
1094
1095 QBitArray qba(7);
1096 short tmpPos;
1097 if ( index == last ) {
1098 // e.g. MP1 #0
1099 tmpPos = anEvent->dtStart().date().day() / 7 + 1;
1100 if ( tmpPos == 5 ) {
1101 tmpPos = -1;
1102 }
1103 qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
1104 anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
1105 } else {
1106 // e.g. MP1 1+ SU #0
1107 while ( index < last ) {
1108 tmpPos = tmpStr.mid( index, 1 ).toShort();
1109 index += 1;
1110 if ( tmpStr.mid( index, 1 ) == "-" ) {
1111 // convert tmpPos to negative
1112 tmpPos = 0 - tmpPos;
1113 }
1114 index += 2; // advance to day(s)
1115 while ( numFromDay( tmpStr.mid( index, 3 ) ) >= 0 ) {
1116 int dayNum = numFromDay( tmpStr.mid( index, 3 ) );
1117 qba.setBit( dayNum );
1118 index += 3; // advance to next day, or possibly pos or "#"
1119 }
1120 anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
1121 qba.detach();
1122 qba.fill( false ); // clear out
1123 } // while != "#"
1124 }
1125 break;
1126 }
1127
1128 case Recurrence::rMonthlyDay:
1129 anEvent->recurrence()->setMonthly( rFreq );
1130 if( index == last ) {
1131 // e.g. MD1 #0
1132 short tmpDay = anEvent->dtStart().date().day();
1133 anEvent->recurrence()->addMonthlyDate( tmpDay );
1134 } else {
1135 // e.g. MD1 3 #0
1136 while ( index < last ) {
1137 int index2 = tmpStr.indexOf( ' ', index );
1138 short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
1139 index = index2 - 1;
1140 if ( tmpStr.mid( index, 1 ) == "-" ) {
1141 tmpDay = 0 - tmpDay;
1142 }
1143 index += 2; // advance the index;
1144 anEvent->recurrence()->addMonthlyDate( tmpDay );
1145 } // while != #
1146 }
1147 break;
1148
1149 case Recurrence::rYearlyMonth:
1150 anEvent->recurrence()->setYearly( rFreq );
1151
1152 if ( index == last ) {
1153 // e.g. YM1 #0
1154 short tmpMonth = anEvent->dtStart().date().month();
1155 anEvent->recurrence()->addYearlyMonth( tmpMonth );
1156 } else {
1157 // e.g. YM1 3 #0
1158 while ( index < last ) {
1159 int index2 = tmpStr.indexOf( ' ', index );
1160 short tmpMonth = tmpStr.mid( index, ( index2 - index ) ).toShort();
1161 index = index2 + 1;
1162 anEvent->recurrence()->addYearlyMonth( tmpMonth );
1163 } // while != #
1164 }
1165 break;
1166
1167 case Recurrence::rYearlyDay:
1168 anEvent->recurrence()->setYearly( rFreq );
1169
1170 if ( index == last ) {
1171 // e.g. YD1 #0
1172 short tmpDay = anEvent->dtStart().date().dayOfYear();
1173 anEvent->recurrence()->addYearlyDay( tmpDay );
1174 } else {
1175 // e.g. YD1 123 #0
1176 while ( index < last ) {
1177 int index2 = tmpStr.indexOf( ' ', index );
1178 short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
1179 index = index2 + 1;
1180 anEvent->recurrence()->addYearlyDay( tmpDay );
1181 } // while != #
1182 }
1183 break;
1184
1185 default:
1186 break;
1187 }
1188
1189 // find the last field, which is either the duration or the end date
1190 index = last;
1191 if ( tmpStr.mid( index, 1 ) == "#" ) {
1192 // Nr of occurrences
1193 index++;
1194 int rDuration = tmpStr.mid( index, tmpStr.length() - index ).toInt();
1195 if ( rDuration > 0 ) {
1196 anEvent->recurrence()->setDuration( rDuration );
1197 }
1198 } else if ( tmpStr.indexOf( 'T', index ) != -1 ) {
1199 KDateTime rEndDate = ISOToKDateTime( tmpStr.mid( index, tmpStr.length() - index ) );
1200 rEndDate.setDateOnly( true );
1201 anEvent->recurrence()->setEndDateTime( rEndDate );
1202 }
1203// anEvent->recurrence()->dump();
1204
1205 } else {
1206 kDebug() << "we don't understand this type of recurrence!";
1207 } // if known recurrence type
1208 } // repeats
1209
1210 // recurrence exceptions
1211 if ( ( vo = isAPropertyOf( vevent, VCExpDateProp ) ) != 0 ) {
1212 s = fakeCString( vObjectUStringZValue( vo ) );
1213 QStringList exDates = QString::fromLocal8Bit( s ).split( ',' );
1214 QStringList::ConstIterator it;
1215 for ( it = exDates.constBegin(); it != exDates.constEnd(); ++it ) {
1216 anEvent->recurrence()->addExDate( ISOToQDate(*it) );
1217 }
1218 deleteStr( s );
1219 }
1220
1221 // summary
1222 if ( ( vo = isAPropertyOf( vevent, VCSummaryProp ) ) ) {
1223 s = fakeCString( vObjectUStringZValue( vo ) );
1224 anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
1225 deleteStr( s );
1226 }
1227
1228 // description
1229 if ( ( vo = isAPropertyOf( vevent, VCDescriptionProp ) ) != 0 ) {
1230 s = fakeCString( vObjectUStringZValue( vo ) );
1231 bool isRich = Qt::mightBeRichText( s );
1232 if ( !anEvent->description().isEmpty() ) {
1233 anEvent->setDescription(
1234 anEvent->description() + '\n' + QString::fromLocal8Bit( s ), isRich );
1235 } else {
1236 anEvent->setDescription( QString::fromLocal8Bit( s ), isRich );
1237 }
1238 deleteStr( s );
1239 }
1240
1241 // location
1242 if ( ( vo = isAPropertyOf( vevent, VCLocationProp ) ) != 0 ) {
1243 s = fakeCString( vObjectUStringZValue( vo ) );
1244 anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
1245 deleteStr( s );
1246 }
1247
1248 // some stupid vCal exporters ignore the standard and use Description
1249 // instead of Summary for the default field. Correct for this.
1250 if ( anEvent->summary().isEmpty() && !( anEvent->description().isEmpty() ) ) {
1251 QString tmpStr = anEvent->description().simplified();
1252 anEvent->setDescription( "" );
1253 anEvent->setSummary( tmpStr );
1254 }
1255
1256#if 0
1257 // status
1258 if ( ( vo = isAPropertyOf( vevent, VCStatusProp ) ) != 0 ) {
1259 QString tmpStr( s = fakeCString( vObjectUStringZValue( vo ) ) );
1260 deleteStr( s );
1261// TODO: Define Event status
1262// anEvent->setStatus( tmpStr );
1263 } else {
1264// anEvent->setStatus( "NEEDS ACTION" );
1265 }
1266#endif
1267
1268 // secrecy
1269 Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
1270 if ( ( vo = isAPropertyOf( vevent, VCClassProp ) ) != 0 ) {
1271 s = fakeCString( vObjectUStringZValue( vo ) );
1272 if ( s && strcmp( s, "PRIVATE" ) == 0 ) {
1273 secrecy = Incidence::SecrecyPrivate;
1274 } else if ( s && strcmp( s, "CONFIDENTIAL" ) == 0 ) {
1275 secrecy = Incidence::SecrecyConfidential;
1276 }
1277 deleteStr( s );
1278 }
1279 anEvent->setSecrecy( secrecy );
1280
1281 // categories
1282 if ( ( vo = isAPropertyOf( vevent, VCCategoriesProp ) ) != 0 ) {
1283 s = fakeCString( vObjectUStringZValue( vo ) );
1284 QString categories = QString::fromLocal8Bit( s );
1285 deleteStr( s );
1286 QStringList tmpStrList = categories.split( ',' );
1287 anEvent->setCategories( tmpStrList );
1288 }
1289
1290 // attachments
1291 initPropIterator( &voi, vevent );
1292 while ( moreIteration( &voi ) ) {
1293 vo = nextVObject( &voi );
1294 if ( strcmp( vObjectName( vo ), VCAttachProp ) == 0 ) {
1295 s = fakeCString( vObjectUStringZValue( vo ) );
1296 anEvent->addAttachment( new Attachment( QString( s ) ) );
1297 deleteStr( s );
1298 }
1299 }
1300
1301 // resources
1302 if ( ( vo = isAPropertyOf( vevent, VCResourcesProp ) ) != 0 ) {
1303 QString resources = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
1304 deleteStr( s );
1305 QStringList tmpStrList = resources.split( ';' );
1306 anEvent->setResources( tmpStrList );
1307 }
1308
1309 // alarm stuff
1310 if ( ( vo = isAPropertyOf( vevent, VCDAlarmProp ) ) ) {
1311 Alarm *alarm = anEvent->newAlarm();
1312 VObject *a;
1313 if ( ( a = isAPropertyOf( vo, VCRunTimeProp ) ) ) {
1314 alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
1315 deleteStr( s );
1316 }
1317 alarm->setEnabled( true );
1318 if ( ( vo = isAPropertyOf( vevent, VCPAlarmProp ) ) ) {
1319 if ( ( a = isAPropertyOf( vo, VCProcedureNameProp ) ) ) {
1320 s = fakeCString( vObjectUStringZValue( a ) );
1321 alarm->setProcedureAlarm( QFile::decodeName( s ) );
1322 deleteStr( s );
1323 }
1324 }
1325 if ( ( vo = isAPropertyOf( vevent, VCAAlarmProp ) ) ) {
1326 if ( ( a = isAPropertyOf( vo, VCAudioContentProp ) ) ) {
1327 s = fakeCString( vObjectUStringZValue( a ) );
1328 alarm->setAudioAlarm( QFile::decodeName( s ) );
1329 deleteStr( s );
1330 }
1331 }
1332 }
1333
1334 // priority
1335 if ( ( vo = isAPropertyOf( vevent, VCPriorityProp ) ) ) {
1336 s = fakeCString( vObjectUStringZValue( vo ) );
1337 if ( s ) {
1338 anEvent->setPriority( atoi( s ) );
1339 deleteStr( s );
1340 }
1341 }
1342
1343 // transparency
1344 if ( ( vo = isAPropertyOf( vevent, VCTranspProp ) ) != 0 ) {
1345 s = fakeCString( vObjectUStringZValue( vo ) );
1346 if ( s ) {
1347 int i = atoi( s );
1348 anEvent->setTransparency( i == 1 ? Event::Transparent : Event::Opaque );
1349 deleteStr( s );
1350 }
1351 }
1352
1353 // related event
1354 if ( ( vo = isAPropertyOf( vevent, VCRelatedToProp ) ) != 0 ) {
1355 anEvent->setRelatedToUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
1356 deleteStr( s );
1357 d->mEventsRelate.append( anEvent );
1358 }
1359
1360 /* PILOT SYNC STUFF */
1361 if ( ( vo = isAPropertyOf( vevent, KPilotIdProp ) ) ) {
1362 anEvent->setNonKDECustomProperty(
1363 KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
1364 deleteStr( s );
1365 if ( ( vo = isAPropertyOf( vevent, KPilotStatusProp ) ) ) {
1366 anEvent->setNonKDECustomProperty(
1367 KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
1368 deleteStr( s );
1369 } else {
1370 anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( SYNCMOD ) );
1371 }
1372 }
1373
1374 return anEvent;
1375}
1376
1377QString VCalFormat::qDateToISO( const QDate &qd )
1378{
1379 QString tmpStr;
1380
1381 if ( !qd.isValid() ) {
1382 return QString();
1383 }
1384
1385 tmpStr.sprintf( "%.2d%.2d%.2d", qd.year(), qd.month(), qd.day() );
1386 return tmpStr;
1387
1388}
1389
1390QString VCalFormat::kDateTimeToISO( const KDateTime &dt, bool zulu )
1391{
1392 QString tmpStr;
1393
1394 if ( !dt.isValid() ) {
1395 return QString();
1396 }
1397
1398 QDateTime tmpDT;
1399 if ( zulu ) {
1400 tmpDT = dt.toUtc().dateTime();
1401 } else {
1402 tmpDT = dt.toTimeSpec( d->mCalendar->timeSpec() ).dateTime();
1403 }
1404 tmpStr.sprintf( "%.2d%.2d%.2dT%.2d%.2d%.2d",
1405 tmpDT.date().year(), tmpDT.date().month(),
1406 tmpDT.date().day(), tmpDT.time().hour(),
1407 tmpDT.time().minute(), tmpDT.time().second() );
1408 if ( zulu ) {
1409 tmpStr += 'Z';
1410 }
1411 return tmpStr;
1412}
1413
1414KDateTime VCalFormat::ISOToKDateTime( const QString &dtStr )
1415{
1416 QDate tmpDate;
1417 QTime tmpTime;
1418 QString tmpStr;
1419 int year, month, day, hour, minute, second;
1420
1421 tmpStr = dtStr;
1422 year = tmpStr.left( 4 ).toInt();
1423 month = tmpStr.mid( 4, 2 ).toInt();
1424 day = tmpStr.mid( 6, 2 ).toInt();
1425 hour = tmpStr.mid( 9, 2 ).toInt();
1426 minute = tmpStr.mid( 11, 2 ).toInt();
1427 second = tmpStr.mid( 13, 2 ).toInt();
1428 tmpDate.setYMD( year, month, day );
1429 tmpTime.setHMS( hour, minute, second );
1430
1431 if ( tmpDate.isValid() && tmpTime.isValid() ) {
1432 // correct for GMT if string is in Zulu format
1433 if ( dtStr.at( dtStr.length() - 1 ) == 'Z' ) {
1434 return KDateTime( tmpDate, tmpTime, KDateTime::UTC );
1435 } else {
1436 return KDateTime( tmpDate, tmpTime, d->mCalendar->timeSpec() );
1437 }
1438 } else {
1439 return KDateTime();
1440 }
1441}
1442
1443QDate VCalFormat::ISOToQDate( const QString &dateStr )
1444{
1445 int year, month, day;
1446
1447 year = dateStr.left( 4 ).toInt();
1448 month = dateStr.mid( 4, 2 ).toInt();
1449 day = dateStr.mid( 6, 2 ).toInt();
1450
1451 return QDate( year, month, day );
1452}
1453
1454// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
1455// and break it down from it's tree-like format into the dictionary format
1456// that is used internally in the VCalFormat.
1457void VCalFormat::populate( VObject *vcal )
1458{
1459 // this function will populate the caldict dictionary and other event
1460 // lists. It turns vevents into Events and then inserts them.
1461
1462 VObjectIterator i;
1463 VObject *curVO, *curVOProp;
1464 Event *anEvent;
1465
1466 if ( ( curVO = isAPropertyOf( vcal, ICMethodProp ) ) != 0 ) {
1467 char *methodType = 0;
1468 methodType = fakeCString( vObjectUStringZValue( curVO ) );
1469 kDebug() << "This calendar is an iTIP transaction of type '"
1470 << methodType << "'";
1471 deleteStr( methodType );
1472 }
1473
1474 // warn the user that we might have trouble reading non-known calendar.
1475 if ( ( curVO = isAPropertyOf( vcal, VCProdIdProp ) ) != 0 ) {
1476 char *s = fakeCString( vObjectUStringZValue( curVO ) );
1477 if ( !s || strcmp( productId().toLocal8Bit(), s ) != 0 ) {
1478 kDebug() << "This vCalendar file was not created by KOrganizer or"
1479 << "any other product we support. Loading anyway...";
1480 }
1481 setLoadedProductId( s );
1482 deleteStr( s );
1483 }
1484
1485 // warn the user we might have trouble reading this unknown version.
1486 if ( ( curVO = isAPropertyOf( vcal, VCVersionProp ) ) != 0 ) {
1487 char *s = fakeCString( vObjectUStringZValue( curVO ) );
1488 if ( !s || strcmp( _VCAL_VERSION, s ) != 0 ) {
1489 kDebug() << "This vCalendar file has version" << s
1490 << "We only support" << _VCAL_VERSION;
1491 }
1492 deleteStr( s );
1493 }
1494
1495#if 0
1496 // set the time zone (this is a property of the view, so just discard!)
1497 if ( ( curVO = isAPropertyOf( vcal, VCTimeZoneProp ) ) != 0 ) {
1498 char *s = fakeCString( vObjectUStringZValue( curVO ) );
1499 d->mCalendar->setTimeZone( s );
1500 deleteStr( s );
1501 }
1502#endif
1503
1504 // Store all events with a relatedTo property in a list for post-processing
1505 d->mEventsRelate.clear();
1506 d->mTodosRelate.clear();
1507
1508 initPropIterator( &i, vcal );
1509
1510 // go through all the vobjects in the vcal
1511 while ( moreIteration( &i ) ) {
1512 curVO = nextVObject( &i );
1513
1514 /************************************************************************/
1515
1516 // now, check to see that the object is an event or todo.
1517 if ( strcmp( vObjectName( curVO ), VCEventProp ) == 0 ) {
1518
1519 if ( ( curVOProp = isAPropertyOf( curVO, KPilotStatusProp ) ) != 0 ) {
1520 char *s;
1521 s = fakeCString( vObjectUStringZValue( curVOProp ) );
1522 // check to see if event was deleted by the kpilot conduit
1523 if ( s ) {
1524 if ( atoi( s ) == SYNCDEL ) {
1525 deleteStr( s );
1526 kDebug() << "skipping pilot-deleted event";
1527 goto SKIP;
1528 }
1529 deleteStr( s );
1530 }
1531 }
1532
1533 // this code checks to see if we are trying to read in an event
1534 // that we already find to be in the calendar. If we find this
1535 // to be the case, we skip the event.
1536 if ( ( curVOProp = isAPropertyOf( curVO, VCUniqueStringProp ) ) != 0 ) {
1537 char *s = fakeCString( vObjectUStringZValue( curVOProp ) );
1538 QString tmpStr( s );
1539 deleteStr( s );
1540
1541 if ( d->mCalendar->incidence( tmpStr ) ) {
1542 goto SKIP;
1543 }
1544 }
1545
1546 if ( ( !( curVOProp = isAPropertyOf( curVO, VCDTstartProp ) ) ) &&
1547 ( !( curVOProp = isAPropertyOf( curVO, VCDTendProp ) ) ) ) {
1548 kDebug() << "found a VEvent with no DTSTART and no DTEND! Skipping...";
1549 goto SKIP;
1550 }
1551
1552 anEvent = VEventToEvent( curVO );
1553 // we now use addEvent instead of insertEvent so that the
1554 // signal/slot get connected.
1555 if ( anEvent ) {
1556 if ( anEvent->dtStart().isValid() && anEvent->dtEnd().isValid() ) {
1557 d->mCalendar->addEvent( anEvent );
1558 }
1559 } else {
1560 // some sort of error must have occurred while in translation.
1561 goto SKIP;
1562 }
1563 } else if ( strcmp( vObjectName( curVO ), VCTodoProp ) == 0 ) {
1564 Todo *aTodo = VTodoToEvent( curVO );
1565
1566 Todo *old = d->mCalendar->todo( aTodo->uid() );
1567 if ( old ) {
1568 d->mCalendar->deleteTodo( old );
1569 d->mTodosRelate.removeAll( old );
1570 }
1571
1572 d->mCalendar->addTodo( aTodo );
1573 } else if ( ( strcmp( vObjectName( curVO ), VCVersionProp ) == 0 ) ||
1574 ( strcmp( vObjectName( curVO ), VCProdIdProp ) == 0 ) ||
1575 ( strcmp( vObjectName( curVO ), VCTimeZoneProp ) == 0 ) ) {
1576 // do nothing, we know these properties and we want to skip them.
1577 // we have either already processed them or are ignoring them.
1578 ;
1579 } else {
1580 kDebug() << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
1581 }
1582 SKIP:
1583 ;
1584 } // while
1585
1586 // Post-Process list of events with relations, put Event objects in relation
1587 Event::List::ConstIterator eIt;
1588 for ( eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt ) {
1589 (*eIt)->setRelatedTo( d->mCalendar->incidence( (*eIt)->relatedToUid() ) );
1590 }
1591 Todo::List::ConstIterator tIt;
1592 for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) {
1593 (*tIt)->setRelatedTo( d->mCalendar->incidence( (*tIt)->relatedToUid() ) );
1594 }
1595}
1596
1597const char *VCalFormat::dayFromNum( int day )
1598{
1599 const char *days[7] = { "MO ", "TU ", "WE ", "TH ", "FR ", "SA ", "SU " };
1600
1601 return days[day];
1602}
1603
1604int VCalFormat::numFromDay( const QString &day )
1605{
1606 if ( day == "MO " ) {
1607 return 0;
1608 }
1609 if ( day == "TU " ) {
1610 return 1;
1611 }
1612 if ( day == "WE " ) {
1613 return 2;
1614 }
1615 if ( day == "TH " ) {
1616 return 3;
1617 }
1618 if ( day == "FR " ) {
1619 return 4;
1620 }
1621 if ( day == "SA " ) {
1622 return 5;
1623 }
1624 if ( day == "SU " ) {
1625 return 6;
1626 }
1627
1628 return -1; // something bad happened. :)
1629}
1630
1631Attendee::PartStat VCalFormat::readStatus( const char *s ) const
1632{
1633 QString statStr = s;
1634 statStr = statStr.toUpper();
1635 Attendee::PartStat status;
1636
1637 if ( statStr == "X-ACTION" ) {
1638 status = Attendee::NeedsAction;
1639 } else if ( statStr == "NEEDS ACTION" ) {
1640 status = Attendee::NeedsAction;
1641 } else if ( statStr == "ACCEPTED" ) {
1642 status = Attendee::Accepted;
1643 } else if ( statStr == "SENT" ) {
1644 status = Attendee::NeedsAction;
1645 } else if ( statStr == "TENTATIVE" ) {
1646 status = Attendee::Tentative;
1647 } else if ( statStr == "CONFIRMED" ) {
1648 status = Attendee::Accepted;
1649 } else if ( statStr == "DECLINED" ) {
1650 status = Attendee::Declined;
1651 } else if ( statStr == "COMPLETED" ) {
1652 status = Attendee::Completed;
1653 } else if ( statStr == "DELEGATED" ) {
1654 status = Attendee::Delegated;
1655 } else {
1656 kDebug() << "error setting attendee mStatus, unknown mStatus!";
1657 status = Attendee::NeedsAction;
1658 }
1659
1660 return status;
1661}
1662
1663QByteArray VCalFormat::writeStatus( Attendee::PartStat status ) const
1664{
1665 switch( status ) {
1666 default:
1667 case Attendee::NeedsAction:
1668 return "NEEDS ACTION";
1669 break;
1670 case Attendee::Accepted:
1671 return "ACCEPTED";
1672 break;
1673 case Attendee::Declined:
1674 return "DECLINED";
1675 break;
1676 case Attendee::Tentative:
1677 return "TENTATIVE";
1678 break;
1679 case Attendee::Delegated:
1680 return "DELEGATED";
1681 break;
1682 case Attendee::Completed:
1683 return "COMPLETED";
1684 break;
1685 case Attendee::InProcess:
1686 return "NEEDS ACTION";
1687 break;
1688 }
1689}
1690