KCalendarCore

incidencebase.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the kcalcore library.
3 
4  SPDX-FileCopyrightText: 2001,2004 Cornelius Schumacher <[email protected]>
5  SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <[email protected]>
6  SPDX-FileCopyrightText: 2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
7  SPDX-FileContributor: Alvaro Manera <[email protected]>
8 
9  SPDX-License-Identifier: LGPL-2.0-or-later
10 */
24 #include "incidencebase.h"
25 #include "calformat.h"
26 #include "visitor.h"
27 #include "utils_p.h"
28 
29 #include <QTime>
30 #include "kcalendarcore_debug.h"
31 #include <QUrl>
32 
33 #include <QStringList>
34 
35 #define KCALCORE_MAGIC_NUMBER 0xCA1C012E
36 #define KCALCORE_SERIALIZATION_VERSION 1
37 
38 using namespace KCalendarCore;
39 
44 //@cond PRIVATE
45 class Q_DECL_HIDDEN KCalendarCore::IncidenceBase::Private
46 {
47 public:
48  Private()
49  : mUpdateGroupLevel(0)
50  , mUpdatedPending(false)
51  , mAllDay(false)
52  , mHasDuration(false)
53  {
54  }
55 
56  Private(const Private &other)
57  : mUpdateGroupLevel(0)
58  , mUpdatedPending(false)
59  , mAllDay(true)
60  , mHasDuration(false)
61  {
62  init(other);
63  }
64 
65  ~Private()
66  {
67  }
68 
69  void init(const Private &other);
70 
71  QDateTime mLastModified; // incidence last modified date
72  QDateTime mDtStart; // incidence start time
73  Person mOrganizer; // incidence person (owner)
74  QString mUid; // incidence unique id
75  Duration mDuration; // incidence duration
76  int mUpdateGroupLevel; // if non-zero, suppresses update() calls
77  bool mUpdatedPending = false; // true if an update has occurred since startUpdates()
78  bool mAllDay = false; // true if the incidence is all-day
79  bool mHasDuration = false; // true if the incidence has a duration
80  Attendee::List mAttendees; // list of incidence attendees
81  QStringList mComments; // list of incidence comments
82  QStringList mContacts; // list of incidence contacts
83  QList<IncidenceObserver *> mObservers; // list of incidence observers
84  QSet<Field> mDirtyFields; // Fields that changed since last time the incidence was created
85  // or since resetDirtyFlags() was called
86  QUrl mUrl; // incidence url property
87 };
88 
89 void IncidenceBase::Private::init(const Private &other)
90 {
91  mLastModified = other.mLastModified;
92  mDtStart = other.mDtStart;
93  mOrganizer = other.mOrganizer;
94  mUid = other.mUid;
95  mDuration = other.mDuration;
96  mAllDay = other.mAllDay;
97  mHasDuration = other.mHasDuration;
98 
99  mComments = other.mComments;
100  mContacts = other.mContacts;
101 
102  mAttendees = other.mAttendees;
103  mAttendees.reserve(other.mAttendees.count());
104  mUrl = other.mUrl;
105 }
106 
107 //@endcond
108 
110  : d(new KCalendarCore::IncidenceBase::Private)
111 {
112  mReadOnly = false;
114 }
115 
117  : CustomProperties(i)
118  , d(new KCalendarCore::IncidenceBase::Private(*i.d))
119 {
120  mReadOnly = i.mReadOnly;
121 }
122 
124 {
125  delete d;
126 }
127 
129 {
130  Q_ASSERT(type() == other.type());
131 
132  startUpdates();
133 
134  // assign is virtual, will call the derived class's
135  IncidenceBase &ret = assign(other);
136  endUpdates();
137  return ret;
138 }
139 
141 {
143  d->init(*other.d);
144  mReadOnly = other.mReadOnly;
145  d->mDirtyFields.clear();
146  d->mDirtyFields.insert(FieldUnknown);
147  return *this;
148 }
149 
151 {
152  if (i2.type() != type()) {
153  return false;
154  } else {
155  // equals is virtual, so here we're calling the derived class method
156  return equals(i2);
157  }
158 }
159 
161 {
162  return !operator==(i2);
163 }
164 
166 {
167  if (attendees().count() != i2.attendees().count()) {
168  // qCDebug(KCALCORE_LOG) << "Attendee count is different";
169  return false;
170  }
171 
172  Attendee::List al1 = attendees();
173  Attendee::List al2 = i2.attendees();
176  //TODO Does the order of attendees in the list really matter?
177  //Please delete this comment if you know it's ok, kthx
178  for (; a1 != al1.constEnd() && a2 != al2.constEnd(); ++a1, ++a2) {
179  if (!(*a1 == *a2)) {
180  // qCDebug(KCALCORE_LOG) << "Attendees are different";
181  return false;
182  }
183  }
184 
185  if (!CustomProperties::operator==(i2)) {
186  // qCDebug(KCALCORE_LOG) << "Properties are different";
187  return false;
188  }
189 
190  // Don't compare lastModified, otherwise the operator is not
191  // of much use. We are not comparing for identity, after all.
192  // no need to compare mObserver
193 
194  bool a = ((dtStart() == i2.dtStart()) || (!dtStart().isValid() && !i2.dtStart().isValid()));
195  bool b = organizer() == i2.organizer();
196  bool c = uid() == i2.uid();
197  bool d = allDay() == i2.allDay();
198  bool e = duration() == i2.duration();
199  bool f = hasDuration() == i2.hasDuration();
200  bool g = url() == i2.url();
201 
202  //qCDebug(KCALCORE_LOG) << a << b << c << d << e << f << g;
203  return a && b && c && d && e && f && g;
204 }
205 
207 {
208  Q_UNUSED(v);
209  Q_UNUSED(incidence);
210  return false;
211 }
212 
214 {
215  if (d->mUid != uid) {
216  update();
217  d->mUid = uid;
218  d->mDirtyFields.insert(FieldUid);
219  updated();
220  }
221 }
222 
224 {
225  return d->mUid;
226 }
227 
229 {
230  // DON'T! updated() because we call this from
231  // Calendar::updateEvent().
232 
233  d->mDirtyFields.insert(FieldLastModified);
234 
235  // Convert to UTC and remove milliseconds part.
236  QDateTime current = lm.toUTC();
237  QTime t = current.time();
238  t.setHMS(t.hour(), t.minute(), t.second(), 0);
239  current.setTime(t);
240 
241  d->mLastModified = current;
242 }
243 
245 {
246  return d->mLastModified;
247 }
248 
250 {
251  update();
252  // we don't check for readonly here, because it is
253  // possible that by setting the organizer we are changing
254  // the event's readonly status...
255  d->mOrganizer = organizer;
256 
257  d->mDirtyFields.insert(FieldOrganizer);
258 
259  updated();
260 }
261 
263 {
264  QString mail(o);
265  if (mail.startsWith(QLatin1String("MAILTO:"), Qt::CaseInsensitive)) {
266  mail.remove(0, 7);
267  }
268 
269  // split the string into full name plus email.
270  const Person organizer = Person::fromFullName(mail);
271  setOrganizer(organizer);
272 }
273 
275 {
276  return d->mOrganizer;
277 }
278 
279 void IncidenceBase::setReadOnly(bool readOnly)
280 {
281  mReadOnly = readOnly;
282 }
283 
285 {
286  return mReadOnly;
287 }
288 
290 {
291 // if ( mReadOnly ) return;
292 
293  if (!dtStart.isValid() && type() != IncidenceBase::TypeTodo) {
294  qCWarning(KCALCORE_LOG) << "Invalid dtStart";
295  }
296 
297  if (d->mDtStart != dtStart) {
298  update();
299  d->mDtStart = dtStart;
300  d->mDirtyFields.insert(FieldDtStart);
301  updated();
302  }
303 }
304 
306 {
307  return d->mDtStart;
308 }
309 
310 bool IncidenceBase::allDay() const
311 {
312  return d->mAllDay;
313 }
314 
316 {
317  if (mReadOnly || f == d->mAllDay) {
318  return;
319  }
320  update();
321  d->mAllDay = f;
322  if (d->mDtStart.isValid()) {
323  d->mDirtyFields.insert(FieldDtStart);
324  }
325  updated();
326 }
327 
328 void IncidenceBase::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
329 {
330  update();
331  d->mDtStart = d->mDtStart.toTimeZone(oldZone);
332  d->mDtStart.setTimeZone(newZone);
333  d->mDirtyFields.insert(FieldDtStart);
334  d->mDirtyFields.insert(FieldDtEnd);
335  updated();
336 }
337 
338 void IncidenceBase::addComment(const QString &comment)
339 {
340  d->mComments += comment;
341 }
342 
344 {
345  bool found = false;
347 
348  for (i = d->mComments.begin(); !found && i != d->mComments.end(); ++i) {
349  if ((*i) == comment) {
350  found = true;
351  d->mComments.erase(i);
352  }
353  }
354 
355  if (found) {
356  d->mDirtyFields.insert(FieldComment);
357  }
358 
359  return found;
360 }
361 
363 {
364  d->mDirtyFields.insert(FieldComment);
365  d->mComments.clear();
366 }
367 
369 {
370  return d->mComments;
371 }
372 
373 void IncidenceBase::addContact(const QString &contact)
374 {
375  if (!contact.isEmpty()) {
376  d->mContacts += contact;
377  d->mDirtyFields.insert(FieldContact);
378  }
379 }
380 
382 {
383  bool found = false;
385 
386  for (i = d->mContacts.begin(); !found && i != d->mContacts.end(); ++i) {
387  if ((*i) == contact) {
388  found = true;
389  d->mContacts.erase(i);
390  }
391  }
392 
393  if (found) {
394  d->mDirtyFields.insert(FieldContact);
395  }
396 
397  return found;
398 }
399 
401 {
402  d->mDirtyFields.insert(FieldContact);
403  d->mContacts.clear();
404 }
405 
407 {
408  return d->mContacts;
409 }
410 
411 void IncidenceBase::addAttendee(const Attendee &a, bool doupdate)
412 {
413  if (a.isNull() || mReadOnly) {
414  return;
415  }
416  Q_ASSERT(!a.uid().isEmpty());
417 
418  if (doupdate) {
419  update();
420  }
421 
422  d->mAttendees.append(a);
423  if (doupdate) {
424  d->mDirtyFields.insert(FieldAttendees);
425  updated();
426  }
427 }
428 
430 {
431  return d->mAttendees;
432 }
433 
435 {
436  return d->mAttendees.count();
437 }
438 
440 {
441  if (mReadOnly) {
442  return;
443  }
444 
445  if (doUpdate) {
446  update();
447  }
448 
449  // don't simply assign, we need the logic in addAttendee here too
450  clearAttendees();
451  d->mAttendees.reserve(attendees.size());
452  for (const auto &a : attendees) {
453  addAttendee(a, false);
454  }
455 
456  if (doUpdate) {
457  d->mDirtyFields.insert(FieldAttendees);
458  updated();
459  }
460 }
461 
463 {
464  if (mReadOnly) {
465  return;
466  }
467  d->mDirtyFields.insert(FieldAttendees);
468  d->mAttendees.clear();
469 }
470 
472 {
474  for (it = d->mAttendees.constBegin(); it != d->mAttendees.constEnd(); ++it) {
475  if ((*it).email() == email) {
476  return *it;
477  }
478  }
479 
480  return {};
481 }
482 
483 Attendee IncidenceBase::attendeeByMails(const QStringList &emails, const QString &email) const
484 {
485  QStringList mails = emails;
486  if (!email.isEmpty()) {
487  mails.append(email);
488  }
489 
491  for (itA = d->mAttendees.constBegin(); itA != d->mAttendees.constEnd(); ++itA) {
492  for (QStringList::const_iterator it = mails.constBegin(); it != mails.constEnd(); ++it) {
493  if ((*itA).email() == (*it)) {
494  return *itA;
495  }
496  }
497  }
498 
499  return {};
500 }
501 
503 {
505  for (it = d->mAttendees.constBegin(); it != d->mAttendees.constEnd(); ++it) {
506  if ((*it).uid() == uid) {
507  return *it;
508  }
509  }
510 
511  return {};
512 }
513 
515 {
516  update();
517  d->mDuration = duration;
518  setHasDuration(true);
519  d->mDirtyFields.insert(FieldDuration);
520  updated();
521 }
522 
524 {
525  return d->mDuration;
526 }
527 
529 {
530  d->mHasDuration = hasDuration;
531 }
532 
534 {
535  return d->mHasDuration;
536 }
537 
539 {
540  d->mDirtyFields.insert(FieldUrl);
541  d->mUrl = url;
542 }
543 
545 {
546  return d->mUrl;
547 }
548 
550 {
551  if (observer && !d->mObservers.contains(observer)) {
552  d->mObservers.append(observer);
553  }
554 }
555 
557 {
558  d->mObservers.removeAll(observer);
559 }
560 
562 {
563  if (!d->mUpdateGroupLevel) {
564  d->mUpdatedPending = true;
565  const auto rid = recurrenceId();
566  for (IncidenceObserver *o : qAsConst(d->mObservers)) {
567  o->incidenceUpdate(uid(), rid);
568  }
569  }
570 }
571 
573 {
574  if (d->mUpdateGroupLevel) {
575  d->mUpdatedPending = true;
576  } else {
577  const auto rid = recurrenceId();
578  for (IncidenceObserver *o : qAsConst(d->mObservers)) {
579  o->incidenceUpdated(uid(), rid);
580  }
581  }
582 }
583 
585 {
586  update();
587  ++d->mUpdateGroupLevel;
588 }
589 
591 {
592  if (d->mUpdateGroupLevel > 0) {
593  if (--d->mUpdateGroupLevel == 0 && d->mUpdatedPending) {
594  d->mUpdatedPending = false;
595  updated();
596  }
597  }
598 }
599 
601 {
602  update();
603 }
604 
606 {
607  updated();
608 }
609 
611 {
612  return QDateTime();
613 }
614 
616 {
617  d->mDirtyFields.clear();
618 }
619 
621 {
622  return d->mDirtyFields;
623 }
624 
626 {
627  d->mDirtyFields.insert(field);
628 }
629 
631 {
632  return QUrl(QStringLiteral("urn:x-ical:") + uid());
633 }
634 
636 {
637  d->mDirtyFields = dirtyFields;
638 }
639 
641 {
642  Q_UNUSED(out);
643 }
644 
646 {
647  Q_UNUSED(in);
648 }
649 
652 {
653  return KCALCORE_MAGIC_NUMBER;
654 }
655 
657 {
658  if (!i) {
659  return out;
660  }
661 
662  out << static_cast<quint32>(KCALCORE_MAGIC_NUMBER); // Magic number to identify KCalendarCore data
663  out << static_cast<quint32>(KCALCORE_SERIALIZATION_VERSION);
664  out << static_cast<qint32>(i->type());
665 
666  out << *(static_cast<CustomProperties *>(i.data()));
667  serializeQDateTimeAsKDateTime(out, i->d->mLastModified);
668  serializeQDateTimeAsKDateTime(out, i->d->mDtStart);
669  out << i->organizer() << i->d->mUid << i->d->mDuration
670  << i->d->mAllDay << i->d->mHasDuration << i->d->mComments << i->d->mContacts
671  << i->d->mAttendees.count() << i->d->mUrl;
672 
673  for (const Attendee &attendee : qAsConst(i->d->mAttendees)) {
674  out << attendee;
675  }
676 
677  // Serialize the sub-class data.
678  i->serialize(out);
679 
680  return out;
681 }
682 
684 {
685  if (!i) {
686  return in;
687  }
688 
689  qint32 attendeeCount, type;
690  quint32 magic, version;
691 
692  in >> magic;
693 
694  if (magic != KCALCORE_MAGIC_NUMBER) {
695  qCWarning(KCALCORE_LOG) << "Invalid magic on serialized data";
696  return in;
697  }
698 
699  in >> version;
700 
701  if (version > KCALCORE_MAGIC_NUMBER) {
702  qCWarning(KCALCORE_LOG) << "Invalid version on serialized data";
703  return in;
704  }
705 
706  in >> type;
707 
708  in >> *(static_cast<CustomProperties *>(i.data()));
709  deserializeKDateTimeAsQDateTime(in, i->d->mLastModified);
710  deserializeKDateTimeAsQDateTime(in, i->d->mDtStart);
711  in >> i->d->mOrganizer >> i->d->mUid >> i->d->mDuration
712  >> i->d->mAllDay >> i->d->mHasDuration >> i->d->mComments >> i->d->mContacts >> attendeeCount
713  >> i->d->mUrl;
714 
715  i->d->mAttendees.clear();
716  i->d->mAttendees.reserve(attendeeCount);
717  for (int it = 0; it < attendeeCount; it++) {
718  Attendee attendee;
719  in >> attendee;
720  i->d->mAttendees.append(attendee);
721  }
722 
723  // Deserialize the sub-class data.
724  i->deserialize(in);
725 
726  return in;
727 }
728 
730 {
731 }
732 
733 QVariantList IncidenceBase::attendeesVariant() const
734 {
735  QVariantList l;
736  l.reserve(d->mAttendees.size());
737  std::transform(d->mAttendees.begin(), d->mAttendees.end(), std::back_inserter(l), [](const Attendee &a) { return QVariant::fromValue(a); });
738  return l;
739 }
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
Alarm serializer.
Definition: alarm.cpp:825
int minute() const const
virtual void serialize(QDataStream &out) const
Sub-type specific serialization.
void setDirtyFields(const QSet< IncidenceBase::Field > &)
Sets which fields are dirty.
void endUpdates()
Call this when a group of updates is complete, to notify observers that the instance has changed...
QDateTime toUTC() const const
virtual IncidenceBase & assign(const IncidenceBase &other)
Provides polymorfic assignment.
virtual void setLastModified(const QDateTime &lm)
Sets the time the incidence was last modified to lm.
void update()
Call this to notify the observers after the IncidenceBase object will be changed. ...
A class to manage custom calendar properties.
This class provides the interface for a visitor of calendar components.
Definition: visitor.h:31
bool hasDuration() const
Returns true if the incidence has a duration; false otherwise.
void clearComments()
Deletes all incidence comments.
bool setHMS(int h, int m, int s, int ms)
virtual QDateTime recurrenceId() const
Returns the incidence recurrenceId.
QUrl uri() const
Returns the uri for the incidence, of form urn:x-ical:<uid>
Represents a person, by name and email address.
Definition: person.h:38
Represents information related to an attendee of an Calendar Incidence, typically a meeting or task (...
Definition: attendee.h:45
static QString createUniqueId()
Creates a unique id string.
Definition: calformat.cpp:105
QVector::const_iterator constEnd() const const
Something changed. Always set when you use the assignment operator.
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
Alarm deserializer.
Definition: alarm.cpp:849
virtual ~IncidenceObserver()
Destroys the IncidenceObserver.
void setTime(const QTime &time)
void customPropertyUpdate() override
static Person fromFullName(const QString &fullName)
Constructs a person with name and email address taken from fullName.
Definition: person.cpp:363
~IncidenceBase() override
Destroys the IncidenceBase.
Field
The different types of incidence fields.
void addContact(const QString &contact)
Adds a contact to thieincidence.
QTime time() const const
void setUrl(const QUrl &url)
Sets the incidences url.
void setOrganizer(const Person &organizer)
Sets the organizer for the incidence.
virtual bool equals(const IncidenceBase &incidenceBase) const
Provides polymorfic comparison for equality.
QString & remove(int position, int n)
typedef ConstIterator
bool operator!=(const IncidenceBase &ib) const
Compares this with IncidenceBase ib for inequality.
T * data() const const
This file is part of the API for handling calendar data and defines the CalFormat abstract base class...
int second() const const
Attendee attendeeByMails(const QStringList &emails, const QString &email=QString()) const
Returns the first incidence attendee with one of the specified email addresses.
virtual void setDuration(const Duration &duration)
Sets the incidence duration.
An abstract class that provides a common base for all calendar incidence classes. ...
Definition: incidencebase.h:96
virtual void setReadOnly(bool readOnly)
Sets readonly status.
IncidenceBase & operator=(const IncidenceBase &other)
Assignment operator.
QStringList comments() const
Returns all incidence comments as a list of strings.
Represents a span of time measured in seconds or days.
Definition: duration.h:44
void clearAttendees()
Removes all attendees from the incidence.
void append(const T &value)
QString & insert(int position, QChar ch)
virtual void setDtStart(const QDateTime &dtStart)
Sets the incidence&#39;s starting date/time with a QDateTime.
Duration duration() const
Returns the length of the incidence duration.
int attendeeCount() const
Returns the number of incidence attendees.
QUrl url() const
Returns the url.
void setHasDuration(bool hasDuration)
Sets if the incidence has a duration.
void addAttendee(const Attendee &attendee, bool doUpdate=true)
Add Attendee to this incidence.
CaseInsensitive
bool isEmpty() const const
void setAttendees(const Attendee::List &attendees, bool doUpdate=true)
Set the attendees of this incidence.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
virtual void shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
Shift the times of the incidence so that they appear at the same clock time as before but in a new ti...
void startUpdates()
Call this when a group of updates is going to be made.
Field representing the UID component.
typedef Iterator
virtual bool accept(Visitor &v, const IncidenceBase::Ptr &incidence)
Accept IncidenceVisitor.
void setUid(const QString &uid)
Sets the unique id for the incidence to uid.
bool operator==(const IncidenceBase &ib) const
Compares this with IncidenceBase ib for equality.
QSet< IncidenceBase::Field > dirtyFields() const
Returns a QSet with all Fields that were changed since the incidence was created or resetDirtyFields(...
virtual IncidenceType type() const =0
Returns the incidence type.
Field representing the DURATION component.
Field representing the ATTENDEE component.
void addComment(const QString &comment)
Adds a comment to the incidence.
Attendee attendeeByUid(const QString &uid) const
Returns the incidence attendee with the specified attendee UID.
static quint32 magicSerializationIdentifier()
Constant that identifies KCalendarCore data in a binary stream.
int hour() const const
Field representing the DTSTART component.
Field representing the X-KDE-LIBKCAL-ID component.
Field representing the CONTACT component.
void updated()
Call this to notify the observers after the IncidenceBase object has changed.
void customPropertyUpdated() override
QVariant fromValue(const T &value)
Person organizer() const
Returns the Person associated with this incidence.
QCA_EXPORT void init()
IncidenceBase()
Constructs an empty IncidenceBase.
bool removeComment(const QString &comment)
Removes a comment from the incidence.
bool isValid() const const
Field representing the COMMENT component.
QVector::const_iterator constBegin() const const
void setFieldDirty(IncidenceBase::Field field)
Marks Field field as dirty.
This file is part of the API for handling calendar data and defines the IncidenceBase class...
bool isReadOnly() const
Returns true the object is read-only; false otherwise.
void clearContacts()
Deletes all incidence contacts.
bool removeContact(const QString &contact)
Removes a contact from the incidence.
bool allDay() const
Returns true or false depending on whether the incidence is all-day.
QDateTime lastModified() const
Returns the time the incidence was last modified.
QStringList contacts() const
Returns all incidence contacts as a list of strings.
Attendee attendeeByMail(const QString &email) const
Returns the attendee with the specified email address.
void resetDirtyFields()
Resets dirty fields.
virtual void setAllDay(bool allDay)
Sets whether the incidence is all-day, i.e.
virtual QDateTime dtStart() const
Returns an incidence&#39;s starting date/time as a QDateTime.
virtual void deserialize(QDataStream &in)
Sub-type specific deserialization.
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
Field representing the SEQUENCE component.
int size() const const
Field representing the DTEND component.
void unRegisterObserver(IncidenceObserver *observer)
Unregister observer.
Attendee::List attendees() const
Returns a list of incidence attendees.
CustomProperties & operator=(const CustomProperties &other)
Assignment operator.
Namespace for all KCalendarCore types.
Definition: alarm.h:36
QString uid() const
Returns the unique id (uid) for the incidence.
void registerObserver(IncidenceObserver *observer)
Register observer.
bool mReadOnly
Identifies a read-only incidence.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jul 2 2020 22:49:16 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.