1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling calendar data and
25 defines the IncidenceBase class.
26
27 @brief
28 An abstract base class that provides a common base for all calendar incidence
29 classes.
30
31 @author Cornelius Schumacher \<schumacher@kde.org\>
32 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
33*/
34
35#include "incidencebase.h"
36#include "calformat.h"
37#include "incidenceformatter.h"
38
39#include <kglobal.h>
40#include <klocale.h>
41#include <klocalizedstring.h>
42#include <kdebug.h>
43#include <kurl.h>
44#include <ksystemtimezone.h>
45
46#include <QtCore/QList>
47
48using namespace KCal;
49
50/**
51 Private class that helps to provide binary compatibility between releases.
52 @internal
53*/
54//@cond PRIVATE
55class KCal::IncidenceBase::Private
56{
57 public:
58 Private()
59 : mUpdateGroupLevel( 0 ),
60 mUpdatedPending( false ),
61 mAllDay( true ),
62 mHasDuration( false )
63 { mAttendees.setAutoDelete( true ); }
64
65 Private( const Private &other )
66 : mUpdateGroupLevel( 0 ),
67 mUpdatedPending( false ),
68 mAllDay( true ),
69 mHasDuration( false )
70 {
71 mAttendees.setAutoDelete( true );
72 init( other );
73 }
74
75 void init( const Private &other );
76
77 KDateTime mLastModified; // incidence last modified date
78 KDateTime mDtStart; // incidence start time
79 Person mOrganizer; // incidence person (owner)
80 QString mUid; // incidence unique id
81 Duration mDuration; // incidence duration
82 int mUpdateGroupLevel; // if non-zero, suppresses update() calls
83 bool mUpdatedPending; // true if an update has occurred since startUpdates()
84 bool mAllDay; // true if the incidence is all-day
85 bool mHasDuration; // true if the incidence has a duration
86
87 Attendee::List mAttendees; // list of incidence attendees
88 QStringList mComments; // list of incidence comments
89 QList<IncidenceObserver*> mObservers; // list of incidence observers
90};
91
92void IncidenceBase::Private::init( const Private &other )
93{
94 mLastModified = other.mLastModified;
95 mDtStart = other.mDtStart;
96 mOrganizer = other.mOrganizer;
97 mUid = other.mUid;
98 mDuration = other.mDuration;
99 mAllDay = other.mAllDay;
100 mHasDuration = other.mHasDuration;
101 mComments = other.mComments;
102
103 mAttendees.clearAll();
104 Attendee::List::ConstIterator it;
105 for ( it = other.mAttendees.constBegin(); it != other.mAttendees.constEnd(); ++it ) {
106 mAttendees.append( new Attendee( *(*it) ) );
107 }
108}
109//@endcond
110
111IncidenceBase::IncidenceBase()
112 : d( new KCal::IncidenceBase::Private )
113{
114 mReadOnly = false;
115
116 setUid( CalFormat::createUniqueId() );
117}
118
119IncidenceBase::IncidenceBase( const IncidenceBase &i )
120 : CustomProperties( i ),
121 d( new KCal::IncidenceBase::Private( *i.d ) )
122{
123 mReadOnly = i.mReadOnly;
124}
125
126IncidenceBase::~IncidenceBase()
127{
128 delete d;
129}
130
131IncidenceBase &IncidenceBase::operator=( const IncidenceBase &other )
132{
133 // check for self assignment
134 if ( &other == this ) {
135 return *this;
136 }
137
138 CustomProperties::operator=( other );
139 d->init( *other.d );
140 mReadOnly = other.mReadOnly;
141 return *this;
142}
143
144bool IncidenceBase::operator==( const IncidenceBase &i2 ) const
145{
146 if ( attendees().count() != i2.attendees().count() ) {
147 return false; // no need to check further
148 }
149
150 Attendee::List al1 = attendees();
151 Attendee::List al2 = i2.attendees();
152 Attendee::List::ConstIterator a1 = al1.constBegin();
153 Attendee::List::ConstIterator a2 = al2.constBegin();
154 //TODO Does the order of attendees in the list really matter?
155 //Please delete this comment if you know it's ok, kthx
156 for ( ; a1 != al1.constEnd() && a2 != al2.constEnd(); ++a1, ++a2 ) {
157 if ( !( **a1 == **a2 ) ) {
158 return false;
159 }
160 }
161
162 if ( !CustomProperties::operator == (i2) ) {
163 return false;
164 }
165
166 return
167 dtStart() == i2.dtStart() &&
168 organizer() == i2.organizer() &&
169 uid() == i2.uid() &&
170 // Don't compare lastModified, otherwise the operator is not
171 // of much use. We are not comparing for identity, after all.
172 allDay() == i2.allDay() &&
173 duration() == i2.duration() &&
174 hasDuration() == i2.hasDuration();
175 // no need to compare mObserver
176}
177
178void IncidenceBase::setUid( const QString &uid )
179{
180 d->mUid = uid;
181 updated();
182}
183
184QString IncidenceBase::uid() const
185{
186 return d->mUid;
187}
188
189void IncidenceBase::setLastModified( const KDateTime &lm )
190{
191 // DON'T! updated() because we call this from
192 // Calendar::updateEvent().
193
194 // Convert to UTC and remove milliseconds part.
195 KDateTime current = lm.toUtc();
196 QTime t = current.time();
197 t.setHMS( t.hour(), t.minute(), t.second(), 0 );
198 current.setTime( t );
199
200 d->mLastModified = current;
201}
202
203KDateTime IncidenceBase::lastModified() const
204{
205 return d->mLastModified;
206}
207
208void IncidenceBase::setOrganizer( const Person &o )
209{
210 // we don't check for readonly here, because it is
211 // possible that by setting the organizer we are changing
212 // the event's readonly status...
213 d->mOrganizer = o;
214
215 updated();
216}
217
218void IncidenceBase::setOrganizer( const QString &o )
219{
220 QString mail( o );
221 if ( mail.startsWith( QLatin1String( "MAILTO:" ), Qt::CaseInsensitive ) ) {
222 mail = mail.remove( 0, 7 );
223 }
224
225 // split the string into full name plus email.
226 const Person organizer = Person::fromFullName( mail );
227 setOrganizer( organizer );
228}
229
230Person IncidenceBase::organizer() const
231{
232 return d->mOrganizer;
233}
234
235void IncidenceBase::setReadOnly( bool readOnly )
236{
237 mReadOnly = readOnly;
238}
239
240void IncidenceBase::setDtStart( const KDateTime &dtStart )
241{
242// if ( mReadOnly ) return;
243 d->mDtStart = dtStart;
244 d->mAllDay = dtStart.isDateOnly();
245 updated();
246}
247
248KDateTime IncidenceBase::dtStart() const
249{
250 return d->mDtStart;
251}
252
253QString IncidenceBase::dtStartTimeStr( bool shortfmt, const KDateTime::Spec &spec ) const
254{
255 if ( spec.isValid() ) {
256
257 QString timeZone;
258 if ( spec.timeZone() != KSystemTimeZones::local() ) {
259 timeZone = ' ' + spec.timeZone().name();
260 }
261
262 return KGlobal::locale()->formatTime(
263 dtStart().toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
264 } else {
265 return KGlobal::locale()->formatTime( dtStart().time(), !shortfmt );
266 }
267}
268
269QString IncidenceBase::dtStartDateStr( bool shortfmt, const KDateTime::Spec &spec ) const
270{
271 if ( spec.isValid() ) {
272
273 QString timeZone;
274 if ( spec.timeZone() != KSystemTimeZones::local() ) {
275 timeZone = ' ' + spec.timeZone().name();
276 }
277
278 return KGlobal::locale()->formatDate(
279 dtStart().toTimeSpec( spec ).date(),
280 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
281 } else {
282 return KGlobal::locale()->formatDate(
283 dtStart().date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
284 }
285}
286
287QString IncidenceBase::dtStartStr( bool shortfmt, const KDateTime::Spec &spec ) const
288{
289 if ( allDay() ) {
290 return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec );
291 }
292
293 if ( spec.isValid() ) {
294
295 QString timeZone;
296 if ( spec.timeZone() != KSystemTimeZones::local() ) {
297 timeZone = ' ' + spec.timeZone().name();
298 }
299
300 return KGlobal::locale()->formatDateTime(
301 dtStart().toTimeSpec( spec ).dateTime(),
302 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
303 } else {
304 return KGlobal::locale()->formatDateTime(
305 dtStart().dateTime(),
306 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
307 }
308}
309
310bool IncidenceBase::allDay() const
311{
312 return d->mAllDay;
313}
314
315void IncidenceBase::setAllDay( bool f )
316{
317 if ( mReadOnly || f == d->mAllDay ) {
318 return;
319 }
320 d->mAllDay = f;
321 updated();
322}
323
324void IncidenceBase::shiftTimes( const KDateTime::Spec &oldSpec,
325 const KDateTime::Spec &newSpec )
326{
327 d->mDtStart = d->mDtStart.toTimeSpec( oldSpec );
328 d->mDtStart.setTimeSpec( newSpec );
329 updated();
330}
331
332void IncidenceBase::addComment( const QString &comment )
333{
334 d->mComments += comment;
335}
336
337bool IncidenceBase::removeComment( const QString &comment )
338{
339 bool found = false;
340 QStringList::Iterator i;
341
342 for ( i = d->mComments.begin(); !found && i != d->mComments.end(); ++i ) {
343 if ( (*i) == comment ) {
344 found = true;
345 d->mComments.erase( i );
346 }
347 }
348
349 return found;
350}
351
352void IncidenceBase::clearComments()
353{
354 d->mComments.clear();
355}
356
357QStringList IncidenceBase::comments() const
358{
359 return d->mComments;
360}
361
362void IncidenceBase::addAttendee( Attendee *a, bool doupdate )
363{
364 if ( !a || mReadOnly ) {
365 return;
366 }
367
368 if ( a->name().left(7).toUpper() == "MAILTO:" ) {
369 a->setName( a->name().remove( 0, 7 ) );
370 }
371
372 d->mAttendees.append( a );
373 if ( doupdate ) {
374 updated();
375 }
376}
377
378const Attendee::List &IncidenceBase::attendees() const
379{
380 return d->mAttendees;
381}
382
383int IncidenceBase::attendeeCount() const
384{
385 return d->mAttendees.count();
386}
387
388void IncidenceBase::clearAttendees()
389{
390 if ( mReadOnly ) {
391 return;
392 }
393 qDeleteAll( d->mAttendees );
394 d->mAttendees.clear();
395}
396
397Attendee *IncidenceBase::attendeeByMail( const QString &email ) const
398{
399 Attendee::List::ConstIterator it;
400 for ( it = d->mAttendees.constBegin(); it != d->mAttendees.constEnd(); ++it ) {
401 if ( (*it)->email() == email ) {
402 return *it;
403 }
404 }
405
406 return 0;
407}
408
409Attendee *IncidenceBase::attendeeByMails( const QStringList &emails,
410 const QString &email ) const
411{
412 QStringList mails = emails;
413 if ( !email.isEmpty() ) {
414 mails.append( email );
415 }
416
417 Attendee::List::ConstIterator itA;
418 for ( itA = d->mAttendees.constBegin(); itA != d->mAttendees.constEnd(); ++itA ) {
419 for ( QStringList::const_iterator it = mails.constBegin(); it != mails.constEnd(); ++it ) {
420 if ( (*itA)->email() == (*it) ) {
421 return *itA;
422 }
423 }
424 }
425
426 return 0;
427}
428
429Attendee *IncidenceBase::attendeeByUid( const QString &uid ) const
430{
431 Attendee::List::ConstIterator it;
432 for ( it = d->mAttendees.constBegin(); it != d->mAttendees.constEnd(); ++it ) {
433 if ( (*it)->uid() == uid ) {
434 return *it;
435 }
436 }
437
438 return 0;
439}
440
441void IncidenceBase::setDuration( const Duration &duration )
442{
443 d->mDuration = duration;
444 setHasDuration( true );
445 updated();
446}
447
448Duration IncidenceBase::duration() const
449{
450 return d->mDuration;
451}
452
453void IncidenceBase::setHasDuration( bool hasDuration )
454{
455 d->mHasDuration = hasDuration;
456}
457
458bool IncidenceBase::hasDuration() const
459{
460 return d->mHasDuration;
461}
462
463void IncidenceBase::registerObserver( IncidenceBase::IncidenceObserver *observer )
464{
465 if ( !d->mObservers.contains( observer ) ) {
466 d->mObservers.append( observer );
467 }
468}
469
470void IncidenceBase::unRegisterObserver( IncidenceBase::IncidenceObserver *observer )
471{
472 d->mObservers.removeAll( observer );
473}
474
475void IncidenceBase::updated()
476{
477 if ( d->mUpdateGroupLevel ) {
478 d->mUpdatedPending = true;
479 } else {
480 foreach ( IncidenceObserver *o, d->mObservers ) {
481 if ( o ) {
482 o->incidenceUpdated( this );
483 }
484 }
485 }
486}
487
488void IncidenceBase::startUpdates()
489{
490 ++d->mUpdateGroupLevel;
491}
492
493void IncidenceBase::endUpdates()
494{
495 if ( d->mUpdateGroupLevel > 0 ) {
496 if ( --d->mUpdateGroupLevel == 0 && d->mUpdatedPending ) {
497 d->mUpdatedPending = false;
498 updated();
499 }
500 }
501}
502
503void IncidenceBase::customPropertyUpdated()
504{
505 updated();
506}
507
508KUrl IncidenceBase::uri() const
509{
510 return KUrl( QString( "urn:x-ical:" ) + uid() );
511}
512
513bool IncidenceBase::Visitor::visit( Event *event )
514{
515 Q_UNUSED( event );
516 return false;
517}
518
519bool IncidenceBase::Visitor::visit( Todo *todo )
520{
521 Q_UNUSED( todo );
522 return false;
523}
524
525bool IncidenceBase::Visitor::visit( Journal *journal )
526{
527 Q_UNUSED( journal );
528 return false;
529}
530
531bool IncidenceBase::Visitor::visit( FreeBusy *freebusy )
532{
533 Q_UNUSED( freebusy );
534 return false;
535}
536