• 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     foreach ( const QString &c, incidence->comments() ) {
00715       if ( !c.isEmpty() ) {
00716         comments += string2HTML( c );
00717       }
00718     }
00719     if ( !incidence->description().isEmpty() ) {
00720       // use description too
00721       if ( !incidence->descriptionIsRich() ) {
00722         descr = string2HTML( incidence->description() );
00723       } else {
00724         descr = incidence->richDescription();
00725         if ( noHtmlMode ) {
00726           descr = cleanHtml( descr );
00727         }
00728       }
00729     }
00730   }
00731 
00732   if( !descr.isEmpty() ) {
00733     html += "<p>";
00734     html += "<table border=\"0\" style=\"margin-top:4px;\">";
00735     html += "<tr><td><center>" +
00736             eventViewerAddTag( "u", i18n( "Description:" ) ) +
00737             "</center></td></tr>";
00738     html += "<tr><td>" + descr + "</td></tr>";
00739     html += "</table>";
00740   }
00741 
00742   if ( !comments.isEmpty() ) {
00743     html += "<p>";
00744     html += "<table border=\"0\" style=\"margin-top:4px;\">";
00745     html += "<tr><td><center>" +
00746             eventViewerAddTag( "u", i18n( "Comments:" ) ) +
00747             "</center></td></tr>";
00748     html += "<tr>";
00749     if ( comments.count() > 1 ) {
00750       html += "<td><ul>";
00751       for ( int i=0; i < comments.count(); ++i ) {
00752         html += "<li>" + comments[i] + "</li>";
00753       }
00754       html += "</ul></td>";
00755     } else {
00756       html += "<td>" + comments[0] + "</td>";
00757     }
00758     html += "</tr>";
00759     html += "</table>";
00760   }
00761   return html;
00762 }
00763 
00764 static QString invitationDetailsEvent( Event *event, bool noHtmlMode )
00765 {
00766   // Meeting details are formatted into an HTML table
00767   if ( !event ) {
00768     return QString();
00769   }
00770 
00771   QString sSummary = i18n( "Summary unspecified" );
00772   if ( !event->summary().isEmpty() ) {
00773     if ( !event->summaryIsRich() ) {
00774       sSummary = string2HTML( event->summary() );
00775     } else {
00776       sSummary = event->richSummary();
00777       if ( noHtmlMode ) {
00778         sSummary = cleanHtml( sSummary );
00779       }
00780     }
00781   }
00782 
00783   QString sLocation = i18n( "Location unspecified" );
00784   if ( !event->location().isEmpty() ) {
00785     if ( !event->locationIsRich() ) {
00786       sLocation = string2HTML( event->location() );
00787     } else {
00788       sLocation = event->richLocation();
00789       if ( noHtmlMode ) {
00790         sLocation = cleanHtml( sLocation );
00791       }
00792     }
00793   }
00794 
00795   QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
00796   QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
00797 
00798   // Meeting summary & location rows
00799   html += invitationRow( i18n( "What:" ), sSummary );
00800   html += invitationRow( i18n( "Where:" ), sLocation );
00801 
00802   // Meeting Start Time Row
00803   html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) );
00804 
00805   // Meeting End Time Row
00806   html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) );
00807 
00808   // Meeting Duration Row
00809   if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) {
00810     QString tmp;
00811     QTime sDuration( 0, 0, 0 ), t;
00812     int secs = event->dtStart().secsTo( event->dtEnd() );
00813     t = sDuration.addSecs( secs );
00814     if ( t.hour() > 0 ) {
00815       tmp += i18np( "1 hour ", "%1 hours ", t.hour() );
00816     }
00817     if ( t.minute() > 0 ) {
00818       tmp += i18np( "1 minute ", "%1 minutes ", t.minute() );
00819     }
00820 
00821     html += invitationRow( i18n( "Duration:" ), tmp );
00822   }
00823 
00824   if ( event->recurs() ) {
00825     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
00826   }
00827 
00828   html += "</div>";
00829   html += invitationsDetailsIncidence( event, noHtmlMode );
00830 
00831   return html;
00832 }
00833 
00834 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
00835 {
00836   // To-do details are formatted into an HTML table
00837   if ( !todo ) {
00838     return QString();
00839   }
00840 
00841   QString sSummary = i18n( "Summary unspecified" );
00842   QString sDescr = i18n( "Description unspecified" );
00843   if ( ! todo->summary().isEmpty() ) {
00844     sSummary = todo->richSummary();
00845     if ( noHtmlMode ) {
00846       sSummary = cleanHtml( sSummary );
00847     }
00848   }
00849   if ( ! todo->description().isEmpty() ) {
00850     sDescr = todo->description();
00851     if ( noHtmlMode ) {
00852       sDescr = cleanHtml( sDescr );
00853     }
00854   }
00855   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00856                  "cellpadding=\"1\" cellspacing=\"1\">";
00857   html += invitationRow( i18n( "Summary:" ), sSummary );
00858   html += invitationRow( i18n( "Description:" ), sDescr );
00859   html += "</table>\n";
00860   html += invitationsDetailsIncidence( todo, noHtmlMode );
00861 
00862   return html;
00863 }
00864 
00865 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
00866 {
00867   if ( !journal ) {
00868     return QString();
00869   }
00870 
00871   QString sSummary = i18n( "Summary unspecified" );
00872   QString sDescr = i18n( "Description unspecified" );
00873   if ( ! journal->summary().isEmpty() ) {
00874     sSummary = journal->richSummary();
00875     if ( noHtmlMode ) {
00876       sSummary = cleanHtml( sSummary );
00877     }
00878   }
00879   if ( ! journal->description().isEmpty() ) {
00880     sDescr = journal->richDescription();
00881     if ( noHtmlMode ) {
00882       sDescr = cleanHtml( sDescr );
00883     }
00884   }
00885   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00886                  "cellpadding=\"1\" cellspacing=\"1\">";
00887   html += invitationRow( i18n( "Summary:" ), sSummary );
00888   html += invitationRow( i18n( "Date:" ),
00889                          IncidenceFormatter::dateToString( journal->dtStart(), false,
00890                                                            journal->dtStart().timeSpec() ) );
00891   html += invitationRow( i18n( "Description:" ), sDescr );
00892   html += "</table>\n";
00893   html += invitationsDetailsIncidence( journal, noHtmlMode );
00894 
00895   return html;
00896 }
00897 
00898 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00899 {
00900   if ( !fb ) {
00901     return QString();
00902   }
00903 
00904   QString html = "<table border=\"0\" width=\"80%\" align=\"center\" "
00905                  "cellpadding=\"1\" cellspacing=\"1\">";
00906   html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() );
00907   html += invitationRow( i18n( "Start date:" ),
00908                          IncidenceFormatter::dateToString( fb->dtStart(),
00909                                                            true,
00910                                                            fb->dtStart().timeSpec() ) );
00911   html += invitationRow( i18n( "End date:" ),
00912                          KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) );
00913   html += "<tr><td colspan=2><hr></td></tr>\n";
00914   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00915 
00916   QList<Period> periods = fb->busyPeriods();
00917   QList<Period>::iterator it;
00918   for ( it = periods.begin(); it != periods.end(); ++it ) {
00919     Period per = *it;
00920     if ( per.hasDuration() ) {
00921       int dur = per.duration().asSeconds();
00922       QString cont;
00923       if ( dur >= 3600 ) {
00924         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00925         dur %= 3600;
00926       }
00927       if ( dur >= 60 ) {
00928         cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
00929         dur %= 60;
00930       }
00931       if ( dur > 0 ) {
00932         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00933       }
00934       html += invitationRow(
00935         QString(), i18nc( "startDate for duration", "%1 for %2",
00936                           KGlobal::locale()->formatDateTime(
00937                             per.start().dateTime(), KLocale::LongDate ), cont ) );
00938     } else {
00939       QString cont;
00940       if ( per.start().date() == per.end().date() ) {
00941         cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00942                       KGlobal::locale()->formatDate( per.start().date() ),
00943                       KGlobal::locale()->formatTime( per.start().time() ),
00944                       KGlobal::locale()->formatTime( per.end().time() ) );
00945       } else {
00946         cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
00947                       KGlobal::locale()->formatDateTime(
00948                         per.start().dateTime(), KLocale::LongDate ),
00949                       KGlobal::locale()->formatDateTime(
00950                         per.end().dateTime(), KLocale::LongDate ) );
00951       }
00952 
00953       html += invitationRow( QString(), cont );
00954     }
00955   }
00956 
00957   html += "</table>\n";
00958   return html;
00959 }
00960 
00961 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
00962 {
00963   if ( !msg || !event ) {
00964     return QString();
00965   }
00966 
00967   switch ( msg->method() ) {
00968   case iTIPPublish:
00969     return i18n( "This event has been published" );
00970   case iTIPRequest:
00971     if ( event->revision() > 0 ) {
00972       //TODO: 4.4, remove the h3 tag
00973       return i18n( "<h3>This meeting has been updated</h3>" );
00974     } else {
00975       return i18n( "You have been invited to this meeting" );
00976     }
00977   case iTIPRefresh:
00978     return i18n( "This invitation was refreshed" );
00979   case iTIPCancel:
00980     return i18n( "This meeting has been canceled" );
00981   case iTIPAdd:
00982     return i18n( "Addition to the meeting invitation" );
00983   case iTIPReply:
00984   {
00985     Attendee::List attendees = event->attendees();
00986     if( attendees.count() == 0 ) {
00987       kDebug() << "No attendees in the iCal reply!";
00988       return QString();
00989     }
00990     if ( attendees.count() != 1 ) {
00991       kDebug() << "Warning: attendeecount in the reply should be 1"
00992                << "but is" << attendees.count();
00993     }
00994     Attendee *attendee = *attendees.begin();
00995     QString attendeeName = attendee->name();
00996     if ( attendeeName.isEmpty() ) {
00997       attendeeName = attendee->email();
00998     }
00999     if ( attendeeName.isEmpty() ) {
01000       attendeeName = i18n( "Sender" );
01001     }
01002 
01003     QString delegatorName, dummy;
01004     KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
01005     if ( delegatorName.isEmpty() ) {
01006       delegatorName = attendee->delegator();
01007     }
01008 
01009     switch( attendee->status() ) {
01010     case Attendee::NeedsAction:
01011       return i18n( "%1 indicates this invitation still needs some action", attendeeName );
01012     case Attendee::Accepted:
01013       if ( delegatorName.isEmpty() ) {
01014         return i18n( "%1 accepts this meeting invitation", attendeeName );
01015       }
01016       return i18n( "%1 accepts this meeting invitation on behalf of %2",
01017                    attendeeName, delegatorName );
01018     case Attendee::Tentative:
01019       if ( delegatorName.isEmpty() ) {
01020         return i18n( "%1 tentatively accepts this meeting invitation", attendeeName );
01021       }
01022       return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2",
01023                    attendeeName, delegatorName );
01024     case Attendee::Declined:
01025       if ( delegatorName.isEmpty() ) {
01026         return i18n( "%1 declines this meeting invitation", attendeeName );
01027       }
01028       return i18n( "%1 declines this meeting invitation on behalf of %2",
01029                    attendeeName, delegatorName );
01030     case Attendee::Delegated:
01031     {
01032       QString delegate, dummy;
01033       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01034       if ( delegate.isEmpty() ) {
01035         delegate = attendee->delegate();
01036       }
01037       if ( !delegate.isEmpty() ) {
01038         return i18n( "%1 has delegated this meeting invitation to %2", attendeeName, delegate );
01039       }
01040       return i18n( "%1 has delegated this meeting invitation", attendeeName );
01041     }
01042     case Attendee::Completed:
01043       return i18n( "This meeting invitation is now completed" );
01044     case Attendee::InProcess:
01045       return i18n( "%1 is still processing the invitation", attendeeName );
01046     default:
01047       return i18n( "Unknown response to this meeting invitation" );
01048     }
01049     break;
01050   }
01051   case iTIPCounter:
01052     return i18n( "Sender makes this counter proposal" );
01053   case iTIPDeclineCounter:
01054     return i18n( "Sender declines the counter proposal" );
01055   case iTIPNoMethod:
01056     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01057   }
01058   return QString();
01059 }
01060 
01061 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01062 {
01063   if ( !msg || !todo ) {
01064     return QString();
01065   }
01066 
01067   switch ( msg->method() ) {
01068   case iTIPPublish:
01069     return i18n( "This to-do has been published" );
01070   case iTIPRequest:
01071     if ( todo->revision() > 0 ) {
01072       return i18n( "This to-do has been updated" );
01073     } else {
01074       return i18n( "You have been assigned this to-do" );
01075     }
01076   case iTIPRefresh:
01077     return i18n( "This to-do was refreshed" );
01078   case iTIPCancel:
01079     return i18n( "This to-do was canceled" );
01080   case iTIPAdd:
01081     return i18n( "Addition to the to-do" );
01082   case iTIPReply:
01083   {
01084     Attendee::List attendees = todo->attendees();
01085     if ( attendees.count() == 0 ) {
01086       kDebug() << "No attendees in the iCal reply!";
01087       return QString();
01088     }
01089     if ( attendees.count() != 1 ) {
01090       kDebug() << "Warning: attendeecount in the reply should be 1"
01091                << "but is" << attendees.count();
01092     }
01093     Attendee *attendee = *attendees.begin();
01094     switch( attendee->status() ) {
01095     case Attendee::NeedsAction:
01096       return i18n( "Sender indicates this to-do assignment still needs some action" );
01097     case Attendee::Accepted:
01098       return i18n( "Sender accepts this to-do" );
01099     case Attendee::Tentative:
01100       return i18n( "Sender tentatively accepts this to-do" );
01101     case Attendee::Declined:
01102       return i18n( "Sender declines this to-do" );
01103     case Attendee::Delegated:
01104     {
01105       QString delegate, dummy;
01106       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01107       if ( delegate.isEmpty() ) {
01108         delegate = attendee->delegate();
01109       }
01110       if ( !delegate.isEmpty() ) {
01111         return i18n( "Sender has delegated this request for the to-do to %1", delegate );
01112       }
01113       return i18n( "Sender has delegated this request for the to-do " );
01114     }
01115     case Attendee::Completed:
01116       return i18n( "The request for this to-do is now completed" );
01117     case Attendee::InProcess:
01118       return i18n( "Sender is still processing the invitation" );
01119     default:
01120       return i18n( "Unknown response to this to-do" );
01121     }
01122     break;
01123   }
01124   case iTIPCounter:
01125     return i18n( "Sender makes this counter proposal" );
01126   case iTIPDeclineCounter:
01127     return i18n( "Sender declines the counter proposal" );
01128   case iTIPNoMethod:
01129     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01130   }
01131   return QString();
01132 }
01133 
01134 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01135 {
01136   // TODO: Several of the methods are not allowed for journals, so remove them.
01137   if ( !msg || !journal ) {
01138     return QString();
01139   }
01140 
01141   switch ( msg->method() ) {
01142   case iTIPPublish:
01143     return i18n( "This journal has been published" );
01144   case iTIPRequest:
01145     return i18n( "You have been assigned this journal" );
01146   case iTIPRefresh:
01147     return i18n( "This journal was refreshed" );
01148   case iTIPCancel:
01149     return i18n( "This journal was canceled" );
01150   case iTIPAdd:
01151     return i18n( "Addition to the journal" );
01152   case iTIPReply:
01153   {
01154     Attendee::List attendees = journal->attendees();
01155     if ( attendees.count() == 0 ) {
01156       kDebug() << "No attendees in the iCal reply!";
01157       return QString();
01158     }
01159 
01160     if( attendees.count() != 1 ) {
01161       kDebug() << "Warning: attendeecount in the reply should be 1"
01162                << "but is" << attendees.count();
01163     }
01164 
01165     Attendee *attendee = *attendees.begin();
01166     switch( attendee->status() ) {
01167     case Attendee::NeedsAction:
01168       return i18n( "Sender indicates this journal assignment still needs some action" );
01169     case Attendee::Accepted:
01170       return i18n( "Sender accepts this journal" );
01171     case Attendee::Tentative:
01172       return i18n( "Sender tentatively accepts this journal" );
01173     case Attendee::Declined:
01174       return i18n( "Sender declines this journal" );
01175     case Attendee::Delegated:
01176       return i18n( "Sender has delegated this request for the journal" );
01177     case Attendee::Completed:
01178       return i18n( "The request for this journal is now completed" );
01179     case Attendee::InProcess:
01180       return i18n( "Sender is still processing the invitation" );
01181     default:
01182       return i18n( "Unknown response to this journal" );
01183     }
01184     break;
01185   }
01186   case iTIPCounter:
01187     return i18n( "Sender makes this counter proposal" );
01188   case iTIPDeclineCounter:
01189     return i18n( "Sender declines the counter proposal" );
01190   case iTIPNoMethod:
01191     return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01192   }
01193   return QString();
01194 }
01195 
01196 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01197 {
01198   if ( !msg || !fb ) {
01199     return QString();
01200   }
01201 
01202   switch ( msg->method() ) {
01203   case iTIPPublish:
01204     return i18n( "This free/busy list has been published" );
01205   case iTIPRequest:
01206     return i18n( "The free/busy list has been requested" );
01207   case iTIPRefresh:
01208     return i18n( "This free/busy list was refreshed" );
01209   case iTIPCancel:
01210     return i18n( "This free/busy list was canceled" );
01211   case iTIPAdd:
01212     return i18n( "Addition to the free/busy list" );
01213   case iTIPNoMethod:
01214   default:
01215     return i18n( "Error: Free/Busy iMIP message with unknown method: '%1'", msg->method() );
01216   }
01217 }
01218 //@endcond
01219 
01220 //@cond PRIVATE
01221 class KCal::IncidenceFormatter::ScheduleMessageVisitor
01222   : public IncidenceBase::Visitor
01223 {
01224   public:
01225     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01226     bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01227     {
01228       mMessage = msg;
01229       return incidence->accept( *this );
01230     }
01231     QString result() const { return mResult; }
01232 
01233   protected:
01234     QString mResult;
01235     ScheduleMessage *mMessage;
01236 };
01237 
01238 class KCal::IncidenceFormatter::InvitationHeaderVisitor :
01239       public IncidenceFormatter::ScheduleMessageVisitor
01240 {
01241   protected:
01242     bool visit( Event *event )
01243     {
01244       mResult = invitationHeaderEvent( event, mMessage );
01245       return !mResult.isEmpty();
01246     }
01247     bool visit( Todo *todo )
01248     {
01249       mResult = invitationHeaderTodo( todo, mMessage );
01250       return !mResult.isEmpty();
01251     }
01252     bool visit( Journal *journal )
01253     {
01254       mResult = invitationHeaderJournal( journal, mMessage );
01255       return !mResult.isEmpty();
01256     }
01257     bool visit( FreeBusy *fb )
01258     {
01259       mResult = invitationHeaderFreeBusy( fb, mMessage );
01260       return !mResult.isEmpty();
01261     }
01262 };
01263 
01264 class KCal::IncidenceFormatter::InvitationBodyVisitor
01265   : public IncidenceFormatter::ScheduleMessageVisitor
01266 {
01267   public:
01268     InvitationBodyVisitor( bool noHtmlMode )
01269       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) { }
01270 
01271   protected:
01272     bool visit( Event *event )
01273     {
01274       mResult = invitationDetailsEvent( event, mNoHtmlMode );
01275       return !mResult.isEmpty();
01276     }
01277     bool visit( Todo *todo )
01278     {
01279       mResult = invitationDetailsTodo( todo, mNoHtmlMode );
01280       return !mResult.isEmpty();
01281     }
01282     bool visit( Journal *journal )
01283     {
01284       mResult = invitationDetailsJournal( journal, mNoHtmlMode );
01285       return !mResult.isEmpty();
01286     }
01287     bool visit( FreeBusy *fb )
01288     {
01289       mResult = invitationDetailsFreeBusy( fb );
01290       return !mResult.isEmpty();
01291     }
01292 
01293   private:
01294     bool mNoHtmlMode;
01295 };
01296 //@endcond
01297 
01298 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
01299 {
01300   return id;
01301 }
01302 
01303 //@cond PRIVATE
01304 class IncidenceFormatter::IncidenceCompareVisitor
01305   : public IncidenceBase::Visitor
01306 {
01307   public:
01308     IncidenceCompareVisitor() : mExistingIncidence( 0 ) {}
01309     bool act( IncidenceBase *incidence, Incidence *existingIncidence )
01310     {
01311       if ( !existingIncidence ) {
01312         return false;
01313       }
01314       Incidence *inc = dynamic_cast<Incidence *>( incidence );
01315       if ( inc && inc->revision() <= existingIncidence->revision() ) {
01316         return false;
01317       }
01318       mExistingIncidence = existingIncidence;
01319       return incidence->accept( *this );
01320     }
01321 
01322     QString result() const
01323     {
01324       if ( mChanges.isEmpty() ) {
01325         return QString();
01326       }
01327       QString html = "<div align=\"left\"><ul><li>";
01328       html += mChanges.join( "</li><li>" );
01329       html += "</li><ul></div>";
01330       return html;
01331     }
01332 
01333   protected:
01334     bool visit( Event *event )
01335     {
01336       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01337       compareIncidences( event, mExistingIncidence );
01338       return !mChanges.isEmpty();
01339     }
01340     bool visit( Todo *todo )
01341     {
01342       compareIncidences( todo, mExistingIncidence );
01343       return !mChanges.isEmpty();
01344     }
01345     bool visit( Journal *journal )
01346     {
01347       compareIncidences( journal, mExistingIncidence );
01348       return !mChanges.isEmpty();
01349     }
01350     bool visit( FreeBusy *fb )
01351     {
01352       Q_UNUSED( fb );
01353       return !mChanges.isEmpty();
01354     }
01355 
01356   private:
01357     void compareEvents( Event *newEvent, Event *oldEvent )
01358     {
01359       if ( !oldEvent || !newEvent ) {
01360         return;
01361       }
01362       if ( oldEvent->dtStart() != newEvent->dtStart() ||
01363            oldEvent->allDay() != newEvent->allDay() ) {
01364         mChanges += i18n( "The begin of the meeting has been changed from %1 to %2",
01365                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01366       }
01367       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01368            oldEvent->allDay() != newEvent->allDay() ) {
01369         mChanges += i18n( "The end of the meeting has been changed from %1 to %2",
01370                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01371       }
01372     }
01373 
01374     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01375     {
01376       if ( !oldInc || !newInc ) {
01377         return;
01378       }
01379 
01380       if ( oldInc->summary() != newInc->summary() ) {
01381         mChanges += i18n( "The summary has been changed to: \"%1\"",
01382                           newInc->richSummary() );
01383       }
01384 
01385       if ( oldInc->location() != newInc->location() ) {
01386         mChanges += i18n( "The location has been changed to: \"%1\"",
01387                           newInc->richLocation() );
01388       }
01389 
01390       if ( oldInc->description() != newInc->description() ) {
01391         mChanges += i18n( "The description has been changed to: \"%1\"",
01392                           newInc->richDescription() );
01393       }
01394 
01395       Attendee::List oldAttendees = oldInc->attendees();
01396       Attendee::List newAttendees = newInc->attendees();
01397       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01398             it != newAttendees.constEnd(); ++it ) {
01399         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01400         if ( !oldAtt ) {
01401           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01402         } else {
01403           if ( oldAtt->status() != (*it)->status() ) {
01404             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01405                               (*it)->fullName(), (*it)->statusStr() );
01406           }
01407         }
01408       }
01409 
01410       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01411             it != oldAttendees.constEnd(); ++it ) {
01412         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01413         if ( !newAtt ) {
01414           mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01415         }
01416       }
01417     }
01418 
01419   private:
01420     Incidence *mExistingIncidence;
01421     QStringList mChanges;
01422 };
01423 //@endcond
01424 
01425 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01426 {
01427   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01428   return res.arg( generateLinkURL( id ) ).arg( text );
01429   return res;
01430 }
01431 
01432 Calendar *InvitationFormatterHelper::calendar() const
01433 {
01434   return 0;
01435 }
01436 
01437 //@cond PRIVATE
01438 // Check if the given incidence is likely one that we own instead one from
01439 // a shared calendar (Kolab-specific)
01440 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01441 {
01442   CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01443   if ( !cal || !incidence ) {
01444     return true;
01445   }
01446 
01447   ResourceCalendar *res = cal->resource( incidence );
01448   if ( !res ) {
01449     return true;
01450   }
01451 
01452   const QString subRes = res->subresourceIdentifier( incidence );
01453   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01454     return false;
01455   }
01456   return true;
01457 }
01458 
01459 static QString formatICalInvitationHelper( QString invitation, Calendar *mCalendar,
01460     InvitationFormatterHelper *helper, bool noHtmlMode )
01461 {
01462   if ( invitation.isEmpty() ) {
01463     return QString();
01464   }
01465 
01466   ICalFormat format;
01467   // parseScheduleMessage takes the tz from the calendar,
01468   // no need to set it manually here for the format!
01469   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01470 
01471   if( !msg ) {
01472     kDebug() << "Failed to parse the scheduling message";
01473     Q_ASSERT( format.exception() );
01474     kDebug() << format.exception()->message();
01475     return QString();
01476   }
01477 
01478   IncidenceBase *incBase = msg->event();
01479   incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
01480 
01481   Incidence *existingIncidence = 0;
01482   if ( helper->calendar() ) {
01483     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01484     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
01485       existingIncidence = 0;
01486     }
01487     if ( !existingIncidence ) {
01488       const Incidence::List list = helper->calendar()->incidences();
01489       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01490         if ( (*it)->schedulingID() == incBase->uid() &&
01491              incidenceOwnedByMe( helper->calendar(), *it ) ) {
01492           existingIncidence = *it;
01493           break;
01494         }
01495       }
01496     }
01497   }
01498 
01499   // First make the text of the message
01500   QString html;
01501   html += "<div align=\"center\" style=\"border:solid 1px; width:80%;\">";
01502   html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01503 
01504   IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
01505   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01506   if ( !headerVisitor.act( incBase, msg ) ) {
01507     return QString();
01508   }
01509   html += "<tr>" + eventViewerAddTag( "h3", headerVisitor.result() ) + "</tr>";
01510 
01511   IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode );
01512   if ( !bodyVisitor.act( incBase, msg ) ) {
01513     return QString();
01514   }
01515   html += bodyVisitor.result();
01516 
01517   if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well?
01518     IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
01519     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01520       html +=
01521         i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
01522       html += compareVisitor.result();
01523     }
01524   }
01525 
01526   // Add groupware links
01527 
01528   html += "<p>";
01529   html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
01530 
01531   //TODO: 4.4, in tdOpen, change border-width:0px to border-width:2px and also
01532   //remove the [] on the button strings: "Accept", "Decline", "Counter", etc.
01533   const QString tdOpen = "<td style=\"border-width:0px;border-style:outset\">";
01534   const QString tdClose = "</td>";
01535   Incidence *incidence = dynamic_cast<Incidence*>( incBase );
01536   switch ( msg->method() ) {
01537   case iTIPPublish:
01538   case iTIPRequest:
01539   case iTIPRefresh:
01540   case iTIPAdd:
01541   {
01542     if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
01543       html += tdOpen;
01544       if ( incBase ) {
01545         if ( incBase->type() == "Todo" ) {
01546           //TODO: 4.4, remove the []
01547           html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01548         } else {
01549           html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01550         }
01551       }
01552       html += tdClose;
01553     }
01554 
01555     if ( incidence && !existingIncidence ) {
01556       // Accept
01557       html += tdOpen;
01558         //TODO: 4.4, remove the []
01559       html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) );
01560       html += tdClose;
01561 
01562       // Accept conditionally
01563       html += tdOpen;
01564       //TODO: 4.4, remove the []
01565       html += helper->makeLink( "accept_conditionally",
01566                                 i18nc( "Accept conditionally", "[Accept cond.]" ) );
01567       html += tdClose;
01568 
01569       // Counter proposal
01570       html += tdOpen;
01571       //TODO: 4.4, remove the []
01572       html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01573       html += tdClose;
01574 
01575       // Decline
01576       html += tdOpen;
01577       //TODO: 4.4, remove the []
01578       html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) );
01579       html += tdClose;
01580 
01581       // Delegate
01582       html += tdOpen;
01583       //TODO: 4.4, remove the []
01584       html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) );
01585       html += tdClose;
01586 
01587       // Forward
01588       html += tdOpen;
01589       //TODO: 4.4, remove the []
01590       html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) );
01591       html += tdClose;
01592 
01593       // Check in calendar
01594       if ( incBase && incBase->type() == "Event" ) {
01595         html += tdOpen;
01596         //TODO: 4.4, remove the []
01597         //TODO: 4.4, change to "Check calendar"
01598         html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01599         html += tdClose;
01600       }
01601     }
01602     break;
01603   }
01604 
01605   case iTIPCancel:
01606     // Cancel event from my calendar
01607     html += tdOpen;
01608     //TODO: 4.4, remove the []
01609     html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01610     html += tdClose;
01611     break;
01612 
01613   case iTIPReply:
01614     // Enter this into my calendar
01615     html += tdOpen;
01616     //TODO: 4.4, remove the []
01617     //TODO: 4.4, change string to "Enter this response into my..."
01618     if ( incBase ) {
01619       if ( incBase->type() == "Todo" ) {
01620         html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01621       } else {
01622         html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01623       }
01624     }
01625     html += tdClose;
01626     break;
01627 
01628   case iTIPCounter:
01629     html += tdOpen;
01630     //TODO: 4.4, remove the []
01631     html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
01632     html += tdClose;
01633 
01634     html += tdOpen;
01635     //TODO: 4.4, remove the []
01636     html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
01637     html += tdClose;
01638 
01639     html += tdOpen;
01640     //TODO: 4.4, remove the []
01641     //TODO: 4.4, change string to "Check calendar"
01642     html += helper->makeLink( "check_calendar", i18n( "[Check my calendar]" ) );
01643     html += tdClose;
01644     break;
01645 
01646   case iTIPDeclineCounter:
01647   case iTIPNoMethod:
01648     break;
01649   }
01650 
01651   // close the groupware table
01652   html += "</tr></table>";
01653 
01654   // close the top-level table
01655   html += "</table></div>";
01656   kDebug() << html;
01657   return html;
01658 }
01659 //@endcond
01660 
01661 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01662     InvitationFormatterHelper *helper )
01663 {
01664   return formatICalInvitationHelper( invitation, mCalendar, helper, false );
01665 }
01666 
01667 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation, Calendar *mCalendar,
01668     InvitationFormatterHelper *helper )
01669 {
01670   return formatICalInvitationHelper( invitation, mCalendar, helper, true );
01671 }
01672 
01673 /*******************************************************************
01674  *  Helper functions for the Incidence tooltips
01675  *******************************************************************/
01676 
01677 //@cond PRIVATE
01678 class KCal::IncidenceFormatter::ToolTipVisitor
01679   : public IncidenceBase::Visitor
01680 {
01681   public:
01682     ToolTipVisitor()
01683       : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
01684 
01685     bool act( IncidenceBase *incidence, bool richText=true, KDateTime::Spec spec=KDateTime::Spec() )
01686     {
01687       mRichText = richText;
01688       mSpec = spec;
01689       mResult = "";
01690       return incidence ? incidence->accept( *this ) : false;
01691     }
01692     QString result() const { return mResult; }
01693 
01694   protected:
01695     bool visit( Event *event );
01696     bool visit( Todo *todo );
01697     bool visit( Journal *journal );
01698     bool visit( FreeBusy *fb );
01699 
01700     QString dateRangeText( Event *event );
01701     QString dateRangeText( Todo *todo );
01702     QString dateRangeText( Journal *journal );
01703     QString dateRangeText( FreeBusy *fb );
01704 
01705     QString generateToolTip( Incidence *incidence, QString dtRangeText );
01706 
01707   protected:
01708     bool mRichText;
01709     KDateTime::Spec mSpec;
01710     QString mResult;
01711 };
01712 
01713 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event )
01714 {
01715   //FIXME: support mRichText==false
01716   QString ret;
01717   QString tmp;
01718   if ( event->isMultiDay() ) {
01719 
01720     tmp = IncidenceFormatter::dateToString( event->dtStart(), true, mSpec );
01721     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
01722 
01723     tmp = IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec );
01724     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
01725 
01726   } else {
01727 
01728     ret += "<br>" +
01729       i18n( "<i>Date:</i> %1", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) );
01730     if ( !event->allDay() ) {
01731       const QString dtStartTime = IncidenceFormatter::timeToString( event->dtStart(), true, mSpec );
01732       const QString dtEndTime = IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec );
01733       if ( dtStartTime == dtEndTime ) {
01734         // to prevent 'Time: 17:00 - 17:00'
01735         tmp = "<br>" +
01736               i18nc( "time for event", "<i>Time:</i> %1",
01737                      dtStartTime );
01738       } else {
01739         tmp = "<br>" +
01740               i18nc( "time range for event",
01741                      "<i>Time:</i> %1 - %2",
01742                      dtStartTime, dtEndTime );
01743       }
01744       ret += tmp;
01745     }
01746   }
01747   return ret.replace( ' ', "&nbsp;" );
01748 }
01749 
01750 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo )
01751 {
01752   //FIXME: support mRichText==false
01753   QString ret;
01754   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01755     // No need to add <i> here. This is separated issue and each line
01756     // is very visible on its own. On the other hand... Yes, I like it
01757     // italics here :)
01758     ret += "<br>" + i18n( "<i>Start:</i> %1",
01759                           IncidenceFormatter::dateToString( todo->dtStart( false ), true, mSpec ) );
01760   }
01761   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01762     ret += "<br>" + i18n( "<i>Due:</i> %1",
01763                           IncidenceFormatter::dateTimeToString( todo->dtDue(),
01764                                                                 todo->allDay(),
01765                                                                 true, mSpec ) );
01766   }
01767   if ( todo->isCompleted() ) {
01768     ret += "<br>" +
01769            i18n( "<i>Completed:</i> %1", todo->completedStr() );
01770   } else {
01771     ret += "<br>" +
01772            i18nc( "percent complete", "%1 % completed", todo->percentComplete() );
01773   }
01774 
01775   return ret.replace( ' ', "&nbsp;" );
01776 }
01777 
01778 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
01779 {
01780   //FIXME: support mRichText==false
01781   QString ret;
01782   if ( journal->dtStart().isValid() ) {
01783     ret += "<br>" +
01784            i18n( "<i>Date:</i> %1",
01785                  IncidenceFormatter::dateToString( journal->dtStart(), false, mSpec ) );
01786   }
01787   return ret.replace( ' ', "&nbsp;" );
01788 }
01789 
01790 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01791 {
01792   //FIXME: support mRichText==false
01793   QString ret;
01794   ret = "<br>" +
01795         i18n( "<i>Period start:</i> %1",
01796               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
01797   ret += "<br>" +
01798          i18n( "<i>Period start:</i> %1",
01799                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
01800   return ret.replace( ' ', "&nbsp;" );
01801 }
01802 
01803 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01804 {
01805   mResult = generateToolTip( event, dateRangeText( event ) );
01806   return !mResult.isEmpty();
01807 }
01808 
01809 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01810 {
01811   mResult = generateToolTip( todo, dateRangeText( todo ) );
01812   return !mResult.isEmpty();
01813 }
01814 
01815 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01816 {
01817   mResult = generateToolTip( journal, dateRangeText( journal ) );
01818   return !mResult.isEmpty();
01819 }
01820 
01821 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01822 {
01823   //FIXME: support mRichText==false
01824   mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
01825   mResult += dateRangeText( fb );
01826   mResult += "</qt>";
01827   return !mResult.isEmpty();
01828 }
01829 
01830 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
01831                                                              QString dtRangeText )
01832 {
01833   //FIXME: support mRichText==false
01834   if ( !incidence ) {
01835     return QString();
01836   }
01837 
01838   QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
01839 
01840   tmp += dtRangeText;
01841 
01842   if ( !incidence->location().isEmpty() ) {
01843     // Put Location: in italics
01844     tmp += "<br>" +
01845            i18n( "<i>Location:</i>&nbsp;%1", incidence->richLocation() );
01846   }
01847 
01848   if ( !incidence->description().isEmpty() ) {
01849     QString desc( incidence->description() );
01850     if ( !incidence->descriptionIsRich() ) {
01851       if ( desc.length() > 120 ) {
01852         desc = desc.left( 120 ) + "...";
01853       }
01854       desc = Qt::escape( desc ).replace( '\n', "<br>" );
01855     } else {
01856       // TODO: truncate the description when it's rich text
01857     }
01858     tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
01859   }
01860   tmp += "</qt>";
01861   return tmp;
01862 }
01863 //@endcond
01864 
01865 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence,
01866                                            bool richText )
01867 {
01868   return toolTipStr( incidence, richText, KDateTime::Spec() );
01869 }
01870 
01871 QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence,
01872                                         bool richText, KDateTime::Spec spec )
01873 {
01874   ToolTipVisitor v;
01875   if ( v.act( incidence, richText, spec ) ) {
01876     return v.result();
01877   } else {
01878     return QString();
01879   }
01880 }
01881 
01882 /*******************************************************************
01883  *  Helper functions for the Incidence tooltips
01884  *******************************************************************/
01885 
01886 //@cond PRIVATE
01887 static QString mailBodyIncidence( Incidence *incidence )
01888 {
01889   QString body;
01890   if ( !incidence->summary().isEmpty() ) {
01891     body += i18n( "Summary: %1\n", incidence->richSummary() );
01892   }
01893   if ( !incidence->organizer().isEmpty() ) {
01894     body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
01895   }
01896   if ( !incidence->location().isEmpty() ) {
01897     body += i18n( "Location: %1\n", incidence->richLocation() );
01898   }
01899   return body;
01900 }
01901 //@endcond
01902 
01903 //@cond PRIVATE
01904 class KCal::IncidenceFormatter::MailBodyVisitor
01905   : public IncidenceBase::Visitor
01906 {
01907   public:
01908     MailBodyVisitor()
01909       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
01910 
01911     bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
01912     {
01913       mSpec = spec;
01914       mResult = "";
01915       return incidence ? incidence->accept( *this ) : false;
01916     }
01917     QString result() const
01918     {
01919       return mResult;
01920     }
01921 
01922   protected:
01923     bool visit( Event *event );
01924     bool visit( Todo *todo );
01925     bool visit( Journal *journal );
01926     bool visit( FreeBusy * )
01927     {
01928       mResult = i18n( "This is a Free Busy Object" );
01929       return !mResult.isEmpty();
01930     }
01931   protected:
01932     KDateTime::Spec mSpec;
01933     QString mResult;
01934 };
01935 
01936 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
01937 {
01938   QString recurrence[]= {
01939     i18nc( "no recurrence", "None" ),
01940     i18nc( "event recurs by minutes", "Minutely" ),
01941     i18nc( "event recurs by hours", "Hourly" ),
01942     i18nc( "event recurs by days", "Daily" ),
01943     i18nc( "event recurs by weeks", "Weekly" ),
01944     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
01945     i18nc( "event recurs same day each month", "Monthly Same Day" ),
01946     i18nc( "event recurs same month each year", "Yearly Same Month" ),
01947     i18nc( "event recurs same day each year", "Yearly Same Day" ),
01948     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
01949   };
01950 
01951   mResult = mailBodyIncidence( event );
01952   mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( event->dtStart(), true,
01953                                                                          mSpec ) );
01954   if ( !event->allDay() ) {
01955     mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( event->dtStart(),
01956                                                                            true, mSpec ) );
01957   }
01958   if ( event->dtStart() != event->dtEnd() ) {
01959     mResult += i18n( "End Date: %1\n", IncidenceFormatter::dateToString( event->dtEnd(), true,
01960                                                                          mSpec ) );
01961   }
01962   if ( !event->allDay() ) {
01963     mResult += i18n( "End Time: %1\n", IncidenceFormatter::timeToString( event->dtEnd(), true,
01964                                                                          mSpec ) );
01965   }
01966   if ( event->recurs() ) {
01967     Recurrence *recur = event->recurrence();
01968     // TODO: Merge these two to one of the form "Recurs every 3 days"
01969     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
01970     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
01971 
01972     if ( recur->duration() > 0 ) {
01973       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
01974       mResult += '\n';
01975     } else {
01976       if ( recur->duration() != -1 ) {
01977 // TODO_Recurrence: What to do with all-day
01978         QString endstr;
01979         if ( event->allDay() ) {
01980           endstr = KGlobal::locale()->formatDate( recur->endDate() );
01981         } else {
01982           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
01983         }
01984         mResult += i18n( "Repeat until: %1\n", endstr );
01985       } else {
01986         mResult += i18n( "Repeats forever\n" );
01987       }
01988     }
01989   }
01990 
01991   QString details = event->richDescription();
01992   if ( !details.isEmpty() ) {
01993     mResult += i18n( "Details:\n%1\n", details );
01994   }
01995   return !mResult.isEmpty();
01996 }
01997 
01998 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
01999 {
02000   mResult = mailBodyIncidence( todo );
02001 
02002   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02003     mResult += i18n( "Start Date: %1\n",
02004                      IncidenceFormatter::dateToString( todo->dtStart(false), true, mSpec ) );
02005     if ( !todo->allDay() ) {
02006       mResult += i18n( "Start Time: %1\n",
02007                        IncidenceFormatter::timeToString( todo->dtStart(false), true, mSpec ) );
02008     }
02009   }
02010   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02011     mResult += i18n( "Due Date: %1\n",
02012                      IncidenceFormatter::dateToString( todo->dtDue(), true, mSpec ) );
02013     if ( !todo->allDay() ) {
02014       mResult += i18n( "Due Time: %1\n",
02015                        IncidenceFormatter::timeToString( todo->dtDue(), true, mSpec ) );
02016     }
02017   }
02018   QString details = todo->richDescription();
02019   if ( !details.isEmpty() ) {
02020     mResult += i18n( "Details:\n%1\n", details );
02021   }
02022   return !mResult.isEmpty();
02023 }
02024 
02025 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02026 {
02027   mResult = mailBodyIncidence( journal );
02028   mResult += i18n( "Date: %1\n", IncidenceFormatter::dateToString( journal->dtStart(), true,
02029                                                                 mSpec ) );
02030 
02031   if ( !journal->allDay() ) {
02032     mResult += i18n( "Time: %1\n",
02033                      IncidenceFormatter::timeToString( journal->dtStart(), true, mSpec ) );
02034 
02035   }
02036   if ( !journal->description().isEmpty() ) {
02037     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
02038   }
02039   return !mResult.isEmpty();
02040 }
02041 //@endcond
02042 
02043 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02044 {
02045   return mailBodyStr( incidence, KDateTime::Spec() );
02046 }
02047 
02048 QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence,
02049                                          KDateTime::Spec spec )
02050 {
02051   if ( !incidence ) {
02052     return QString();
02053   }
02054 
02055   MailBodyVisitor v;
02056   if ( v.act( incidence, spec ) ) {
02057     return v.result();
02058   }
02059   return QString();
02060 }
02061 
02062 //@cond PRIVATE
02063 static QString recurEnd( Incidence *incidence )
02064 {
02065   QString endstr;
02066   if ( incidence->allDay() ) {
02067     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02068   } else {
02069     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02070   }
02071   return endstr;
02072 }
02073 //@endcond
02074 
02075 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
02076 {
02077   if ( !incidence->recurs() ) {
02078     return i18n( "No recurrence" );
02079   }
02080   QStringList dayList;
02081   dayList.append( i18n( "31st Last" ) );
02082   dayList.append( i18n( "30th Last" ) );
02083   dayList.append( i18n( "29th Last" ) );
02084   dayList.append( i18n( "28th Last" ) );
02085   dayList.append( i18n( "27th Last" ) );
02086   dayList.append( i18n( "26th Last" ) );
02087   dayList.append( i18n( "25th Last" ) );
02088   dayList.append( i18n( "24th Last" ) );
02089   dayList.append( i18n( "23rd Last" ) );
02090   dayList.append( i18n( "22nd Last" ) );
02091   dayList.append( i18n( "21st Last" ) );
02092   dayList.append( i18n( "20th Last" ) );
02093   dayList.append( i18n( "19th Last" ) );
02094   dayList.append( i18n( "18th Last" ) );
02095   dayList.append( i18n( "17th Last" ) );
02096   dayList.append( i18n( "16th Last" ) );
02097   dayList.append( i18n( "15th Last" ) );
02098   dayList.append( i18n( "14th Last" ) );
02099   dayList.append( i18n( "13th Last" ) );
02100   dayList.append( i18n( "12th Last" ) );
02101   dayList.append( i18n( "11th Last" ) );
02102   dayList.append( i18n( "10th Last" ) );
02103   dayList.append( i18n( "9th Last" ) );
02104   dayList.append( i18n( "8th Last" ) );
02105   dayList.append( i18n( "7th Last" ) );
02106   dayList.append( i18n( "6th Last" ) );
02107   dayList.append( i18n( "5th Last" ) );
02108   dayList.append( i18n( "4th Last" ) );
02109   dayList.append( i18n( "3rd Last" ) );
02110   dayList.append( i18n( "2nd Last" ) );
02111   dayList.append( i18nc( "last day of the month", "Last" ) );
02112   dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
02113   dayList.append( i18n( "1st" ) );
02114   dayList.append( i18n( "2nd" ) );
02115   dayList.append( i18n( "3rd" ) );
02116   dayList.append( i18n( "4th" ) );
02117   dayList.append( i18n( "5th" ) );
02118   dayList.append( i18n( "6th" ) );
02119   dayList.append( i18n( "7th" ) );
02120   dayList.append( i18n( "8th" ) );
02121   dayList.append( i18n( "9th" ) );
02122   dayList.append( i18n( "10th" ) );
02123   dayList.append( i18n( "11th" ) );
02124   dayList.append( i18n( "12th" ) );
02125   dayList.append( i18n( "13th" ) );
02126   dayList.append( i18n( "14th" ) );
02127   dayList.append( i18n( "15th" ) );
02128   dayList.append( i18n( "16th" ) );
02129   dayList.append( i18n( "17th" ) );
02130   dayList.append( i18n( "18th" ) );
02131   dayList.append( i18n( "19th" ) );
02132   dayList.append( i18n( "20th" ) );
02133   dayList.append( i18n( "21st" ) );
02134   dayList.append( i18n( "22nd" ) );
02135   dayList.append( i18n( "23rd" ) );
02136   dayList.append( i18n( "24th" ) );
02137   dayList.append( i18n( "25th" ) );
02138   dayList.append( i18n( "26th" ) );
02139   dayList.append( i18n( "27th" ) );
02140   dayList.append( i18n( "28th" ) );
02141   dayList.append( i18n( "29th" ) );
02142   dayList.append( i18n( "30th" ) );
02143   dayList.append( i18n( "31st" ) );
02144   int weekStart = KGlobal::locale()->weekStartDay();
02145   QString dayNames;
02146   QString txt;
02147   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
02148   Recurrence *recur = incidence->recurrence();
02149   switch ( recur->recurrenceType() ) {
02150   case Recurrence::rNone:
02151     return i18n( "No recurrence" );
02152   case Recurrence::rMinutely:
02153     if ( recur->duration() != -1 ) {
02154       txt = i18np( "Recurs every minute until %2",
02155                    "Recurs every %1 minutes until %2",
02156                    recur->frequency(), recurEnd( incidence ) );
02157       if ( recur->duration() >  0 ) {
02158         txt += i18nc( "number of occurrences",
02159                       " (<numid>%1</numid> occurrences)",
02160                       recur->duration() );
02161       }
02162       return txt;
02163     }
02164     return i18np( "Recurs every minute",
02165                   "Recurs every %1 minutes", recur->frequency() );
02166   case Recurrence::rHourly:
02167     if ( recur->duration() != -1 ) {
02168       txt = i18np( "Recurs hourly until %2",
02169                    "Recurs every %1 hours until %2",
02170                    recur->frequency(), recurEnd( incidence ) );
02171       if ( recur->duration() >  0 ) {
02172         txt += i18nc( "number of occurrences",
02173                       " (<numid>%1</numid> occurrences)",
02174                       recur->duration() );
02175       }
02176       return txt;
02177     }
02178     return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
02179   case Recurrence::rDaily:
02180     if ( recur->duration() != -1 ) {
02181       txt = i18np( "Recurs daily until %2",
02182                    "Recurs every %1 days until %2",
02183                    recur->frequency(), recurEnd( incidence ) );
02184       if ( recur->duration() >  0 ) {
02185         txt += i18nc( "number of occurrences",
02186                       " (<numid>%1</numid> occurrences)",
02187                       recur->duration() );
02188       }
02189       return txt;
02190     }
02191     return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
02192   case Recurrence::rWeekly:
02193   {
02194     bool addSpace = false;
02195     for ( int i = 0; i < 7; ++i ) {
02196       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
02197         if ( addSpace ) {
02198           dayNames.append( i18nc( "separator for list of days", ", " ) );
02199         }
02200         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
02201                                               KCalendarSystem::ShortDayName ) );
02202         addSpace = true;
02203       }
02204     }
02205     if ( dayNames.isEmpty() ) {
02206       dayNames = i18nc( "Recurs weekly on no days", "no days" );
02207     }
02208     if ( recur->duration() != -1 ) {
02209       txt = i18ncp( "Recurs weekly on [list of days] until end-date",
02210                     "Recurs weekly on %2 until %3",
02211                     "Recurs every <numid>%1</numid> weeks on %2 until %3",
02212                     recur->frequency(), dayNames, recurEnd( incidence ) );
02213       if ( recur->duration() >  0 ) {
02214         txt += i18nc( "number of occurrences",
02215                       " (<numid>%1</numid> occurrences)",
02216                       recur->duration() );
02217       }
02218       return txt;
02219     }
02220     return i18ncp( "Recurs weekly on [list of days]",
02221                    "Recurs weekly on %2",
02222                    "Recurs every <numid>%1</numid> weeks on %2",
02223                    recur->frequency(), dayNames );
02224   }
02225   case Recurrence::rMonthlyPos:
02226   {
02227     KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
02228     if ( recur->duration() != -1 ) {
02229       txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
02230                     " weekdayname until end-date",
02231                     "Recurs every month on the %2 %3 until %4",
02232                     "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
02233                     recur->frequency(),
02234                     dayList[rule.pos() + 31],
02235                     calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02236                     recurEnd( incidence ) );
02237       if ( recur->duration() >  0 ) {
02238         txt += i18nc( "number of occurrences",
02239                       " (<numid>%1</numid> occurrences)",
02240                       recur->duration() );
02241       }
02242       return txt;
02243     }
02244     return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
02245                    "Recurs every month on the %2 %3",
02246                    "Recurs every %1 months on the %2 %3",
02247                    recur->frequency(),
02248                    dayList[rule.pos() + 31],
02249                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
02250   }
02251   case Recurrence::rMonthlyDay:
02252   {
02253     int days = recur->monthDays()[0];
02254     if ( recur->duration() != -1 ) {
02255         txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
02256                       "Recurs monthly on the %2 day until %3",
02257                       "Recurs every %1 months on the %2 day until %3",
02258                       recur->frequency(),
02259                       dayList[days + 31],
02260                       recurEnd( incidence ) );
02261         if ( recur->duration() >  0 ) {
02262           txt += i18nc( "number of occurrences",
02263                         " (<numid>%1</numid> occurrences)",
02264                         recur->duration() );
02265         }
02266         return txt;
02267     }
02268     return i18ncp( "Recurs monthly on the [1st|2nd|...] day",
02269                    "Recurs monthly on the %2 day",
02270                    "Recurs every <numid>%1</numid> month on the %2 day",
02271                    recur->frequency(),
02272                    dayList[days + 31] );
02273   }
02274   case Recurrence::rYearlyMonth:
02275   {
02276     if ( recur->duration() != -1 ) {
02277       if ( !recur->yearDates().isEmpty() ) {
02278         txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
02279                       " until end-date",
02280                       "Recurs yearly on %2 %3 until %4",
02281                       "Recurs every %1 years on %2 %3 until %4",
02282                       recur->frequency(),
02283                       calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02284                       dayList[ recur->yearDates()[0] + 31 ],
02285                       recurEnd( incidence ) );
02286         if ( recur->duration() >  0 ) {
02287           txt += i18nc( "number of occurrences",
02288                         " (<numid>%1</numid> occurrences)",
02289                         recur->duration() );
02290         }
02291         return txt;
02292       }
02293     }
02294     if ( !recur->yearDates().isEmpty() ) {
02295       return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
02296                      "Recurs yearly on %2 %3",
02297                      "Recurs every %1 years on %2 %3",
02298                      recur->frequency(),
02299                      calSys->monthName( recur->yearMonths()[0],
02300                                         recur->startDate().year() ),
02301                      dayList[ recur->yearDates()[0] + 31 ] );
02302     } else {
02303       if (!recur->yearMonths().isEmpty() ) {
02304         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02305                       "Recurs yearly on %1 %2",
02306                       calSys->monthName( recur->yearMonths()[0],
02307                                          recur->startDate().year() ),
02308                       dayList[ recur->startDate().day() + 31 ] );
02309       } else {
02310         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02311                       "Recurs yearly on %1 %2",
02312                       calSys->monthName( recur->startDate().month(),
02313                                          recur->startDate().year() ),
02314                       dayList[ recur->startDate().day() + 31 ] );
02315       }
02316     }
02317   }
02318   case Recurrence::rYearlyDay:
02319     if ( recur->duration() != -1 ) {
02320       txt = i18ncp( "Recurs every N years on day N until end-date",
02321                     "Recurs every year on day <numid>%2</numid> until %3",
02322                     "Recurs every <numid>%1</numid> years"
02323                     " on day <numid>%2</numid> until %3",
02324                     recur->frequency(),
02325                     recur->yearDays()[0],
02326                     recurEnd( incidence ) );
02327       if ( recur->duration() >  0 ) {
02328         txt += i18nc( "number of occurrences",
02329                       " (<numid>%1</numid> occurrences)",
02330                       recur->duration() );
02331       }
02332       return txt;
02333     }
02334     return i18ncp( "Recurs every N YEAR[S] on day N",
02335                    "Recurs every year on day <numid>%2</numid>",
02336                    "Recurs every <numid>%1</numid> years"
02337                    " on day <numid>%2</numid>",
02338                    recur->frequency(), recur->yearDays()[0] );
02339   case Recurrence::rYearlyPos:
02340   {
02341     KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
02342     if ( recur->duration() != -1 ) {
02343       txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02344                     "of monthname until end-date",
02345                     "Every year on the %2 %3 of %4 until %5",
02346                     "Every <numid>%1</numid> years on the %2 %3 of %4"
02347                     " until %5",
02348                     recur->frequency(),
02349                     dayList[rule.pos() + 31],
02350                     calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02351                     calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02352                     recurEnd( incidence ) );
02353       if ( recur->duration() >  0 ) {
02354         txt += i18nc( "number of occurrences",
02355                       " (<numid>%1</numid> occurrences)",
02356                       recur->duration() );
02357       }
02358       return txt;
02359     }
02360     return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02361                    "of monthname",
02362                    "Every year on the %2 %3 of %4",
02363                    "Every <numid>%1</numid> years on the %2 %3 of %4",
02364                    recur->frequency(),
02365                    dayList[rule.pos() + 31],
02366                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02367                    calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
02368   }
02369   default:
02370     return i18n( "Incidence recurs" );
02371   }
02372 }
02373 
02374 QString IncidenceFormatter::timeToString( const KDateTime &date,
02375                                           bool shortfmt,
02376                                           const KDateTime::Spec &spec )
02377 {
02378   if ( spec.isValid() ) {
02379 
02380     QString timeZone;
02381     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02382       timeZone = ' ' + spec.timeZone().name();
02383     }
02384 
02385     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
02386   } else {
02387     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
02388   }
02389 }
02390 
02391 QString IncidenceFormatter::dateToString( const KDateTime &date,
02392                                           bool shortfmt,
02393                                           const KDateTime::Spec &spec )
02394 {
02395   if ( spec.isValid() ) {
02396 
02397     QString timeZone;
02398     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02399       timeZone = ' ' + spec.timeZone().name();
02400     }
02401 
02402     return
02403       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
02404                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
02405       timeZone;
02406   } else {
02407     return
02408       KGlobal::locale()->formatDate( date.date(),
02409                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
02410   }
02411 }
02412 
02413 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
02414                                               bool allDay,
02415                                               bool shortfmt,
02416                                               const KDateTime::Spec &spec )
02417 {
02418   if ( allDay ) {
02419     return dateToString( date, shortfmt, spec );
02420   }
02421 
02422   if ( spec.isValid() ) {
02423     QString timeZone;
02424     if ( spec.timeZone() != KSystemTimeZones::local() ) {
02425       timeZone = ' ' + spec.timeZone().name();
02426     }
02427 
02428     return KGlobal::locale()->formatDateTime(
02429       date.toTimeSpec( spec ).dateTime(),
02430       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
02431   } else {
02432     return  KGlobal::locale()->formatDateTime(
02433       date.dateTime(),
02434       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
02435   }
02436 }

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.9
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