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

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