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

KCal Library

incidenceformatter.cpp

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

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