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

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