• 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       mExistingIncidence = existingIncidence;
01136       return incidence->accept( *this );
01137     }
01138 
01139     QString result() const
01140     {
01141       if ( mChanges.isEmpty() ) {
01142         return QString();
01143       }
01144       QString html = "<div align=\"left\"><ul><li>";
01145       html += mChanges.join( "</li><li>" );
01146       html += "</li><ul></div>";
01147       return html;
01148     }
01149 
01150   protected:
01151     bool visit( Event *event )
01152     {
01153       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01154       compareIncidences( event, mExistingIncidence );
01155       return !mChanges.isEmpty();
01156     }
01157     bool visit( Todo *todo )
01158     {
01159       compareIncidences( todo, mExistingIncidence );
01160       return !mChanges.isEmpty();
01161     }
01162     bool visit( Journal *journal )
01163     {
01164       compareIncidences( journal, mExistingIncidence );
01165       return !mChanges.isEmpty();
01166     }
01167     bool visit( FreeBusy *fb )
01168     {
01169       Q_UNUSED( fb );
01170       return !mChanges.isEmpty();
01171     }
01172 
01173   private:
01174     void compareEvents( Event *newEvent, Event *oldEvent )
01175     {
01176       if ( !oldEvent || !newEvent ) {
01177         return;
01178       }
01179       if ( oldEvent->dtStart() != newEvent->dtStart() ||
01180            oldEvent->allDay() != newEvent->allDay() ) {
01181         mChanges += i18n( "The begin of the meeting has been changed from %1 to %2",
01182                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01183       }
01184       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01185            oldEvent->allDay() != newEvent->allDay() ) {
01186         mChanges += i18n( "The end of the meeting has been changed from %1 to %2",
01187                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01188       }
01189     }
01190 
01191     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01192     {
01193       if ( !oldInc || !newInc ) {
01194         return;
01195       }
01196 
01197       if ( oldInc->summary() != newInc->summary() ) {
01198         mChanges += i18n( "The summary has been changed to: \"%1\"",
01199                           newInc->richSummary() );
01200       }
01201 
01202       if ( oldInc->location() != newInc->location() ) {
01203         mChanges += i18n( "The location has been changed to: \"%1\"",
01204                           newInc->richLocation() );
01205       }
01206 
01207       if ( oldInc->description() != newInc->description() ) {
01208         mChanges += i18n( "The description has been changed to: \"%1\"",
01209                           newInc->richDescription() );
01210       }
01211 
01212       Attendee::List oldAttendees = oldInc->attendees();
01213       Attendee::List newAttendees = newInc->attendees();
01214       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01215             it != newAttendees.constEnd(); ++it ) {
01216         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01217         if ( !oldAtt ) {
01218           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01219         } else {
01220           if ( oldAtt->status() != (*it)->status() ) {
01221             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01222                               (*it)->fullName(), (*it)->statusStr() );
01223           }
01224         }
01225       }
01226 
01227       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01228             it != oldAttendees.constEnd(); ++it ) {
01229         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01230         if ( !newAtt ) {
01231           mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01232         }
01233       }
01234     }
01235 
01236   private:
01237     Incidence *mExistingIncidence;
01238     QStringList mChanges;
01239 };
01240 
01241 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01242 {
01243   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01244   return res.arg( generateLinkURL( id ) ).arg( text );
01245   return res;
01246 }
01247 
01248 Calendar *InvitationFormatterHelper::calendar() const
01249 {
01250   return 0;
01251 }
01252 
01253 // Check if the given incidence is likely one that we own instead one from
01254 // a shared calendar (Kolab-specific)
01255 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01256 {
01257   CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01258   if ( !cal || !incidence ) {
01259     return true;
01260   }
01261 
01262   ResourceCalendar *res = cal->resource( incidence );
01263   if ( !res ) {
01264     return true;
01265   }
01266 
01267   const QString subRes = res->subresourceIdentifier( incidence );
01268   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01269     return false;
01270   }
01271   return true;
01272 }
01273 
01274 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01275     InvitationFormatterHelper *helper )
01276 {
01277   if ( invitation.isEmpty() ) {
01278     return QString();
01279   }
01280 
01281   ICalFormat format;
01282   // parseScheduleMessage takes the tz from the calendar,
01283   // no need to set it manually here for the format!
01284   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01285 
01286   if( !msg ) {
01287     kDebug() << "Failed to parse the scheduling message";
01288     Q_ASSERT( format.exception() );
01289     kDebug() << format.exception()->message();
01290     return QString();
01291   }
01292 
01293   IncidenceBase *incBase = msg->event();
01294 
01295   Incidence *existingIncidence = 0;
01296   if ( helper->calendar() ) {
01297     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01298     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
01299       existingIncidence = 0;
01300     }
01301     if ( !existingIncidence ) {
01302       const Incidence::List list = helper->calendar()->incidences();
01303       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01304         if ( (*it)->schedulingID() == incBase->uid() &&
01305              incidenceOwnedByMe( helper->calendar(), *it ) ) {
01306           existingIncidence = *it;
01307           break;
01308         }
01309       }
01310     }
01311   }
01312 
01313   // First make the text of the message
01314   QString html;
01315 
01316   QString tableStyle = QString::fromLatin1(
01317     "style=\"border: solid 1px; margin: 0em;\"" );
01318   QString tableHead = QString::fromLatin1(
01319     "<div align=\"center\">"
01320     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01321     "<tr><td>" ).arg( tableStyle );
01322 
01323   html += tableHead;
01324   InvitationHeaderVisitor headerVisitor;
01325   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01326   if ( !headerVisitor.act( incBase, msg ) ) {
01327     return QString();
01328   }
01329   html += "<h3>" + headerVisitor.result() + "</h3>";
01330 
01331   InvitationBodyVisitor bodyVisitor;
01332   if ( !bodyVisitor.act( incBase, msg ) ) {
01333     return QString();
01334   }
01335   html += bodyVisitor.result();
01336 
01337   if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well?
01338     IncidenceCompareVisitor compareVisitor;
01339     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01340       html +=
01341         i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
01342       html += compareVisitor.result();
01343     }
01344   }
01345 
01346   html += "<br/>";
01347   html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr><tr>";
01348 
01349 #if 0
01350   // TODO: implement this
01351   html += helper->makeLinkURL( "accept", i18n( "[Enter this into my calendar]" ) );
01352   html += "</td><td> &nbsp; </td><td>";
01353 #endif
01354 
01355   // Add groupware links
01356 
01357   Incidence *incidence = dynamic_cast<Incidence*>( incBase );
01358   switch ( msg->method() ) {
01359   case iTIPPublish:
01360   case iTIPRequest:
01361   case iTIPRefresh:
01362   case iTIPAdd:
01363   {
01364     if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
01365       if ( incBase->type() == "Todo" ) {
01366         html += "<td colspan=\"13\">";
01367         html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01368       } else {
01369         html += "<td colspan=\"9\">";
01370         html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01371       }
01372       html += "</td></tr><tr>";
01373     }
01374     html += "<td>";
01375 
01376     if ( !existingIncidence ) {
01377       // Accept
01378       html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) );
01379       html += "</td><td> &nbsp; </td><td>";
01380       html += helper->makeLink( "accept_conditionally",
01381                               i18nc( "Accept conditionally", "[Accept cond.]" ) );
01382       html += "</td><td> &nbsp; </td><td>";
01383       // counter proposal
01384       html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01385       html += "</td><td> &nbsp; </td><td>";
01386       // Decline
01387       html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) );
01388       html += "</td><td> &nbsp; </td><td>";
01389 
01390       // Delegate
01391       html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) );
01392       html += "</td><td> &nbsp; </td><td>";
01393 
01394       // Forward
01395       html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) );
01396 
01397       if ( incBase->type() == "Event" ) {
01398         html += "</b></a></td><td> &nbsp; </td><td>";
01399         html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01400       }
01401     }
01402     break;
01403   }
01404 
01405   case iTIPCancel:
01406     // Cancel event from my calendar
01407     html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01408     break;
01409 
01410   case iTIPReply:
01411     // Enter this into my calendar
01412     if ( incBase->type() == "Todo" ) {
01413       html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01414     } else {
01415       html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01416     }
01417     break;
01418 
01419   case iTIPCounter:
01420   case iTIPDeclineCounter:
01421   case iTIPNoMethod:
01422     break;
01423   }
01424 
01425   html += "</td></tr></table>";
01426 
01427   html += "</td></tr></table><br></div>";
01428 
01429   return html;
01430 }
01431 
01432 /*******************************************************************
01433  *  Helper functions for the Incidence tooltips
01434  *******************************************************************/
01435 
01436 //@cond PRIVATE
01437 class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01438 {
01439   public:
01440     ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01441 
01442     bool act( IncidenceBase *incidence, bool richText=true )
01443     {
01444       mRichText = richText;
01445       mResult = "";
01446       return incidence ? incidence->accept( *this ) : false;
01447     }
01448     QString result() const { return mResult; }
01449 
01450   protected:
01451     bool visit( Event *event );
01452     bool visit( Todo *todo );
01453     bool visit( Journal *journal );
01454     bool visit( FreeBusy *fb );
01455 
01456     QString dateRangeText( Event *event );
01457     QString dateRangeText( Todo *todo );
01458     QString dateRangeText( Journal *journal );
01459     QString dateRangeText( FreeBusy *fb );
01460 
01461     QString generateToolTip( Incidence *incidence, QString dtRangeText );
01462 
01463   protected:
01464     bool mRichText;
01465     QString mResult;
01466 };
01467 
01468 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event )
01469 {
01470   //FIXME: support mRichText==false
01471   //TODO: the &nbsp; in the strings are obsolete, they should be removed
01472   //      (couldn't do that because of the string freeze)
01473   QString ret;
01474   QString tmp;
01475   if ( event->isMultiDay() ) {
01476 
01477     tmp = event->dtStartStr( true, event->dtStart().timeSpec() );
01478     ret += "<br>" + i18nc( "Event start", "<i>From:</i>&nbsp;%1", tmp );
01479 
01480     tmp = event->dtEndStr( true, event->dtEnd().timeSpec() );
01481     ret += "<br>" + i18nc( "Event end","<i>To:</i>&nbsp;%1", tmp );
01482 
01483   } else {
01484 
01485     ret += "<br>" +
01486            i18n( "<i>Date:</i>&nbsp;%1",
01487                  event->dtStartDateStr(
01488                    true, event->dtStart().timeSpec() ) );
01489     if ( !event->allDay() ) {
01490       if ( event->dtStartTimeStr( true, event->dtStart().timeSpec() ) ==
01491            event->dtEndTimeStr( true, event->dtEnd().timeSpec() ) ) {
01492         // to prevent 'Time: 17:00 - 17:00'
01493         tmp = "<br>" +
01494               // TODO: the comment is no longer true, &nbsp; is not needed anymore, I leave
01495               // because of the string freeze
01496               i18nc( "time for event, &nbsp; to prevent ugly line breaks", "<i>Time:</i>&nbsp;%1",
01497                      event->dtStartTimeStr(
01498                        true, event->dtStart().timeSpec() ) );
01499       } else {
01500         tmp = "<br>" +
01501               // TODO: the comment is no longer true, &nbsp; is not needed anymore, I leave
01502               // because of the string freeze
01503               i18nc( "time range for event, &nbsp; to prevent ugly line breaks",
01504                      "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2",
01505                      event->dtStartTimeStr(
01506                        true, event->dtStart().timeSpec() ),
01507                      event->dtEndTimeStr(
01508                        true, event->dtEnd().timeSpec() ) );
01509       }
01510       ret += tmp;
01511     }
01512   }
01513   return ret.replace( " ", "&nbsp;" );
01514 }
01515 
01516 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo )
01517 {
01518   //FIXME: support mRichText==false
01519   //TODO: the &nbsp; in the strings are obsolete, they should be removed
01520   //      (couldn't do that because of the string freeze)
01521   QString ret;
01522   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01523     // No need to add <i> here. This is separated issue and each line
01524     // is very visible on its own. On the other hand... Yes, I like it
01525     // italics here :)
01526     ret += "<br>" + i18n( "<i>Start:</i>&nbsp;%1",
01527                             todo->dtStartStr(
01528                             true, false, todo->dtStart().timeSpec() ) ) ;
01529   }
01530   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01531     ret += "<br>" + i18n( "<i>Due:</i>&nbsp;%1",
01532                             todo->dtDueStr(
01533                             true, todo->dtDue().timeSpec() ) );
01534   }
01535   if ( todo->isCompleted() ) {
01536     ret += "<br>" +
01537            i18n( "<i>Completed:</i>&nbsp;%1", todo->completedStr() );
01538   } else {
01539     ret += "<br>" +
01540            i18nc( "percent complete", "%1 % completed", todo->percentComplete() );
01541   }
01542 
01543   return ret.replace( " ", "&nbsp;" );
01544 }
01545 
01546 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
01547 {
01548   //FIXME: support mRichText==false
01549   //TODO: the &nbsp; in the strings are obsolete, they should be removed
01550   //      (couldn't do that because of the string freeze)
01551   QString ret;
01552   if ( journal->dtStart().isValid() ) {
01553     ret += "<br>" +
01554            i18n( "<i>Date:</i>&nbsp;%1",
01555                  journal->dtStartDateStr( false, journal->dtStart().timeSpec() ) );
01556   }
01557   return ret.replace( " ", "&nbsp;" );
01558 }
01559 
01560 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01561 {
01562   //FIXME: support mRichText==false
01563   //TODO: the &nbsp; in the strings are obsolete, they should be removed
01564   //      (couldn't do that because of the string freeze)
01565   QString ret;
01566   ret = "<br>" +
01567         i18n( "<i>Period start:</i>&nbsp;%1",
01568               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
01569   ret += "<br>" +
01570          i18n( "<i>Period start:</i>&nbsp;%1",
01571                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
01572   return ret.replace( " ", "&nbsp;" );
01573 }
01574 
01575 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01576 {
01577   mResult = generateToolTip( event, dateRangeText( event ) );
01578   return !mResult.isEmpty();
01579 }
01580 
01581 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01582 {
01583   mResult = generateToolTip( todo, dateRangeText( todo ) );
01584   return !mResult.isEmpty();
01585 }
01586 
01587 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01588 {
01589   mResult = generateToolTip( journal, dateRangeText( journal ) );
01590   return !mResult.isEmpty();
01591 }
01592 
01593 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01594 {
01595   //FIXME: support mRichText==false
01596   mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
01597   mResult += dateRangeText( fb );
01598   mResult += "</qt>";
01599   return !mResult.isEmpty();
01600 }
01601 
01602 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
01603                                                              QString dtRangeText )
01604 {
01605   //FIXME: support mRichText==false
01606 
01607   if ( !incidence ) {
01608     return QString();
01609   }
01610 
01611   QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
01612 
01613   tmp += dtRangeText;
01614 
01615   if ( !incidence->location().isEmpty() ) {
01616     // Put Location: in italics
01617     tmp += "<br>" +
01618            i18n( "<i>Location:</i>&nbsp;%1", incidence->richLocation() );
01619   }
01620 
01621   if ( !incidence->description().isEmpty() ) {
01622     QString desc( incidence->description() );
01623     if ( !incidence->descriptionIsRich() ) {
01624       if ( desc.length() > 120 ) {
01625         desc = desc.left( 120 ) + "...";
01626       }
01627       desc = Qt::escape( desc ).replace( "\n", "<br>" );
01628     } else {
01629       // TODO: truncate the description when it's rich text
01630     }
01631     tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
01632   }
01633   tmp += "</qt>";
01634   return tmp;
01635 }
01636 //@endcond
01637 
01638 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
01639 {
01640   ToolTipVisitor v;
01641   if ( v.act( incidence, richText ) ) {
01642     return v.result();
01643   } else {
01644     return QString();
01645   }
01646 }
01647 
01648 /*******************************************************************
01649  *  Helper functions for the Incidence tooltips
01650  *******************************************************************/
01651 
01652 static QString mailBodyIncidence( Incidence *incidence )
01653 {
01654   QString body;
01655   if ( !incidence->summary().isEmpty() ) {
01656     body += i18n( "Summary: %1\n", incidence->richSummary() );
01657   }
01658   if ( !incidence->organizer().isEmpty() ) {
01659     body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
01660   }
01661   if ( !incidence->location().isEmpty() ) {
01662     body += i18n( "Location: %1\n", incidence->richLocation() );
01663   }
01664   return body;
01665 }
01666 
01667 //@cond PRIVATE
01668 class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
01669 {
01670   public:
01671     MailBodyVisitor() : mResult( "" ) {}
01672 
01673     bool act( IncidenceBase *incidence )
01674     {
01675       mResult = "";
01676       return incidence ? incidence->accept( *this ) : false;
01677     }
01678     QString result() const
01679     {
01680       return mResult;
01681     }
01682 
01683   protected:
01684     bool visit( Event *event );
01685     bool visit( Todo *todo );
01686     bool visit( Journal *journal );
01687     bool visit( FreeBusy * )
01688     {
01689       mResult = i18n( "This is a Free Busy Object" );
01690       return !mResult.isEmpty();
01691     }
01692   protected:
01693     QString mResult;
01694 };
01695 
01696 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
01697 {
01698   QString recurrence[]= {
01699     i18nc( "no recurrence", "None" ),
01700     i18nc( "event recurs by minutes", "Minutely" ),
01701     i18nc( "event recurs by hours", "Hourly" ),
01702     i18nc( "event recurs by days", "Daily" ),
01703     i18nc( "event recurs by weeks", "Weekly" ),
01704     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
01705     i18nc( "event recurs same day each month", "Monthly Same Day" ),
01706     i18nc( "event recurs same month each year", "Yearly Same Month" ),
01707     i18nc( "event recurs same day each year", "Yearly Same Day" ),
01708     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
01709   };
01710 
01711   mResult = mailBodyIncidence( event );
01712   mResult += i18n( "Start Date: %1\n",
01713                    event->dtStartDateStr( true, event->dtStart().timeSpec() ) );
01714   if ( !event->allDay() ) {
01715     mResult += i18n( "Start Time: %1\n",
01716                      event->dtStartTimeStr( true, event->dtStart().timeSpec() ) );
01717   }
01718   if ( event->dtStart() != event->dtEnd() ) {
01719     mResult += i18n( "End Date: %1\n",
01720                      event->dtEndDateStr( true, event->dtStart().timeSpec() ) );
01721   }
01722   if ( !event->allDay() ) {
01723     mResult += i18n( "End Time: %1\n",
01724                      event->dtEndTimeStr( true, event->dtStart().timeSpec() ) );
01725   }
01726   if ( event->recurs() ) {
01727     Recurrence *recur = event->recurrence();
01728     // TODO: Merge these two to one of the form "Recurs every 3 days"
01729     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
01730     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
01731 
01732     if ( recur->duration() > 0 ) {
01733       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
01734       mResult += '\n';
01735     } else {
01736       if ( recur->duration() != -1 ) {
01737 // TODO_Recurrence: What to do with all-day
01738         QString endstr;
01739         if ( event->allDay() ) {
01740           endstr = KGlobal::locale()->formatDate( recur->endDate() );
01741         } else {
01742           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
01743         }
01744         mResult += i18n( "Repeat until: %1\n", endstr );
01745       } else {
01746         mResult += i18n( "Repeats forever\n" );
01747       }
01748     }
01749   }
01750 
01751   QString details = event->richDescription();
01752   if ( !details.isEmpty() ) {
01753     mResult += i18n( "Details:\n%1\n", details );
01754   }
01755   return !mResult.isEmpty();
01756 }
01757 
01758 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
01759 {
01760   mResult = mailBodyIncidence( todo );
01761 
01762   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01763     mResult += i18n( "Start Date: %1\n",
01764                      todo->dtStartDateStr( true, false, todo->dtStart().timeSpec() ) );
01765     if ( !todo->allDay() ) {
01766       mResult += i18n( "Start Time: %1\n",
01767                        todo->dtStartTimeStr( true, false, todo->dtStart().timeSpec() ) );
01768     }
01769   }
01770   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01771     mResult += i18n( "Due Date: %1\n",
01772                      todo->dtDueDateStr( true, todo->dtDue().timeSpec() ) );
01773     if ( !todo->allDay() ) {
01774       mResult += i18n( "Due Time: %1\n",
01775                        todo->dtDueTimeStr( true, todo->dtDue().timeSpec() ) );
01776     }
01777   }
01778   QString details = todo->richDescription();
01779   if ( !details.isEmpty() ) {
01780     mResult += i18n( "Details:\n%1\n", details );
01781   }
01782   return !mResult.isEmpty();
01783 }
01784 
01785 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
01786 {
01787   mResult = mailBodyIncidence( journal );
01788   mResult += i18n( "Date: %1\n", journal->dtStartDateStr( true, journal->dtStart().timeSpec() ) );
01789   if ( !journal->allDay() ) {
01790     mResult += i18n( "Time: %1\n", journal->dtStartTimeStr( true, journal->dtStart().timeSpec() ) );
01791   }
01792   if ( !journal->description().isEmpty() ) {
01793     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
01794   }
01795   return !mResult.isEmpty();
01796 }
01797 //@endcond
01798 
01799 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
01800 {
01801   if ( !incidence ) {
01802     return QString();
01803   }
01804 
01805   MailBodyVisitor v;
01806   if ( v.act( incidence ) ) {
01807     return v.result();
01808   }
01809   return QString();
01810 }
01811 
01812 static QString recurEnd( Incidence *incidence )
01813 {
01814   QString endstr;
01815   if ( incidence->allDay() ) {
01816     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
01817   } else {
01818     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
01819   }
01820   return endstr;
01821 }
01822 
01823 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
01824 {
01825   if ( !incidence->recurs() ) {
01826     return i18n( "No recurrence" );
01827   }
01828 
01829   Recurrence *recur = incidence->recurrence();
01830   switch ( recur->recurrenceType() ) {
01831   case Recurrence::rNone:
01832     return i18n( "No recurrence" );
01833   case Recurrence::rMinutely:
01834     if ( recur->duration() != -1 ) {
01835       return i18np( "Recurs every minute until %2",
01836                     "Recurs every %1 minutes until %2",
01837                     recur->frequency(), recurEnd( incidence ) );
01838     }
01839     return i18np( "Recurs every minute",
01840                   "Recurs every %1 minutes", recur->frequency() );
01841   case Recurrence::rHourly:
01842     if ( recur->duration() != -1 ) {
01843       return i18np( "Recurs hourly until %2",
01844                     "Recurs every %1 hours until %2",
01845                     recur->frequency(), recurEnd( incidence ) );
01846     }
01847     return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
01848   case Recurrence::rDaily:
01849     if ( recur->duration() != -1 ) {
01850       return i18np( "Recurs daily until %2",
01851                     "Recurs every %1 days until %2",
01852                     recur->frequency(), recurEnd( incidence ) );
01853     }
01854     return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
01855   case Recurrence::rWeekly:
01856     if ( recur->duration() != -1 ) {
01857       return i18np( "Recurs weekly until %2",
01858                     "Recurs every %1 weeks until %2",
01859                     recur->frequency(), recurEnd( incidence ) );
01860     }
01861     return i18np( "Recurs weekly", "Recurs every %1 weeks", recur->frequency() );
01862   case Recurrence::rMonthlyPos:
01863   case Recurrence::rMonthlyDay:
01864     if ( recur->duration() != -1 ) {
01865       return i18n( "Recurs monthly until %1", recurEnd( incidence ) );
01866     }
01867     return i18n( "Recurs monthly" );
01868   case Recurrence::rYearlyMonth:
01869   case Recurrence::rYearlyDay:
01870   case Recurrence::rYearlyPos:
01871     if ( recur->duration() != -1 ) {
01872       return i18n( "Recurs yearly until %1", recurEnd( incidence ) );
01873     }
01874     return i18n( "Recurs yearly" );
01875   default:
01876     return i18n( "Incidence recurs" );
01877   }
01878 }

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