• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KCal Library

incidenceformatter.cpp

Go to the documentation of this file.
00001 /*
00002   This file is part of the kcal library.
00003 
00004   Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00005   Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006   Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
00007 
00008   This library is free software; you can redistribute it and/or
00009   modify it under the terms of the GNU Library General Public
00010   License as published by the Free Software Foundation; either
00011   version 2 of the License, or (at your option) any later version.
00012 
00013   This library is distributed in the hope that it will be useful,
00014   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016   Library General Public License for more details.
00017 
00018   You should have received a copy of the GNU Library General Public License
00019   along with this library; see the file COPYING.LIB.  If not, write to
00020   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021   Boston, MA 02110-1301, USA.
00022 */
00035 #include "incidenceformatter.h"
00036 #include "attachment.h"
00037 #include "event.h"
00038 #include "todo.h"
00039 #include "journal.h"
00040 #include "calendar.h"
00041 #include "calendarlocal.h"
00042 #include "icalformat.h"
00043 #include "freebusy.h"
00044 #include "calendarresources.h"
00045 
00046 #include "kpimutils/email.h"
00047 #include "kabc/phonenumber.h"
00048 #include "kabc/vcardconverter.h"
00049 #include "kabc/stdaddressbook.h"
00050 
00051 #include <kdatetime.h>
00052 #include <kiconloader.h>
00053 #include <klocale.h>
00054 #include <kcalendarsystem.h>
00055 #include <ksystemtimezone.h>
00056 
00057 #include <QtCore/QBuffer>
00058 #include <QtCore/QList>
00059 #include <QtGui/QTextDocument>
00060 #include <QtGui/QApplication>
00061 
00062 #include <time.h>
00063 
00064 using namespace KCal;
00065 
00066 /*******************************************************************
00067  *  Helper functions for the extensive display (event viewer)
00068  *******************************************************************/
00069 
00070 //@cond PRIVATE
00071 static QString eventViewerAddLink( const QString &ref, const QString &text,
00072                                    bool newline = true )
00073 {
00074   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00075   if ( newline ) {
00076     tmpStr += '\n';
00077   }
00078   return tmpStr;
00079 }
00080 
00081 static QString eventViewerAddTag( const QString &tag, const QString &text )
00082 {
00083   int numLineBreaks = text.count( "\n" );
00084   QString str = '<' + tag + '>';
00085   QString tmpText = text;
00086   QString tmpStr = str;
00087   if( numLineBreaks >= 0 ) {
00088     if ( numLineBreaks > 0 ) {
00089       int pos = 0;
00090       QString tmp;
00091       for ( int i = 0; i <= numLineBreaks; ++i ) {
00092         pos = tmpText.indexOf( "\n" );
00093         tmp = tmpText.left( pos );
00094         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00095         tmpStr += tmp + "<br>";
00096       }
00097     } else {
00098       tmpStr += tmpText;
00099     }
00100   }
00101   tmpStr += "</" + tag + '>';
00102   return tmpStr;
00103 }
00104 
00105 static QString eventViewerFormatCategories( Incidence *event )
00106 {
00107   QString tmpStr;
00108   if ( !event->categoriesStr().isEmpty() ) {
00109     if ( event->categories().count() == 1 ) {
00110       tmpStr = eventViewerAddTag( "h3", i18n( "Category" ) );
00111     } else {
00112       tmpStr = eventViewerAddTag( "h3", i18n( "Categories" ) );
00113     }
00114     tmpStr += eventViewerAddTag( "p", event->categoriesStr() );
00115   }
00116   return tmpStr;
00117 }
00118 
00119 static QString linkPerson( const QString &email, QString name, QString uid,
00120                            const QString &iconPath )
00121 {
00122   // Make the search, if there is an email address to search on,
00123   // and either name or uid is missing
00124   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00125     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00126     KABC::Addressee::List addressList = add_book->findByEmail( email );
00127     KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() );
00128     if ( !o.isEmpty() && addressList.size() < 2 ) {
00129       if ( name.isEmpty() ) {
00130         // No name set, so use the one from the addressbook
00131         name = o.formattedName();
00132       }
00133       uid = o.uid();
00134     } else {
00135       // Email not found in the addressbook. Don't make a link
00136       uid.clear();
00137     }
00138   }
00139 
00140   // Show the attendee
00141   QString tmpString = "<li>";
00142   if ( !uid.isEmpty() ) {
00143     // There is a UID, so make a link to the addressbook
00144     if ( name.isEmpty() ) {
00145       // Use the email address for text
00146       tmpString += eventViewerAddLink( "uid:" + uid, email );
00147     } else {
00148       tmpString += eventViewerAddLink( "uid:" + uid, name );
00149     }
00150   } else {
00151     // No UID, just show some text
00152     tmpString += ( name.isEmpty() ? email : name );
00153   }
00154   tmpString += '\n';
00155 
00156   // Make the mailto link
00157   if ( !email.isEmpty() && !iconPath.isNull() ) {
00158     KUrl mailto;
00159     mailto.setProtocol( "mailto" );
00160     mailto.setPath( email );
00161     tmpString += eventViewerAddLink( mailto.url(), "<img src=\"" + iconPath + "\">" );
00162   }
00163   tmpString += "</li>\n";
00164 
00165   return tmpString;
00166 }
00167 
00168 static QString eventViewerFormatAttendees( Incidence *event )
00169 {
00170   QString tmpStr;
00171   Attendee::List attendees = event->attendees();
00172   if ( attendees.count() ) {
00173     KIconLoader *iconLoader = KIconLoader::global();
00174     const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00175 
00176     // Add organizer link
00177     tmpStr += eventViewerAddTag( "h4", i18n( "Organizer" ) );
00178     tmpStr += "<ul>";
00179     tmpStr += linkPerson( event->organizer().email(), event->organizer().name(),
00180                           QString(), iconPath );
00181     tmpStr += "</ul>";
00182 
00183     // Add attendees links
00184     tmpStr += eventViewerAddTag( "h4", i18n( "Attendees" ) );
00185     tmpStr += "<ul>";
00186     Attendee::List::ConstIterator it;
00187     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00188       Attendee *a = *it;
00189       tmpStr += linkPerson( a->email(), a->name(), a->uid(), iconPath );
00190       if ( !a->delegator().isEmpty() ) {
00191         tmpStr += i18n( " (delegated by %1)", a->delegator() );
00192       }
00193       if ( !a->delegate().isEmpty() ) {
00194         tmpStr += i18n( " (delegated to %1)", a->delegate() );
00195       }
00196     }
00197     tmpStr += "</ul>";
00198   }
00199   return tmpStr;
00200 }
00201 
00202 static QString eventViewerFormatAttachments( Incidence *i )
00203 {
00204   QString tmpStr;
00205   Attachment::List as = i->attachments();
00206   if ( as.count() > 0 ) {
00207     Attachment::List::ConstIterator it;
00208     for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
00209       if ( (*it)->isUri() ) {
00210         tmpStr += eventViewerAddLink( (*it)->uri(), (*it)->label() );
00211         tmpStr += "<br>";
00212       }
00213     }
00214   }
00215   return tmpStr;
00216 }
00217 
00218 /*
00219   FIXME:This function depends of kaddressbook. Is necessary a new
00220   type of event?
00221 */
00222 static QString eventViewerFormatBirthday( Event *event )
00223 {
00224   if ( !event ) {
00225     return QString();
00226   }
00227   if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" ) {
00228     return QString();
00229   }
00230 
00231   QString uid_1 = event->customProperty( "KABC", "UID-1" );
00232   QString name_1 = event->customProperty( "KABC", "NAME-1" );
00233   QString email_1= event->customProperty( "KABC", "EMAIL-1" );
00234 
00235   KIconLoader *iconLoader = KIconLoader::global();
00236   const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00237   //TODO: add a tart icon
00238   QString tmpString = "<ul>";
00239   tmpString += linkPerson( email_1, name_1, uid_1, iconPath );
00240 
00241   if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00242     QString uid_2 = event->customProperty( "KABC", "UID-2" );
00243     QString name_2 = event->customProperty( "KABC", "NAME-2" );
00244     QString email_2= event->customProperty( "KABC", "EMAIL-2" );
00245     tmpString += linkPerson( email_2, name_2, uid_2, iconPath );
00246   }
00247 
00248   tmpString += "</ul>";
00249   return tmpString;
00250 }
00251 
00252 static QString eventViewerFormatHeader( Incidence *incidence )
00253 {
00254   QString tmpStr = "<table><tr>";
00255 
00256   // show icons
00257   KIconLoader *iconLoader = KIconLoader::global();
00258   tmpStr += "<td>";
00259 
00260   // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't
00261   // need downcasting.
00262 
00263   if ( incidence->type() == "Todo" ) {
00264     tmpStr += "<img src=\"";
00265     Todo *todo = static_cast<Todo *>( incidence );
00266     if ( !todo->isCompleted() ) {
00267       tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small );
00268     } else {
00269       tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small );
00270     }
00271     tmpStr += "\">";
00272   }
00273 
00274   if ( incidence->type() == "Event" ) {
00275     tmpStr += "<img src=\"" +
00276               iconLoader->iconPath( "view-calendar-day", KIconLoader::Small ) +
00277               "\">";
00278   }
00279 
00280   if ( incidence->type() == "Journal" ) {
00281     tmpStr += "<img src=\"" +
00282               iconLoader->iconPath( "view-pim-journal", KIconLoader::Small ) +
00283               "\">";
00284   }
00285 
00286   if ( incidence->isAlarmEnabled() ) {
00287     tmpStr += "<img src=\"" +
00288               iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
00289               "\">";
00290   }
00291   if ( incidence->recurs() ) {
00292     tmpStr += "<img src=\"" +
00293               iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
00294               "\">";
00295   }
00296   if ( incidence->isReadOnly() ) {
00297     tmpStr += "<img src=\"" +
00298               iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
00299               "\">";
00300   }
00301   tmpStr += "</td>";
00302 
00303   tmpStr += "<td>" +
00304             eventViewerAddTag( "h2", incidence->richSummary() ) +
00305             "</td>";
00306   tmpStr += "</tr></table>";
00307 
00308   return tmpStr;
00309 }
00310 
00311 static QString eventViewerFormatEvent( Event *event, KDateTime::Spec spec )
00312 {
00313   if ( !event ) {
00314     return QString();
00315   }
00316 
00317   QString tmpStr = eventViewerFormatHeader( event );
00318 
00319   tmpStr += "<table>";
00320   if ( !event->location().isEmpty() ) {
00321     tmpStr += "<tr>";
00322     tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00323     tmpStr += "<td>" + event->richLocation() + "</td>";
00324     tmpStr += "</tr>";
00325   }
00326 
00327   tmpStr += "<tr>";
00328   if ( event->allDay() ) {
00329     if ( event->isMultiDay() ) {
00330       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00331       tmpStr += "<td>" +
00332                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00333                        IncidenceFormatter::dateToString( event->dtStart(), true, spec ),
00334                        IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) +
00335                 "</td>";
00336     } else {
00337       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00338       tmpStr += "<td>" +
00339                 i18nc( "date as string","%1",
00340                        IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) +
00341                 "</td>";
00342     }
00343   } else {
00344     if ( event->isMultiDay() ) {
00345       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00346       tmpStr += "<td>" +
00347                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00348                        IncidenceFormatter::dateToString( event->dtStart(), true, spec ),
00349                        IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) +
00350                 "</td>";
00351     } else {
00352       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00353       if ( event->hasEndDate() && event->dtStart() != event->dtEnd() ) {
00354         tmpStr += "<td>" +
00355                   i18nc( "<beginTime> - <endTime>","%1 - %2",
00356                          IncidenceFormatter::timeToString( event->dtStart(), true, spec ),
00357                          IncidenceFormatter::timeToString( event->dtEnd(), true, spec ) ) +
00358                   "</td>";
00359       } else {
00360         tmpStr += "<td>" +
00361                   IncidenceFormatter::timeToString( event->dtStart(), true, spec ) +
00362                   "</td>";
00363       }
00364       tmpStr += "</tr><tr>";
00365       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00366       tmpStr += "<td>" +
00367                 i18nc( "date as string","%1",
00368                        IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) +
00369                 "</td>";
00370     }
00371   }
00372   tmpStr += "</tr>";
00373 
00374   if ( event->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00375     tmpStr += "<tr>";
00376     tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>";
00377     tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>";
00378     tmpStr += "</tr>";
00379     tmpStr += "</table>";
00380     return tmpStr;
00381   }
00382 
00383   if ( !event->description().isEmpty() ) {
00384     tmpStr += "<tr>";
00385     tmpStr += "<td></td>";
00386     tmpStr += "<td>" + eventViewerAddTag( "p", event->richDescription() ) + "</td>";
00387     tmpStr += "</tr>";
00388   }
00389 
00390   if ( event->categories().count() > 0 ) {
00391     tmpStr += "<tr>";
00392     tmpStr += "<td align=\"right\"><b>";
00393     tmpStr += i18np( "1&nbsp;category", "%1&nbsp;categories", event->categories().count() ) +
00394               "</b></td>";
00395     tmpStr += "<td>" + event->categoriesStr() + "</td>";
00396     tmpStr += "</tr>";
00397   }
00398 
00399   if ( event->recurs() ) {
00400     KDateTime dt = event->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00401     tmpStr += "<tr>";
00402     tmpStr += "<td align=\"right\"><b>" + i18n( "Next Occurrence" )+ "</b></td>";
00403     tmpStr += "<td>" +
00404               ( dt.isValid() ?
00405                 KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) :
00406                 i18nc( "no date", "none" ) ) +
00407               "</td>";
00408     tmpStr += "</tr>";
00409   }
00410 
00411   tmpStr += "<tr><td colspan=\"2\">";
00412   tmpStr += eventViewerFormatAttendees( event );
00413   tmpStr += "</td></tr>";
00414 
00415   int attachmentCount = event->attachments().count();
00416   if ( attachmentCount > 0 ) {
00417     tmpStr += "<tr>";
00418     tmpStr += "<td align=\"right\"><b>";
00419     tmpStr += i18np( "1&nbsp;attachment", "%1&nbsp;attachments", attachmentCount )+ "</b></td>";
00420     tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>";
00421     tmpStr += "</tr>";
00422   }
00423   KDateTime kdt = event->created().toTimeSpec( spec );
00424   tmpStr += "</table>";
00425   tmpStr += "<p><em>" +
00426             i18n( "Creation date: %1", KGlobal::locale()->formatDateTime(
00427                     kdt.dateTime(),
00428                     KLocale::ShortDate ) ) + "</em>";
00429   return tmpStr;
00430 }
00431 
00432 static QString eventViewerFormatTodo( Todo *todo, KDateTime::Spec spec )
00433 {
00434   if ( !todo ) {
00435     return QString();
00436   }
00437 
00438   QString tmpStr = eventViewerFormatHeader( todo );
00439 
00440   if ( !todo->location().isEmpty() ) {
00441     tmpStr += eventViewerAddTag( "b", i18n(" Location: %1", todo->richLocation() ) );
00442     tmpStr += "<br>";
00443   }
00444 
00445   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00446     tmpStr += i18n( "<b>Due on:</b> %1",
00447                     IncidenceFormatter::dateTimeToString( todo->dtDue(),
00448                                                           todo->allDay(),
00449                                                           true, spec ) );
00450   }
00451 
00452   if ( !todo->description().isEmpty() ) {
00453     tmpStr += eventViewerAddTag( "p", todo->richDescription() );
00454   }
00455 
00456   tmpStr += eventViewerFormatCategories( todo );
00457 
00458   if ( todo->priority() > 0 ) {
00459     tmpStr += i18n( "<p><b>Priority:</b> %1</p>", todo->priority() );
00460   } else {
00461     tmpStr += i18n( "<p><b>Priority:</b> %1</p>", i18n( "Unspecified" ) );
00462   }
00463 
00464   tmpStr += i18n( "<p><i>%1 % completed</i></p>", todo->percentComplete() );
00465 
00466   if ( todo->recurs() ) {
00467     KDateTime dt = todo->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00468     tmpStr += eventViewerAddTag( "p", "<em>" +
00469       i18n( "This is a recurring to-do. The next occurrence will be on %1.",
00470             KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) ) + "</em>" );
00471   }
00472   tmpStr += eventViewerFormatAttendees( todo );
00473   tmpStr += eventViewerFormatAttachments( todo );
00474 
00475   KDateTime kdt = todo->created().toTimeSpec( spec );
00476   tmpStr += "<p><em>" + i18n( "Creation date: %1",
00477     KGlobal::locale()->formatDateTime( kdt.dateTime(), KLocale::ShortDate ) ) + "</em>";
00478   return tmpStr;
00479 }
00480 
00481 static QString eventViewerFormatJournal( Journal *journal, KDateTime::Spec spec )
00482 {
00483   if ( !journal ) {
00484     return QString();
00485   }
00486 
00487   QString tmpStr = eventViewerFormatHeader( journal );
00488 
00489   tmpStr += eventViewerAddTag(
00490     "h3", i18n( "Journal for %1", IncidenceFormatter::dateToString( journal->dtStart(), false,
00491                                                                     spec ) ) );
00492   if ( !journal->description().isEmpty() ) {
00493     tmpStr += eventViewerAddTag( "p", journal->richDescription() );
00494   }
00495   return tmpStr;
00496 }
00497 
00498 static QString eventViewerFormatFreeBusy( FreeBusy *fb, KDateTime::Spec spec )
00499 {
00500   Q_UNUSED( spec );
00501 
00502   if ( !fb ) {
00503     return QString();
00504   }
00505 
00506   QString tmpStr(
00507     eventViewerAddTag(
00508       "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) );
00509   tmpStr += eventViewerAddTag(
00510     "h4", i18n( "Busy times in date range %1 - %2:",
00511                 KGlobal::locale()->formatDate( fb->dtStart().date(), KLocale::ShortDate ),
00512                 KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) ) );
00513 
00514   QList<Period> periods = fb->busyPeriods();
00515 
00516   QString text =
00517     eventViewerAddTag( "em",
00518                        eventViewerAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
00519 
00520   QList<Period>::iterator it;
00521   for ( it = periods.begin(); it != periods.end(); ++it ) {
00522     Period per = *it;
00523     if ( per.hasDuration() ) {
00524       int dur = per.duration().asSeconds();
00525       QString cont;
00526       if ( dur >= 3600 ) {
00527         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00528         dur %= 3600;
00529       }
00530       if ( dur >= 60 ) {
00531         cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
00532         dur %= 60;
00533       }
00534       if ( dur > 0 ) {
00535         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00536       }
00537       text += i18nc( "startDate for duration", "%1 for %2",
00538                      KGlobal::locale()->formatDateTime(
00539                        per.start().dateTime(), KLocale::LongDate ), cont );
00540       text += "<br>";
00541     } else {
00542       if ( per.start().date() == per.end().date() ) {
00543         text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00544                        KGlobal::locale()->formatDate( per.start().date() ),
00545                        KGlobal::locale()->formatTime( per.start().time() ),
00546                        KGlobal::locale()->formatTime( per.end().time() ) );
00547       } else {
00548         text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
00549                        KGlobal::locale()->formatDateTime(
00550                          per.start().dateTime(), KLocale::LongDate ),
00551                        KGlobal::locale()->formatDateTime(
00552                          per.end().dateTime(), KLocale::LongDate ) );
00553       }
00554       text += "<br>";
00555     }
00556   }
00557   tmpStr += eventViewerAddTag( "p", text );
00558   return tmpStr;
00559 }
00560 //@endcond
00561 
00562 //@cond PRIVATE
00563 class KCal::IncidenceFormatter::EventViewerVisitor
00564   : public IncidenceBase::Visitor
00565 {
00566   public:
00567     EventViewerVisitor()
00568       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
00569 
00570     bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
00571     {
00572       mSpec = spec;
00573       mResult = "";
00574       return incidence->accept( *this );
00575     }
00576     QString result() const { return mResult; }
00577 
00578   protected:
00579     bool visit( Event *event )
00580     {
00581       mResult = eventViewerFormatEvent( event, mSpec );
00582       return !mResult.isEmpty();
00583     }
00584     bool visit( Todo *todo )
00585     {
00586       mResult = eventViewerFormatTodo( todo, mSpec );
00587       return !mResult.isEmpty();
00588     }
00589     bool visit( Journal *journal )
00590     {
00591       mResult = eventViewerFormatJournal( journal, mSpec );
00592       return !mResult.isEmpty();
00593     }
00594     bool visit( FreeBusy *fb )
00595     {
00596       mResult = eventViewerFormatFreeBusy( fb, mSpec );
00597       return !mResult.isEmpty();
00598     }
00599 
00600   protected:
00601     KDateTime::Spec mSpec;
00602     QString mResult;
00603 };
00604 //@endcond
00605 
00606 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00607 {
00608   return extensiveDisplayStr( incidence, KDateTime::Spec() );
00609 }
00610 
00611 QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence, KDateTime::Spec spec )
00612 {
00613   if ( !incidence ) {
00614     return QString();
00615   }
00616 
00617   EventViewerVisitor v;
00618   if ( v.act( incidence, spec ) ) {
00619     return v.result();
00620   } else {
00621     return QString();
00622   }
00623 }
00624 
00625 /*******************************************************************
00626  *  Helper functions for the body part formatter of kmail
00627  *******************************************************************/
00628 
00629 //TODO: 4.4: remove "meeting" from the invitation strings
00630 
00631 //@cond PRIVATE
00632 static QString string2HTML( const QString &str )
00633 {
00634   return Qt::escape( str );
00635 }
00636 
00637 static QString cleanHtml( const QString &html )
00638 {
00639   QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
00640   rx.indexIn( html );
00641   QString body = rx.cap( 1 );
00642 
00643   return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
00644 }
00645 
00646 static QString eventStartTimeStr( Event *event )
00647 {
00648   QString tmp;
00649   if ( !event->allDay() ) {
00650     tmp =  i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
00651                   IncidenceFormatter::dateToString( event->dtStart(), true,
00652                                                     KSystemTimeZones::local() ),
00653                   IncidenceFormatter::timeToString( event->dtStart(), true,
00654                                                     KSystemTimeZones::local() ) );
00655   } else {
00656     tmp = i18nc( "%1: Start Date", "%1 (all day)",
00657                  IncidenceFormatter::dateToString( event->dtStart(), true,
00658                                                    KSystemTimeZones::local() ) );
00659   }
00660   return tmp;
00661 }
00662 
00663 static QString eventEndTimeStr( Event *event )
00664 {
00665   QString tmp;
00666   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00667     if ( !event->allDay() ) {
00668       tmp =  i18nc( "%1: End Date, %2: End Time", "%1 %2",
00669                     IncidenceFormatter::dateToString( event->dtEnd(), true,
00670                                                       KSystemTimeZones::local() ),
00671                     IncidenceFormatter::timeToString( event->dtEnd(), true,
00672                                                       KSystemTimeZones::local() ) );
00673     } else {
00674       tmp = i18nc( "%1: End Date", "%1 (all day)",
00675                    IncidenceFormatter::dateToString( event->dtEnd(), true,
00676                                                      KSystemTimeZones::local() ) );
00677     }
00678   } else {
00679     tmp = i18n( "Unspecified" );
00680   }
00681   return tmp;
00682 }
00683 
00684 static QString invitationRow( const QString &cell1, const QString &cell2 )
00685 {
00686   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00687 }
00688 
00689 static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode )
00690 {
00691   // if description and comment -> use both
00692   // if description, but no comment -> use the desc as the comment (and no desc)
00693   // if comment, but no description -> use the comment and no description
00694 
00695   QString html;
00696   QString descr;
00697   QStringList comments;
00698 
00699   if ( incidence->comments().isEmpty() ) {
00700     if ( !incidence->description().isEmpty() ) {
00701       // use description as comments
00702       if ( !incidence->descriptionIsRich() ) {
00703         comments << string2HTML( incidence->description() );
00704       } else {
00705         comments << incidence->richDescription();
00706         if ( noHtmlMode ) {
00707           comments[0] = cleanHtml( comments[0] );
00708         }
00709       }
00710     }
00711     //else desc and comments are empty
00712   } else {
00713     // non-empty comments
00714     for ( int i=0; i < incidence->comments().count(); ++i ) {
00715       comments[i] = string2HTML( incidence->comments()[i] );
00716     }
00717     if ( !incidence->description().isEmpty() ) {
00718       // use description too
00719       if ( !incidence->descriptionIsRich() ) {
00720         descr = string2HTML( incidence->description() );
00721       } else {
00722         descr = incidence->richDescription();
00723         if ( noHtmlMode ) {
00724           descr = cleanHtml( descr );
00725         }
00726       }
00727     }
00728   }
00729 
00730   if( !descr.isEmpty() ) {
00731     html += "<p>";
00732     html += "<table border=\"0\" style=\"margin-top:4px;\">";
00733     html += "<tr><td><center>" +
00734             eventViewerAddTag( "u", i18n( "Description:" ) ) +
00735             "</center></td></tr>";
00736     html += "<tr><td>" + descr + "</td></tr>";
00737     html += "</table>";
00738   }
00739 
00740   if ( !comments.isEmpty() ) {
00741     html += "<p>";
00742     html += "<table border=\"0\" style=\"margin-top:4px;\">";
00743     html += "<tr><td><center>" +
00744             eventViewerAddTag( "u", i18n( "Comments:" ) ) +
00745             "</center></td></tr>";
00746     html += "<tr>";
00747     if ( comments.count() > 1 ) {
00748       html += "<td><ul>";
00749       for ( int i=0; i < comments.count(); ++i ) {
00750         html += "<li>" + comments[i] + "</li>";
00751       }
00752       html += "</ul></td>";
00753     } else {
00754       html += "<td>" + comments[0] + "</td>";
00755     }
00756     html += "</tr>";
00757     html += "</table>";
00758   }
00759   return html;
00760 }
00761 
00762 static QString invitationDetailsEvent( Event *event, bool noHtmlMode )
00763 {
00764   // Meeting details are formatted into an HTML table
00765   if ( !event ) {
00766     return QString();
00767   }
00768 
00769   QString sSummary = i18n( "Summary unspecified" );
00770   if ( !event->summary().isEmpty() ) {
00771     if ( !event->summaryIsRich() ) {
00772       sSummary = string2HTML( event->summary() );
00773     } else {
00774       sSummary = event->richSummary();
00775       if ( noHtmlMode ) {
00776         sSummary = cleanHtml( sSummary );
00777       }
00778     }
00779   }
00780 
00781   QString sLocation = i18n( "Location unspecified" );
00782   if ( !event->location().isEmpty() ) {
00783     if ( !event->locationIsRich() ) {
00784       sLocation = string2HTML( event->location() );
00785     } else {
00786       sLocation = event->richLocation();
00787       if ( noHtmlMode ) {
00788         sLocation = cleanHtml( sLocation );
00789       }
00790     }
00791   }
00792 
00793   QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
00794   QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
00795 
00796   // Meeting summary & location rows
00797   html += invitationRow( i18n( "What:" ), sSummary );
00798   html += invitationRow( i18n( "Where:" ), sLocation );
00799 
00800   // Meeting Start Time Row
00801   html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) );
00802 
00803   // Meeting End Time Row
00804   html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) );
00805 
00806   // Meeting Duration Row
00807   if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) {
00808     QString tmp;
00809     QTime sDuration( 0, 0, 0 ), t;
00810     int secs = event->dtStart().secsTo( event->dtEnd() );
00811     t = sDuration.addSecs( secs );
00812     if ( t.hour() > 0 ) {
00813       tmp += i18np( "1 hour ", "%1 hours ", t.hour() );
00814     }
00815     if ( t.minute() > 0 ) {
00816       tmp += i18np( "1 minute ", "%1 minutes ", t.minute() );
00817     }
00818 
00819     html += invitationRow( i18n( "Duration:" ), tmp );
00820   }
00821 
00822   if ( event->recurs() ) {
00823     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
00824   }
00825 
00826   html += "</div>";
00827   html += invitationsDetailsIncidence( event, noHtmlMode );
00828 
00829   return html;
00830 }
00831 
00832 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
00833 {
00834   // To-do details are formatted into an HTML table
00835   if ( !todo ) {
00836     return QString();
00837   }
00838 
00839   QString sSummary = i18n( "Summary unspecified" );
00840   QString sDescr = i18n( "Description unspecified" );
00841   if ( ! todo->summary().isEmpty() ) {
00842     sSummary = todo->richSummary();
00843     if ( noHtmlMode ) {
00844       sSummary = cleanHtml( sSummary );
00845     }
00846   }
00847   if ( ! todo->description().isEmpty() ) {
00848     sDescr = todo->description();
00849     if ( noHtmlMode ) {
00850       sDescr = cleanHtml( sDescr );
00851     }
00852   }
00853   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00854                  "cellpadding=\"1\" cellspacing=\"1\">";
00855   html += invitationRow( i18n( "Summary:" ), sSummary );
00856   html += invitationRow( i18n( "Description:" ), sDescr );
00857   html += "</table>\n";
00858   html += invitationsDetailsIncidence( todo, noHtmlMode );
00859 
00860   return html;
00861 }
00862 
00863 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
00864 {
00865   if ( !journal ) {
00866     return QString();
00867   }
00868 
00869   QString sSummary = i18n( "Summary unspecified" );
00870   QString sDescr = i18n( "Description unspecified" );
00871   if ( ! journal->summary().isEmpty() ) {
00872     sSummary = journal->richSummary();
00873     if ( noHtmlMode ) {
00874       sSummary = cleanHtml( sSummary );
00875     }
00876   }
00877   if ( ! journal->description().isEmpty() ) {
00878     sDescr = journal->richDescription();
00879     if ( noHtmlMode ) {
00880       sDescr = cleanHtml( sDescr );
00881     }
00882   }
00883   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00884                  "cellpadding=\"1\" cellspacing=\"1\">";
00885   html += invitationRow( i18n( "Summary:" ), sSummary );
00886   html += invitationRow( i18n( "Date:" ),
00887                          IncidenceFormatter::dateToString( journal->dtStart(), false,
00888                                                            journal->dtStart().timeSpec() ) );
00889   html += invitationRow( i18n( "Description:" ), sDescr );
00890   html += "</table>\n";
00891   html += invitationsDetailsIncidence( journal, noHtmlMode );
00892 
00893   return html;
00894 }
00895 
00896 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00897 {
00898   if ( !fb ) {
00899     return QString();
00900   }
00901 
00902   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00903                  "cellpadding=\"1\" cellspacing=\"1\">";
00904   html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() );
00905   html += invitationRow( i18n( "Start date:" ),
00906                          IncidenceFormatter::dateToString( fb->dtStart(),
00907                                                            true,
00908                                                            fb->dtStart().timeSpec() ) );
00909   html += invitationRow( i18n( "End date:" ),
00910                          KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) );
00911   html += "<tr><td colspan=2><hr></td></tr>\n";
00912   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00913 
00914   QList<Period> periods = fb->busyPeriods();
00915   QList<Period>::iterator it;
00916   for ( it = periods.begin(); it != periods.end(); ++it ) {
00917     Period per = *it;
00918     if ( per.hasDuration() ) {
00919       int dur = per.duration().asSeconds();
00920       QString cont;
00921       if ( dur >= 3600 ) {
00922         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00923         dur %= 3600;
00924       }
00925       if ( dur >= 60 ) {
00926         cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
00927         dur %= 60;
00928       }
00929       if ( dur > 0 ) {
00930         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00931       }
00932       html += invitationRow(
00933         QString(), i18nc( "startDate for duration", "%1 for %2",
00934                           KGlobal::locale()->formatDateTime(
00935                             per.start().dateTime(), KLocale::LongDate ), cont ) );
00936     } else {
00937       QString cont;
00938       if ( per.start().date() == per.end().date() ) {
00939         cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00940                       KGlobal::locale()->formatDate( per.start().date() ),
00941                       KGlobal::locale()->formatTime( per.start().time() ),
00942                       KGlobal::locale()->formatTime( per.end().time() ) );
00943       } else {
00944         cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
00945                       KGlobal::locale()->formatDateTime(
00946                         per.start().dateTime(), KLocale::LongDate ),
00947                       KGlobal::locale()->formatDateTime(
00948                         per.end().dateTime(), KLocale::LongDate ) );
00949       }
00950 
00951       html += invitationRow( QString(), cont );
00952     }
00953   }
00954 
00955   html += "</table>\n";
00956   return html;
00957 }
00958 
00959 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
00960 {
00961   if ( !msg || !event ) {
00962     return QString();
00963   }
00964 
00965   switch ( msg->method() ) {
00966   case iTIPPublish:
00967     return i18n( "This event has been published" );
00968   case iTIPRequest:
00969     if ( event->revision() > 0 ) {
00970       //TODO: 4.4, remove the h3 tag
00971       return i18n( "<h3>This meeting has been updated</h3>" );
00972     } else {
00973       return i18n( "You have been invited to this meeting" );
00974     }
00975   case iTIPRefresh:
00976     return i18n( "This invitation was refreshed" );
00977   case iTIPCancel:
00978     return i18n( "This meeting has been canceled" );
00979   case iTIPAdd:
00980     return i18n( "Addition to the meeting invitation" );
00981   case iTIPReply:
00982   {
00983     Attendee::List attendees = event->attendees();
00984     if( attendees.count() == 0 ) {
00985       kDebug() << "No attendees in the iCal reply!";
00986       return QString();
00987     }
00988     if ( attendees.count() != 1 ) {
00989       kDebug() << "Warning: attendeecount in the reply should be 1"
00990                << "but is" << attendees.count();
00991     }
00992     Attendee *attendee = *attendees.begin();
00993     QString attendeeName = attendee->name();
00994     if ( attendeeName.isEmpty() ) {
00995       attendeeName = attendee->email();
00996     }
00997     if ( attendeeName.isEmpty() ) {
00998       attendeeName = i18n( "Sender" );
00999     }
01000 
01001     QString delegatorName, dummy;
01002     KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
01003     if ( delegatorName.isEmpty() ) {
01004       delegatorName = attendee->delegator();
01005     }
01006 
01007     switch( attendee->status() ) {
01008     case Attendee::NeedsAction:
01009       return i18n( "%1 indicates this invitation still needs some action", attendeeName );
01010     case Attendee::Accepted:
01011       if ( delegatorName.isEmpty() ) {
01012         return i18n( "%1 accepts this meeting invitation", attendeeName );
01013       }
01014       return i18n( "%1 accepts this meeting invitation on behalf of %2",
01015                    attendeeName, delegatorName );
01016     case Attendee::Tentative:
01017       if ( delegatorName.isEmpty() ) {
01018         return i18n( "%1 tentatively accepts this meeting invitation", attendeeName );
01019       }
01020       return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2",
01021                    attendeeName, delegatorName );
01022     case Attendee::Declined:
01023       if ( delegatorName.isEmpty() ) {
01024         return i18n( "%1 declines this meeting invitation", attendeeName );
01025       }
01026       return i18n( "%1 declines this meeting invitation on behalf of %2",
01027                    attendeeName, delegatorName );
01028     case Attendee::Delegated:
01029     {
01030       QString delegate, dummy;
01031       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01032       if ( delegate.isEmpty() ) {
01033         delegate = attendee->delegate();
01034       }
01035       if ( !delegate.isEmpty() ) {
01036         return i18n( "%1 has delegated this meeting invitation to %2", attendeeName, delegate );
01037       }
01038       return i18n( "%1 has delegated this meeting invitation", attendeeName );
01039     }
01040     case Attendee::Completed:
01041       return i18n( "This meeting invitation is now completed" );
01042     case Attendee::InProcess:
01043       return i18n( "%1 is still processing the invitation", attendeeName );
01044     default:
01045       return i18n( "Unknown response to this meeting invitation" );
01046     }
01047     break;
01048   }
01049   case iTIPCounter:
01050     return i18n( "Sender makes this counter proposal" );
01051   case iTIPDeclineCounter:
01052     return i18n( "Sender declines the counter proposal" );
01053   case iTIPNoMethod:
01054     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01055   }
01056   return QString();
01057 }
01058 
01059 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01060 {
01061   if ( !msg || !todo ) {
01062     return QString();
01063   }
01064 
01065   switch ( msg->method() ) {
01066   case iTIPPublish:
01067     return i18n( "This to-do has been published" );
01068   case iTIPRequest:
01069     if ( todo->revision() > 0 ) {
01070       return i18n( "This to-do has been updated" );
01071     } else {
01072       return i18n( "You have been assigned this to-do" );
01073     }
01074   case iTIPRefresh:
01075     return i18n( "This to-do was refreshed" );
01076   case iTIPCancel:
01077     return i18n( "This to-do was canceled" );
01078   case iTIPAdd:
01079     return i18n( "Addition to the to-do" );
01080   case iTIPReply:
01081   {
01082     Attendee::List attendees = todo->attendees();
01083     if ( attendees.count() == 0 ) {
01084       kDebug() << "No attendees in the iCal reply!";
01085       return QString();
01086     }
01087     if ( attendees.count() != 1 ) {
01088       kDebug() << "Warning: attendeecount in the reply should be 1"
01089                << "but is" << attendees.count();
01090     }
01091     Attendee *attendee = *attendees.begin();
01092     switch( attendee->status() ) {
01093     case Attendee::NeedsAction:
01094       return i18n( "Sender indicates this to-do assignment still needs some action" );
01095     case Attendee::Accepted:
01096       return i18n( "Sender accepts this to-do" );
01097     case Attendee::Tentative:
01098       return i18n( "Sender tentatively accepts this to-do" );
01099     case Attendee::Declined:
01100       return i18n( "Sender declines this to-do" );
01101     case Attendee::Delegated:
01102     {
01103       QString delegate, dummy;
01104       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01105       if ( delegate.isEmpty() ) {
01106         delegate = attendee->delegate();
01107       }
01108       if ( !delegate.isEmpty() ) {
01109         return i18n( "Sender has delegated this request for the to-do to %1", delegate );
01110       }
01111       return i18n( "Sender has delegated this request for the to-do " );
01112     }
01113     case Attendee::Completed:
01114       return i18n( "The request for this to-do is now completed" );
01115     case Attendee::InProcess:
01116       return i18n( "Sender is still processing the invitation" );
01117     default:
01118       return i18n( "Unknown response to this to-do" );
01119     }
01120     break;
01121   }
01122   case iTIPCounter:
01123     return i18n( "Sender makes this counter proposal" );
01124   case iTIPDeclineCounter:
01125     return i18n( "Sender declines the counter proposal" );
01126   case iTIPNoMethod:
01127     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01128   }
01129   return QString();
01130 }
01131 
01132 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01133 {
01134   // TODO: Several of the methods are not allowed for journals, so remove them.
01135   if ( !msg || !journal ) {
01136     return QString();
01137   }
01138 
01139   switch ( msg->method() ) {
01140   case iTIPPublish:
01141     return i18n( "This journal has been published" );
01142   case iTIPRequest:
01143     return i18n( "You have been assigned this journal" );
01144   case iTIPRefresh:
01145     return i18n( "This journal was refreshed" );
01146   case iTIPCancel:
01147     return i18n( "This journal was canceled" );
01148   case iTIPAdd:
01149     return i18n( "Addition to the journal" );
01150   case iTIPReply:
01151   {
01152     Attendee::List attendees = journal->attendees();
01153     if ( attendees.count() == 0 ) {
01154       kDebug() << "No attendees in the iCal reply!";
01155       return QString();
01156     }
01157 
01158     if( attendees.count() != 1 ) {
01159       kDebug() << "Warning: attendeecount in the reply should be 1"
01160                << "but is" << attendees.count();
01161     }
01162 
01163     Attendee *attendee = *attendees.begin();
01164     switch( attendee->status() ) {
01165     case Attendee::NeedsAction:
01166       return i18n( "Sender indicates this journal assignment still needs some action" );
01167     case Attendee::Accepted:
01168       return i18n( "Sender accepts this journal" );
01169     case Attendee::Tentative:
01170       return i18n( "Sender tentatively accepts this journal" );
01171     case Attendee::Declined:
01172       return i18n( "Sender declines this journal" );
01173     case Attendee::Delegated:
01174       return i18n( "Sender has delegated this request for the journal" );
01175     case Attendee::Completed:
01176       return i18n( "The request for this journal is now completed" );
01177     case Attendee::InProcess:
01178       return i18n( "Sender is still processing the invitation" );
01179     default:
01180       return i18n( "Unknown response to this journal" );
01181     }
01182     break;
01183   }
01184   case iTIPCounter:
01185     return i18n( "Sender makes this counter proposal" );
01186   case iTIPDeclineCounter:
01187     return i18n( "Sender declines the counter proposal" );
01188   case iTIPNoMethod:
01189     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01190   }
01191   return QString();
01192 }
01193 
01194 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01195 {
01196   if ( !msg || !fb ) {
01197     return QString();
01198   }
01199 
01200   switch ( msg->method() ) {
01201   case iTIPPublish:
01202     return i18n( "This free/busy list has been published" );
01203   case iTIPRequest:
01204     return i18n( "The free/busy list has been requested" );
01205   case iTIPRefresh:
01206     return i18n( "This free/busy list was refreshed" );
01207   case iTIPCancel:
01208     return i18n( "This free/busy list was canceled" );
01209   case iTIPAdd:
01210     return i18n( "Addition to the free/busy list" );
01211   case iTIPNoMethod:
01212   default:
01213     return i18n( "Error: Free/Busy iMIP message with unknown method: '%1'", msg->method() );
01214   }
01215 }
01216 //@endcond
01217 
01218 //@cond PRIVATE
01219 class KCal::IncidenceFormatter::ScheduleMessageVisitor
01220   : public IncidenceBase::Visitor
01221 {
01222   public:
01223     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01224     bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01225     {
01226       mMessage = msg;
01227       return incidence->accept( *this );
01228     }
01229     QString result() const { return mResult; }
01230 
01231   protected:
01232     QString mResult;
01233     ScheduleMessage *mMessage;
01234 };
01235 
01236 class KCal::IncidenceFormatter::InvitationHeaderVisitor :
01237       public IncidenceFormatter::ScheduleMessageVisitor
01238 {
01239   protected:
01240     bool visit( Event *event )
01241     {
01242       mResult = invitationHeaderEvent( event, mMessage );
01243       return !mResult.isEmpty();
01244     }
01245     bool visit( Todo *todo )
01246     {
01247       mResult = invitationHeaderTodo( todo, mMessage );
01248       return !mResult.isEmpty();
01249     }
01250     bool visit( Journal *journal )
01251     {
01252       mResult = invitationHeaderJournal( journal, mMessage );
01253       return !mResult.isEmpty();
01254     }
01255     bool visit( FreeBusy *fb )
01256     {
01257       mResult = invitationHeaderFreeBusy( fb, mMessage );
01258       return !mResult.isEmpty();
01259     }
01260 };
01261 
01262 class KCal::IncidenceFormatter::InvitationBodyVisitor
01263   : public IncidenceFormatter::ScheduleMessageVisitor
01264 {
01265   public:
01266     InvitationBodyVisitor( bool noHtmlMode )
01267       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) { }
01268 
01269   protected:
01270     bool visit( Event *event )
01271     {
01272       mResult = invitationDetailsEvent( event, mNoHtmlMode );
01273       return !mResult.isEmpty();
01274     }
01275     bool visit( Todo *todo )
01276     {
01277       mResult = invitationDetailsTodo( todo, mNoHtmlMode );
01278       return !mResult.isEmpty();
01279     }
01280     bool visit( Journal *journal )
01281     {
01282       mResult = invitationDetailsJournal( journal, mNoHtmlMode );
01283       return !mResult.isEmpty();
01284     }
01285     bool visit( FreeBusy *fb )
01286     {
01287       mResult = invitationDetailsFreeBusy( fb );
01288       return !mResult.isEmpty();
01289     }
01290 
01291   private:
01292     bool mNoHtmlMode;
01293 };
01294 //@endcond
01295 
01296 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
01297 {
01298   return id;
01299 }
01300 
01301 //@cond PRIVATE
01302 class IncidenceFormatter::IncidenceCompareVisitor
01303   : public IncidenceBase::Visitor
01304 {
01305   public:
01306     IncidenceCompareVisitor() : mExistingIncidence( 0 ) {}
01307     bool act( IncidenceBase *incidence, Incidence *existingIncidence )
01308     {
01309       if ( !existingIncidence ) {
01310         return false;
01311       }
01312       Incidence *inc = dynamic_cast<Incidence *>( incidence );
01313       if ( inc && inc->revision() <= existingIncidence->revision() ) {
01314         return false;
01315       }
01316       mExistingIncidence = existingIncidence;
01317       return incidence->accept( *this );
01318     }
01319 
01320     QString result() const
01321     {
01322       if ( mChanges.isEmpty() ) {
01323         return QString();
01324       }
01325       QString html = "<div align=\"left\"><ul><li>";
01326       html += mChanges.join( "</li><li>" );
01327       html += "</li><ul></div>";
01328       return html;
01329     }
01330 
01331   protected:
01332     bool visit( Event *event )
01333     {
01334       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01335       compareIncidences( event, mExistingIncidence );
01336       return !mChanges.isEmpty();
01337     }
01338     bool visit( Todo *todo )
01339     {
01340       compareIncidences( todo, mExistingIncidence );
01341       return !mChanges.isEmpty();
01342     }
01343     bool visit( Journal *journal )
01344     {
01345       compareIncidences( journal, mExistingIncidence );
01346       return !mChanges.isEmpty();
01347     }
01348     bool visit( FreeBusy *fb )
01349     {
01350       Q_UNUSED( fb );
01351       return !mChanges.isEmpty();
01352     }
01353 
01354   private:
01355     void compareEvents( Event *newEvent, Event *oldEvent )
01356     {
01357       if ( !oldEvent || !newEvent ) {
01358         return;
01359       }
01360       if ( oldEvent->dtStart() != newEvent->dtStart() ||
01361            oldEvent->allDay() != newEvent->allDay() ) {
01362         mChanges += i18n( "The begin of the meeting has been changed from %1 to %2",
01363                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01364       }
01365       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01366            oldEvent->allDay() != newEvent->allDay() ) {
01367         mChanges += i18n( "The end of the meeting has been changed from %1 to %2",
01368                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01369       }
01370     }
01371 
01372     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01373     {
01374       if ( !oldInc || !newInc ) {
01375         return;
01376       }
01377 
01378       if ( oldInc->summary() != newInc->summary() ) {
01379         mChanges += i18n( "The summary has been changed to: \"%1\"",
01380                           newInc->richSummary() );
01381       }
01382 
01383       if ( oldInc->location() != newInc->location() ) {
01384         mChanges += i18n( "The location has been changed to: \"%1\"",
01385                           newInc->richLocation() );
01386       }
01387 
01388       if ( oldInc->description() != newInc->description() ) {
01389         mChanges += i18n( "The description has been changed to: \"%1\"",
01390                           newInc->richDescription() );
01391       }
01392 
01393       Attendee::List oldAttendees = oldInc->attendees();
01394       Attendee::List newAttendees = newInc->attendees();
01395       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01396             it != newAttendees.constEnd(); ++it ) {
01397         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01398         if ( !oldAtt ) {
01399           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01400         } else {
01401           if ( oldAtt->status() != (*it)->status() ) {
01402             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01403                               (*it)->fullName(), (*it)->statusStr() );
01404           }
01405         }
01406       }
01407 
01408       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01409             it != oldAttendees.constEnd(); ++it ) {
01410         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01411         if ( !newAtt ) {
01412           mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01413         }
01414       }
01415     }
01416 
01417   private:
01418     Incidence *mExistingIncidence;
01419     QStringList mChanges;
01420 };
01421 //@endcond
01422 
01423 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01424 {
01425   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01426   return res.arg( generateLinkURL( id ) ).arg( text );
01427   return res;
01428 }
01429 
01430 Calendar *InvitationFormatterHelper::calendar() const
01431 {
01432   return 0;
01433 }
01434 
01435 //@cond PRIVATE
01436 // Check if the given incidence is likely one that we own instead one from
01437 // a shared calendar (Kolab-specific)
01438 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01439 {
01440   CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01441   if ( !cal || !incidence ) {
01442     return true;
01443   }
01444 
01445   ResourceCalendar *res = cal->resource( incidence );
01446   if ( !res ) {
01447     return true;
01448   }
01449 
01450   const QString subRes = res->subresourceIdentifier( incidence );
01451   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01452     return false;
01453   }
01454   return true;
01455 }
01456 
01457 static QString formatICalInvitationHelper( QString invitation, Calendar *mCalendar,
01458     InvitationFormatterHelper *helper, bool noHtmlMode )
01459 {
01460   if ( invitation.isEmpty() ) {
01461     return QString();
01462   }
01463 
01464   ICalFormat format;
01465   // parseScheduleMessage takes the tz from the calendar,
01466   // no need to set it manually here for the format!
01467   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01468 
01469   if( !msg ) {
01470     kDebug() << "Failed to parse the scheduling message";
01471     Q_ASSERT( format.exception() );
01472     kDebug() << format.exception()->message();
01473     return QString();
01474   }
01475 
01476   IncidenceBase *incBase = msg->event();
01477   incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
01478 
01479   Incidence *existingIncidence = 0;
01480   if ( helper->calendar() ) {
01481     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01482     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
01483       existingIncidence = 0;
01484     }
01485     if ( !existingIncidence ) {
01486       const Incidence::List list = helper->calendar()->incidences();
01487       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01488         if ( (*it)->schedulingID() == incBase->uid() &&
01489              incidenceOwnedByMe( helper->calendar(), *it ) ) {
01490           existingIncidence = *it;
01491           break;
01492         }
01493       }
01494     }
01495   }
01496 
01497   // First make the text of the message
01498   QString html;
01499   html += "<div align=\"center\" style=\"border:solid 1px; width:80%;\">";
01500   html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01501 
01502   IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
01503   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01504   if ( !headerVisitor.act( incBase, msg ) ) {
01505     return QString();
01506   }
01507   html += "<tr>" + eventViewerAddTag( "h3", headerVisitor.result() ) + "</tr>";
01508 
01509   IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode );
01510   if ( !bodyVisitor.act( incBase, msg ) ) {
01511     return QString();
01512   }
01513   html += bodyVisitor.result();
01514 
01515   if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well?
01516     IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
01517     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01518       html +=
01519         i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
01520       html += compareVisitor.result();
01521     }
01522   }
01523 
01524   // Add groupware links
01525 
01526   html += "<p>";
01527   html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
01528 
01529   //TODO: 4.4, in tdOpen, change border-width:0px to border-width:2px and also
01530   //remove the [] on the button strings: "Accept", "Decline", "Counter", etc.
01531   const QString tdOpen = "<td style=\"border-width:0px;border-style:outset\">";
01532   const QString tdClose = "</td>";
01533   Incidence *incidence = dynamic_cast<Incidence*>( incBase );
01534   switch ( msg->method() ) {
01535   case iTIPPublish:
01536   case iTIPRequest:
01537   case iTIPRefresh:
01538   case iTIPAdd:
01539   {
01540     if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
01541       html += tdOpen;
01542       if ( incBase->type() == "Todo" ) {
01543         //TODO: 4.4, remove the []
01544         html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01545       } else {
01546         html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01547       }
01548       html += tdClose;
01549     }
01550 
01551     if ( incidence && !existingIncidence ) {
01552       // Accept
01553       html += tdOpen;
01554         //TODO: 4.4, remove the []
01555       html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) );
01556       html += tdClose;
01557 
01558       // Accept conditionally
01559       html += tdOpen;
01560       //TODO: 4.4, remove the []
01561       html += helper->makeLink( "accept_conditionally",
01562                                 i18nc( "Accept conditionally", "[Accept cond.]" ) );
01563       html += tdClose;
01564 
01565       // Counter proposal
01566       html += tdOpen;
01567       //TODO: 4.4, remove the []
01568       html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01569       html += tdClose;
01570 
01571       // Decline
01572       html += tdOpen;
01573       //TODO: 4.4, remove the []
01574       html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) );
01575       html += tdClose;
01576 
01577       // Delegate
01578       html += tdOpen;
01579       //TODO: 4.4, remove the []
01580       html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) );
01581       html += tdClose;
01582 
01583       // Forward
01584       html += tdOpen;
01585       //TODO: 4.4, remove the []
01586       html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) );
01587       html += tdClose;
01588 
01589       // Check in calendar
01590       if ( incBase->type() == "Event" ) {
01591         html += tdOpen;
01592         //TODO: 4.4, remove the []
01593         //TODO: 4.4, change to "Check calendar"
01594         html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01595         html += tdClose;
01596       }
01597     }
01598     break;
01599   }
01600 
01601   case iTIPCancel:
01602     // Cancel event from my calendar
01603     html += tdOpen;
01604     //TODO: 4.4, remove the []
01605     html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01606     html += tdClose;
01607     break;
01608 
01609   case iTIPReply:
01610     // Enter this into my calendar
01611     html += tdOpen;
01612     //TODO: 4.4, remove the []
01613     //TODO: 4.4, change string to "Enter this response into my..."
01614     if ( incBase->type() == "Todo" ) {
01615       html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01616     } else {
01617       html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01618     }
01619     html += tdClose;
01620     break;
01621 
01622   case iTIPCounter:
01623     html += tdOpen;
01624     //TODO: 4.4, remove the []
01625     html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
01626     html += tdClose;
01627 
01628     html += tdOpen;
01629     //TODO: 4.4, remove the []
01630     html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
01631     html += tdClose;
01632 
01633     html += tdOpen;
01634     //TODO: 4.4, remove the []
01635     //TODO: 4.4, change string to "Check calendar"
01636     html += helper->makeLink( "check_calendar", i18n( "[Check my calendar]" ) );
01637     html += tdClose;
01638     break;
01639 
01640   case iTIPDeclineCounter:
01641   case iTIPNoMethod:
01642     break;
01643   }
01644 
01645   // close the groupware table
01646   html += "</tr></table>";
01647 
01648   // close the top-level table
01649   html += "</table></div>";
01650   kDebug() << html;
01651   return html;
01652 }
01653 //@endcond
01654 
01655 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01656     InvitationFormatterHelper *helper )
01657 {
01658   return formatICalInvitationHelper( invitation, mCalendar, helper, false );
01659 }
01660 
01661 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation, Calendar *mCalendar,
01662     InvitationFormatterHelper *helper )
01663 {
01664   return formatICalInvitationHelper( invitation, mCalendar, helper, true );
01665 }
01666 
01667 /*******************************************************************
01668  *  Helper functions for the Incidence tooltips
01669  *******************************************************************/
01670 
01671 //@cond PRIVATE
01672 class KCal::IncidenceFormatter::ToolTipVisitor
01673   : public IncidenceBase::Visitor
01674 {
01675   public:
01676     ToolTipVisitor()
01677       : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
01678 
01679     bool act( IncidenceBase *incidence, bool richText=true, KDateTime::Spec spec=KDateTime::Spec() )
01680     {
01681       mRichText = richText;
01682       mSpec = spec;
01683       mResult = "";
01684       return incidence ? incidence->accept( *this ) : false;
01685     }
01686     QString result() const { return mResult; }
01687 
01688   protected:
01689     bool visit( Event *event );
01690     bool visit( Todo *todo );
01691     bool visit( Journal *journal );
01692     bool visit( FreeBusy *fb );
01693 
01694     QString dateRangeText( Event *event );
01695     QString dateRangeText( Todo *todo );
01696     QString dateRangeText( Journal *journal );
01697     QString dateRangeText( FreeBusy *fb );
01698 
01699     QString generateToolTip( Incidence *incidence, QString dtRangeText );
01700 
01701   protected:
01702     bool mRichText;
01703     KDateTime::Spec mSpec;
01704     QString mResult;
01705 };
01706 
01707 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event )
01708 {
01709   //FIXME: support mRichText==false
01710   QString ret;
01711   QString tmp;
01712   if ( event->isMultiDay() ) {
01713 
01714     tmp = IncidenceFormatter::dateToString( event->dtStart(), true, mSpec );
01715     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
01716 
01717     tmp = IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec );
01718     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
01719 
01720   } else {
01721 
01722     ret += "<br>" +
01723       i18n( "<i>Date:</i> %1", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) );
01724     if ( !event->allDay() ) {
01725       const QString dtStartTime = IncidenceFormatter::timeToString( event->dtStart(), true, mSpec );
01726       const QString dtEndTime = IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec );
01727       if ( dtStartTime == dtEndTime ) {
01728         // to prevent 'Time: 17:00 - 17:00'
01729         tmp = "<br>" +
01730               i18nc( "time for event", "<i>Time:</i> %1",
01731                      dtStartTime );
01732       } else {
01733         tmp = "<br>" +
01734               i18nc( "time range for event",
01735                      "<i>Time:</i> %1 - %2",
01736                      dtStartTime, dtEndTime );
01737       }
01738       ret += tmp;
01739     }
01740   }
01741   return ret.replace( ' ', "&nbsp;" );
01742 }
01743 
01744 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo )
01745 {
01746   //FIXME: support mRichText==false
01747   QString ret;
01748   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01749     // No need to add <i> here. This is separated issue and each line
01750     // is very visible on its own. On the other hand... Yes, I like it
01751     // italics here :)
01752     ret += "<br>" + i18n( "<i>Start:</i> %1",
01753                           IncidenceFormatter::dateToString( todo->dtStart( false ), true, mSpec ) );
01754   }
01755   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01756     ret += "<br>" + i18n( "<i>Due:</i> %1",
01757                           IncidenceFormatter::dateTimeToString( todo->dtDue(),
01758                                                                 todo->allDay(),
01759                                                                 true, mSpec ) );
01760   }
01761   if ( todo->isCompleted() ) {
01762     ret += "<br>" +
01763            i18n( "<i>Completed:</i> %1", todo->completedStr() );
01764   } else {
01765     ret += "<br>" +
01766            i18nc( "percent complete", "%1 % completed", todo->percentComplete() );
01767   }
01768 
01769   return ret.replace( ' ', "&nbsp;" );
01770 }
01771 
01772 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
01773 {
01774   //FIXME: support mRichText==false
01775   QString ret;
01776   if ( journal->dtStart().isValid() ) {
01777     ret += "<br>" +
01778            i18n( "<i>Date:</i> %1",
01779                  IncidenceFormatter::dateToString( journal->dtStart(), false, mSpec ) );
01780   }
01781   return ret.replace( ' ', "&nbsp;" );
01782 }
01783 
01784 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01785 {
01786   //FIXME: support mRichText==false
01787   QString ret;
01788   ret = "<br>" +
01789         i18n( "<i>Period start:</i> %1",
01790               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
01791   ret += "<br>" +
01792          i18n( "<i>Period start:</i> %1",
01793                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
01794   return ret.replace( ' ', "&nbsp;" );
01795 }
01796 
01797 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01798 {
01799   mResult = generateToolTip( event, dateRangeText( event ) );
01800   return !mResult.isEmpty();
01801 }
01802 
01803 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01804 {
01805   mResult = generateToolTip( todo, dateRangeText( todo ) );
01806   return !mResult.isEmpty();
01807 }
01808 
01809 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01810 {
01811   mResult = generateToolTip( journal, dateRangeText( journal ) );
01812   return !mResult.isEmpty();
01813 }
01814 
01815 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01816 {
01817   //FIXME: support mRichText==false
01818   mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
01819   mResult += dateRangeText( fb );
01820   mResult += "</qt>";
01821   return !mResult.isEmpty();
01822 }
01823 
01824 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
01825                                                              QString dtRangeText )
01826 {
01827   //FIXME: support mRichText==false
01828   if ( !incidence ) {
01829     return QString();
01830   }
01831 
01832   QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
01833 
01834   tmp += dtRangeText;
01835 
01836   if ( !incidence->location().isEmpty() ) {
01837     // Put Location: in italics
01838     tmp += "<br>" +
01839            i18n( "<i>Location:</i>&nbsp;%1", incidence->richLocation() );
01840   }
01841 
01842   if ( !incidence->description().isEmpty() ) {
01843     QString desc( incidence->description() );
01844     if ( !incidence->descriptionIsRich() ) {
01845       if ( desc.length() > 120 ) {
01846         desc = desc.left( 120 ) + "...";
01847       }
01848       desc = Qt::escape( desc ).replace( '\n', "<br>" );
01849     } else {
01850       // TODO: truncate the description when it's rich text
01851     }
01852     tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
01853   }
01854   tmp += "</qt>";
01855   return tmp;
01856 }
01857 //@endcond
01858 
01859 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence,
01860                                            bool richText )
01861 {
01862   return toolTipStr( incidence, richText, KDateTime::Spec() );
01863 }
01864 
01865 QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence,
01866                                         bool richText, KDateTime::Spec spec )
01867 {
01868   ToolTipVisitor v;
01869   if ( v.act( incidence, richText, spec ) ) {
01870     return v.result();
01871   } else {
01872     return QString();
01873   }
01874 }
01875 
01876 /*******************************************************************
01877  *  Helper functions for the Incidence tooltips
01878  *******************************************************************/
01879 
01880 //@cond PRIVATE
01881 static QString mailBodyIncidence( Incidence *incidence )
01882 {
01883   QString body;
01884   if ( !incidence->summary().isEmpty() ) {
01885     body += i18n( "Summary: %1\n", incidence->richSummary() );
01886   }
01887   if ( !incidence->organizer().isEmpty() ) {
01888     body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
01889   }
01890   if ( !incidence->location().isEmpty() ) {
01891     body += i18n( "Location: %1\n", incidence->richLocation() );
01892   }
01893   return body;
01894 }
01895 //@endcond
01896 
01897 //@cond PRIVATE
01898 class KCal::IncidenceFormatter::MailBodyVisitor
01899   : public IncidenceBase::Visitor
01900 {
01901   public:
01902     MailBodyVisitor()
01903       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
01904 
01905     bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
01906     {
01907       mSpec = spec;
01908       mResult = "";
01909       return incidence ? incidence->accept( *this ) : false;
01910     }
01911     QString result() const
01912     {
01913       return mResult;
01914     }
01915 
01916   protected:
01917     bool visit( Event *event );
01918     bool visit( Todo *todo );
01919     bool visit( Journal *journal );
01920     bool visit( FreeBusy * )
01921     {
01922       mResult = i18n( "This is a Free Busy Object" );
01923       return !mResult.isEmpty();
01924     }
01925   protected:
01926     KDateTime::Spec mSpec;
01927     QString mResult;
01928 };
01929 
01930 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
01931 {
01932   QString recurrence[]= {
01933     i18nc( "no recurrence", "None" ),
01934     i18nc( "event recurs by minutes", "Minutely" ),
01935     i18nc( "event recurs by hours", "Hourly" ),
01936     i18nc( "event recurs by days", "Daily" ),
01937     i18nc( "event recurs by weeks", "Weekly" ),
01938     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
01939     i18nc( "event recurs same day each month", "Monthly Same Day" ),
01940     i18nc( "event recurs same month each year", "Yearly Same Month" ),
01941     i18nc( "event recurs same day each year", "Yearly Same Day" ),
01942     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
01943   };
01944 
01945   mResult = mailBodyIncidence( event );
01946   mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( event->dtStart(), true,
01947                                                                          mSpec ) );
01948   if ( !event->allDay() ) {
01949     mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( event->dtStart(),
01950                                                                            true, mSpec ) );
01951   }
01952   if ( event->dtStart() != event->dtEnd() ) {
01953     mResult += i18n( "End Date: %1\n", IncidenceFormatter::dateToString( event->dtEnd(), true,
01954                                                                          mSpec ) );
01955   }
01956   if ( !event->allDay() ) {
01957     mResult += i18n( "End Time: %1\n", IncidenceFormatter::timeToString( event->dtEnd(), true,
01958                                                                          mSpec ) );
01959   }
01960   if ( event->recurs() ) {
01961     Recurrence *recur = event->recurrence();
01962     // TODO: Merge these two to one of the form "Recurs every 3 days"
01963     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
01964     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
01965 
01966     if ( recur->duration() > 0 ) {
01967       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
01968       mResult += '\n';
01969     } else {
01970       if ( recur->duration() != -1 ) {
01971 // TODO_Recurrence: What to do with all-day
01972         QString endstr;
01973         if ( event->allDay() ) {
01974           endstr = KGlobal::locale()->formatDate( recur->endDate() );
01975         } else {
01976           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
01977         }
01978         mResult += i18n( "Repeat until: %1\n", endstr );
01979       } else {
01980         mResult += i18n( "Repeats forever\n" );
01981       }
01982     }
01983   }
01984 
01985   QString details = event->richDescription();
01986   if ( !details.isEmpty() ) {
01987     mResult += i18n( "Details:\n%1\n", details );
01988   }
01989   return !mResult.isEmpty();
01990 }
01991 
01992 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
01993 {
01994   mResult = mailBodyIncidence( todo );
01995 
01996   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01997     mResult += i18n( "Start Date: %1\n",
01998                      IncidenceFormatter::dateToString( todo->dtStart(false), true, mSpec ) );
01999     if ( !todo->allDay() ) {
02000       mResult += i18n( "Start Time: %1\n",
02001                        IncidenceFormatter::timeToString( todo->dtStart(false), true, mSpec ) );
02002     }
02003   }
02004   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02005     mResult += i18n( "Due Date: %1\n",
02006                      IncidenceFormatter::dateToString( todo->dtDue(), true, mSpec ) );
02007     if ( !todo->allDay() ) {
02008       mResult += i18n( "Due Time: %1\n",
02009                        IncidenceFormatter::timeToString( todo->dtDue(), true, mSpec ) );
02010     }
02011   }
02012   QString details = todo->richDescription();
02013   if ( !details.isEmpty() ) {
02014     mResult += i18n( "Details:\n%1\n", details );
02015   }
02016   return !mResult.isEmpty();
02017 }
02018 
02019 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02020 {
02021   mResult = mailBodyIncidence( journal );
02022   mResult += i18n( "Date: %1\n", IncidenceFormatter::dateToString( journal->dtStart(), true,
02023                                                                 mSpec ) );
02024 
02025   if ( !journal->allDay() ) {
02026     mResult += i18n( "Time: %1\n",
02027                      IncidenceFormatter::timeToString( journal->dtStart(), true, mSpec ) );
02028 
02029   }
02030   if ( !journal->description().isEmpty() ) {
02031     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
02032   }
02033   return !mResult.isEmpty();
02034 }
02035 //@endcond
02036 
02037 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02038 {
02039   return mailBodyStr( incidence, KDateTime::Spec() );
02040 }
02041 
02042 QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence,
02043                                          KDateTime::Spec spec )
02044 {
02045   if ( !incidence ) {
02046     return QString();
02047   }
02048 
02049   MailBodyVisitor v;
02050   if ( v.act( incidence, spec ) ) {
02051     return v.result();
02052   }
02053   return QString();
02054 }
02055 
02056 //@cond PRIVATE
02057 static QString recurEnd( Incidence *incidence )
02058 {
02059   QString endstr;
02060   if ( incidence->allDay() ) {
02061     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02062   } else {
02063     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02064   }
02065   return endstr;
02066 }
02067 //@endcond
02068 
02069 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
02070 {
02071   if ( !incidence->recurs() ) {
02072     return i18n( "No recurrence" );
02073   }
02074   QStringList dayList;
02075   dayList.append( i18n( "31st Last" ) );
02076   dayList.append( i18n( "30th Last" ) );
02077   dayList.append( i18n( "29th Last" ) );
02078   dayList.append( i18n( "28th Last" ) );
02079   dayList.append( i18n( "27th Last" ) );
02080   dayList.append( i18n( "26th Last" ) );
02081   dayList.append( i18n( "25th Last" ) );
02082   dayList.append( i18n( "24th Last" ) );
02083   dayList.append( i18n( "23rd Last" ) );
02084   dayList.append( i18n( "22nd Last" ) );
02085   dayList.append( i18n( "21st Last" ) );
02086   dayList.append( i18n( "20th Last" ) );
02087   dayList.append( i18n( "19th Last" ) );
02088   dayList.append( i18n( "18th Last" ) );
02089   dayList.append( i18n( "17th Last" ) );
02090   dayList.append( i18n( "16th Last" ) );
02091   dayList.append( i18n( "15th Last" ) );
02092   dayList.append( i18n( "14th Last" ) );
02093   dayList.append( i18n( "13th Last" ) );
02094   dayList.append( i18n( "12th Last" ) );
02095   dayList.append( i18n( "11th Last" ) );
02096   dayList.append( i18n( "10th Last" ) );
02097   dayList.append( i18n( "9th Last" ) );
02098   dayList.append( i18n( "8th Last" ) );
02099   dayList.append( i18n( "7th Last" ) );
02100   dayList.append( i18n( "6th Last" ) );
02101   dayList.append( i18n( "5th Last" ) );
02102   dayList.append( i18n( "4th Last" ) );
02103   dayList.append( i18n( "3rd Last" ) );
02104   dayList.append( i18n( "2nd Last" ) );
02105   dayList.append( i18nc( "last day of the month", "Last" ) );
02106   dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
02107   dayList.append( i18n( "1st" ) );
02108   dayList.append( i18n( "2nd" ) );
02109   dayList.append( i18n( "3rd" ) );
02110   dayList.append( i18n( "4th" ) );
02111   dayList.append( i18n( "5th" ) );
02112   dayList.append( i18n( "6th" ) );
02113   dayList.append( i18n( "7th" ) );
02114   dayList.append( i18n( "8th" ) );
02115   dayList.append( i18n( "9th" ) );
02116   dayList.append( i18n( "10th" ) );
02117   dayList.append( i18n( "11th" ) );
02118   dayList.append( i18n( "12th" ) );
02119   dayList.append( i18n( "13th" ) );
02120   dayList.append( i18n( "14th" ) );
02121   dayList.append( i18n( "15th" ) );
02122   dayList.append( i18n( "16th" ) );
02123   dayList.append( i18n( "17th" ) );
02124   dayList.append( i18n( "18th" ) );
02125   dayList.append( i18n( "19th" ) );
02126   dayList.append( i18n( "20th" ) );
02127   dayList.append( i18n( "21st" ) );
02128   dayList.append( i18n( "22nd" ) );
02129   dayList.append( i18n( "23rd" ) );
02130   dayList.append( i18n( "24th" ) );
02131   dayList.append( i18n( "25th" ) );
02132   dayList.append( i18n( "26th" ) );
02133   dayList.append( i18n( "27th" ) );
02134   dayList.append( i18n( "28th" ) );
02135   dayList.append( i18n( "29th" ) );
02136   dayList.append( i18n( "30th" ) );
02137   dayList.append( i18n( "31st" ) );
02138   int weekStart = KGlobal::locale()->weekStartDay();
02139   QString dayNames;
02140   QString txt;
02141   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
02142   Recurrence *recur = incidence->recurrence();
02143   switch ( recur->recurrenceType() ) {
02144   case Recurrence::rNone:
02145     return i18n( "No recurrence" );
02146   case Recurrence::rMinutely:
02147     if ( recur->duration() != -1 ) {
02148       txt = i18np( "Recurs every minute until %2",
02149                    "Recurs every %1 minutes until %2",
02150                    recur->frequency(), recurEnd( incidence ) );
02151       if ( recur->duration() >  0 ) {
02152         txt += i18nc( "number of occurrences",
02153                       " (<numid>%1</numid> occurrences)",
02154                       recur->duration() );
02155       }
02156       return txt;
02157     }
02158     return i18np( "Recurs every minute",
02159                   "Recurs every %1 minutes", recur->frequency() );
02160   case Recurrence::rHourly:
02161     if ( recur->duration() != -1 ) {
02162       txt = i18np( "Recurs hourly until %2",
02163                    "Recurs every %1 hours until %2",
02164                    recur->frequency(), recurEnd( incidence ) );
02165       if ( recur->duration() >  0 ) {
02166         txt += i18nc( "number of occurrences",
02167                       " (<numid>%1</numid> occurrences)",
02168                       recur->duration() );
02169       }
02170       return txt;
02171     }
02172     return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
02173   case Recurrence::rDaily:
02174     if ( recur->duration() != -1 ) {
02175       txt = i18np( "Recurs daily until %2",
02176                    "Recurs every %1 days until %2",
02177                    recur->frequency(), recurEnd( incidence ) );
02178       if ( recur->duration() >  0 ) {
02179         txt += i18nc( "number of occurrences",
02180                       " (<numid>%1</numid> occurrences)",
02181                       recur->duration() );
02182       }
02183       return txt;
02184     }
02185     return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
02186   case Recurrence::rWeekly:
02187   {
02188     bool addSpace = false;
02189     for ( int i = 0; i < 7; ++i ) {
02190       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
02191         if ( addSpace ) {
02192           dayNames.append( i18nc( "separator for list of days", ", " ) );
02193         }
02194         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
02195                                               KCalendarSystem::ShortDayName ) );
02196         addSpace = true;
02197       }
02198     }
02199     if ( dayNames.isEmpty() ) {
02200       dayNames = i18nc( "Recurs weekly on no days", "no days" );
02201     }
02202     if ( recur->duration() != -1 ) {
02203       txt = i18ncp( "Recurs weekly on [list of days] until end-date",
02204                     "Recurs weekly on %2 until %3",
02205                     "Recurs every <numid>%1</numid> weeks on %2 until %3",
02206                     recur->frequency(), dayNames, recurEnd( incidence ) );
02207       if ( recur->duration() >  0 ) {
02208         txt += i18nc( "number of occurrences",
02209                       " (<numid>%1</numid> occurrences)",
02210                       recur->duration() );
02211       }
02212       return txt;
02213     }
02214     return i18ncp( "Recurs weekly on [list of days]",
02215                    "Recurs weekly on %2",
02216                    "Recurs every <numid>%1</numid> weeks on %2",
02217                    recur->frequency(), dayNames );
02218   }
02219   case Recurrence::rMonthlyPos:
02220   {
02221     KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
02222     if ( recur->duration() != -1 ) {
02223       txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
02224                     " weekdayname until end-date",
02225                     "Recurs every month on the %2 %3 until %4",
02226                     "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
02227                     recur->frequency(),
02228                     dayList[rule.pos() + 31],
02229                     calSys->weekDayName( rule.day(),KCalendarSystem::LongDayName ),
02230                     recurEnd( incidence ) );
02231       if ( recur->duration() >  0 ) {
02232         txt += i18nc( "number of occurrences",
02233                       " (<numid>%1</numid> occurrences)",
02234                       recur->duration() );
02235       }
02236       return txt;
02237     }
02238     return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
02239                    "Recurs every month on the %2 %3",
02240                    "Recurs every %1 months on the %2 %3",
02241                    recur->frequency(),
02242                    dayList[rule.pos() + 31],
02243                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
02244   }
02245   case Recurrence::rMonthlyDay:
02246   {
02247     int days = recur->monthDays()[0];
02248     if ( recur->duration() != -1 ) {
02249         txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
02250                       "Recurs monthly on the %2 day until %3",
02251                       "Recurs every %1 months on the %2 day until %3",
02252                       recur->frequency(),
02253                       dayList[days + 31],
02254                       recurEnd( incidence ) );
02255         if ( recur->duration() >  0 ) {
02256           txt += i18nc( "number of occurrences",
02257                         " (<numid>%1</numid> occurrences)",
02258                         recur->duration() );
02259         }
02260         return txt;
02261     }
02262     return i18ncp( "Recurs monthly on the [1st|2nd|...] day",
02263                    "Recurs monthly on the %2 day",
02264                    "Recurs every <numid>%1</numid> month on the %2 day",
02265                    recur->frequency(),
02266                    dayList[days + 31] );
02267   }
02268   case Recurrence::rYearlyMonth:
02269   {
02270     if ( recur->duration() != -1 ) {
02271       txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
02272                     " until end-date",
02273                     "Recurs yearly on %2 %3 until %4",
02274                     "Recurs every %1 years on %2 %3 until %4",
02275                     recur->frequency(),
02276                     calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02277                     dayList[ recur->yearDates()[0] + 31 ],
02278                     recurEnd( incidence ) );
02279       if ( recur->duration() >  0 ) {
02280         txt += i18nc( "number of occurrences",
02281                       " (<numid>%1</numid> occurrences)",
02282                       recur->duration() );
02283       }
02284       return txt;
02285     }
02286     if ( !recur->yearDates().isEmpty() ) {
02287       return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
02288                      "Recurs yearly on %2 %3",
02289                      "Recurs every %1 years on %2 %3",
02290                      recur->frequency(),
02291                      calSys->monthName( recur->yearMonths()[0],
02292                                         recur->startDate().year() ),
02293                      dayList[ recur->yearDates()[0] + 31 ] );
02294     } else {
02295       if (!recur->yearMonths().isEmpty() ) {
02296         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02297                       "Recurs yearly on %1 %2",
02298                       calSys->monthName( recur->yearMonths()[0],
02299                                          recur->startDate().year() ),
02300                       dayList[ recur->startDate().day() + 31 ] );
02301       } else {
02302         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02303                       "Recurs yearly on %1 %2",
02304                       calSys->monthName( recur->startDate().month(),
02305                                          recur->startDate().year() ),
02306                       dayList[ recur->startDate().day() + 31 ] );
02307       }
02308     }
02309   }
02310   case Recurrence::rYearlyDay:
02311     if ( recur->duration() != -1 ) {
02312       txt = i18ncp( "Recurs every N years on day N until end-date",
02313                     "Recurs every year on day <numid>%2</numid> until %3",
02314                     "Recurs every <numid>%1</numid> years"
02315                     " on day <numid>%2</numid> until %3",
02316                     recur->frequency(),
02317                     recur->yearDays()[0],
02318                     recurEnd( incidence ) );
02319       if ( recur->duration() >  0 ) {
02320         txt += i18nc( "number of occurrences",
02321                       " (<numid>%1</numid> occurrences)",
02322                       recur->duration() );
02323       }
02324       return txt;
02325     }
02326     return i18ncp( "Recurs every N YEAR[S] on day N",
02327                    "Recurs every year on day <numid>%2</numid>",
02328                    "Recurs every <numid>%1</numid> years"
02329                    " on day <numid>%2</numid>",
02330                    recur->frequency(), recur->yearDays()[0] );
02331   case Recurrence::rYearlyPos:
02332   {
02333     KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
02334     if ( recur->duration() != -1 ) {
02335       txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02336                     "of monthname until end-date",
02337                     "Every year on the %2 %3 of %4 until %5",
02338                     "Every <numid>%1</numid> years on the %2 %3 of %4"
02339                     " until %5",
02340                     recur->frequency(),
02341                     dayList[rule.pos() + 31],
02342                     calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02343                     calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02344                     recurEnd( incidence ) );
02345       if ( recur->duration() >  0 ) {
02346         txt += i18nc( "number of occurrences",
02347                       " (<numid>%1</numid> occurrences)",
02348                       recur->duration() );
02349       }
02350       return txt;
02351     }
02352     return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02353                    "of monthname",
02354                    "Every year on the %2 %3 of %4",
02355                    "Every <numid>%1</numid> years on the %2 %3 of %4",
02356                    recur->frequency(),
02357                    dayList[rule.pos() + 31],
02358                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02359                    calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
02360   }
02361   default:
02362     return i18n( "Incidence recurs" );
02363   }
02364 }
02365 
02366 QString IncidenceFormatter::timeToString( const KDateTime &date,
02367                                           bool shortfmt,
02368                                           const KDateTime::Spec &spec )
02369 {
02370   if ( spec.isValid() ) {
02371 
02372     QString timeZone;
02373     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02374       timeZone = ' ' + spec.timeZone().name();
02375     }
02376 
02377     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
02378   } else {
02379     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
02380   }
02381 }
02382 
02383 QString IncidenceFormatter::dateToString( const KDateTime &date,
02384                                           bool shortfmt,
02385                                           const KDateTime::Spec &spec )
02386 {
02387   if ( spec.isValid() ) {
02388 
02389     QString timeZone;
02390     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02391       timeZone = ' ' + spec.timeZone().name();
02392     }
02393 
02394     return
02395       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
02396                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
02397       timeZone;
02398   } else {
02399     return
02400       KGlobal::locale()->formatDate( date.date(),
02401                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
02402   }
02403 }
02404 
02405 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
02406                                               bool allDay,
02407                                               bool shortfmt,
02408                                               const KDateTime::Spec &spec )
02409 {
02410   if ( allDay ) {
02411     return dateToString( date, shortfmt, spec );
02412   }
02413 
02414   if ( spec.isValid() ) {
02415     QString timeZone;
02416     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02417       timeZone = ' ' + spec.timeZone().name();
02418     }
02419 
02420     return KGlobal::locale()->formatDateTime(
02421       date.toTimeSpec( spec ).dateTime(),
02422       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
02423   } else {
02424     return  KGlobal::locale()->formatDateTime(
02425       date.dateTime(),
02426       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
02427   }
02428 }

KCal Library

Skip menu "KCal Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.8
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal