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

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
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.8
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal