• Skip to content
  • Skip to link menu
KDE 4.8 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KCalUtils Library

incidenceformatter.cpp
Go to the documentation of this file.
00001 /*
00002   This file is part of the kcalutils 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   Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
00008 
00009   This library is free software; you can redistribute it and/or
00010   modify it under the terms of the GNU Library General Public
00011   License as published by the Free Software Foundation; either
00012   version 2 of the License, or (at your option) any later version.
00013 
00014   This library is distributed in the hope that it will be useful,
00015   but WITHOUT ANY WARRANTY; without even the implied warranty of
00016   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017   Library General Public License for more details.
00018 
00019   You should have received a copy of the GNU Library General Public License
00020   along with this library; see the file COPYING.LIB.  If not, write to
00021   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00022   Boston, MA 02110-1301, USA.
00023 */
00036 #include "incidenceformatter.h"
00037 #include "stringify.h"
00038 
00039 #include <kcalcore/event.h>
00040 #include <kcalcore/freebusy.h>
00041 #include <kcalcore/icalformat.h>
00042 #include <kcalcore/journal.h>
00043 #include <kcalcore/memorycalendar.h>
00044 #include <kcalcore/todo.h>
00045 #include <kcalcore/visitor.h>
00046 using namespace KCalCore;
00047 
00048 #include <kpimutils/email.h>
00049 
00050 #include <KCalendarSystem>
00051 #include <KDebug>
00052 #include <KEMailSettings>
00053 #include <KIconLoader>
00054 #include <KLocale>
00055 #include <KMimeType>
00056 #include <KSystemTimeZone>
00057 
00058 #include <QtCore/QBitArray>
00059 #include <QtGui/QApplication>
00060 #include <QtGui/QPalette>
00061 #include <QtGui/QTextDocument>
00062 
00063 using namespace KCalUtils;
00064 using namespace IncidenceFormatter;
00065 
00066 /*******************
00067  *  General helpers
00068  *******************/
00069 
00070 //@cond PRIVATE
00071 static QString htmlAddLink( const QString &ref, const QString &text,
00072                             bool newline = true )
00073 {
00074   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00075   if ( newline ) {
00076     tmpStr += '\n';
00077   }
00078   return tmpStr;
00079 }
00080 
00081 static QString htmlAddMailtoLink( const QString &email, const QString &name )
00082 {
00083   QString str;
00084 
00085   if ( !email.isEmpty() ) {
00086     Person person( name, email );
00087     QString path = person.fullName().simplified();
00088     if ( path.isEmpty() || path.startsWith( '"' ) ) {
00089       path = email;
00090     }
00091     KUrl mailto;
00092     mailto.setProtocol( "mailto" );
00093     mailto.setPath( path );
00094     const QString iconPath =
00095       KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small );
00096     str = htmlAddLink( mailto.url(), "<img valign=\"top\" src=\"" + iconPath + "\">" );
00097   }
00098   return str;
00099 }
00100 
00101 static QString htmlAddUidLink( const QString &email, const QString &name, const QString &uid )
00102 {
00103   QString str;
00104 
00105   if ( !uid.isEmpty() ) {
00106     // There is a UID, so make a link to the addressbook
00107     if ( name.isEmpty() ) {
00108       // Use the email address for text
00109       str += htmlAddLink( "uid:" + uid, email );
00110     } else {
00111       str += htmlAddLink( "uid:" + uid, name );
00112     }
00113   }
00114   return str;
00115 }
00116 
00117 static QString htmlAddTag( const QString &tag, const QString &text )
00118 {
00119   int numLineBreaks = text.count( "\n" );
00120   QString str = '<' + tag + '>';
00121   QString tmpText = text;
00122   QString tmpStr = str;
00123   if( numLineBreaks >= 0 ) {
00124     if ( numLineBreaks > 0 ) {
00125       int pos = 0;
00126       QString tmp;
00127       for ( int i = 0; i <= numLineBreaks; ++i ) {
00128         pos = tmpText.indexOf( "\n" );
00129         tmp = tmpText.left( pos );
00130         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00131         tmpStr += tmp + "<br>";
00132       }
00133     } else {
00134       tmpStr += tmpText;
00135     }
00136   }
00137   tmpStr += "</" + tag + '>';
00138   return tmpStr;
00139 }
00140 
00141 static QPair<QString, QString> searchNameAndUid( const QString &email, const QString &name,
00142                                                  const QString &uid )
00143 {
00144   // Yes, this is a silly method now, but it's predecessor was quite useful in e35.
00145   // For now, please keep this sillyness until e35 is frozen to ease forward porting.
00146   // -Allen
00147   QPair<QString, QString>s;
00148   s.first = name;
00149   s.second = uid;
00150   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00151     s.second.clear();
00152   }
00153   return s;
00154 }
00155 
00156 static QString searchName( const QString &email, const QString &name )
00157 {
00158   const QString printName = name.isEmpty() ? email : name;
00159   return printName;
00160 }
00161 
00162 static bool iamAttendee( Attendee::Ptr attendee )
00163 {
00164   // Check if I'm this attendee
00165 
00166   bool iam = false;
00167   KEMailSettings settings;
00168   QStringList profiles = settings.profiles();
00169   for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
00170     settings.setProfile( *it );
00171     if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
00172       iam = true;
00173       break;
00174     }
00175   }
00176   return iam;
00177 }
00178 
00179 static bool iamOrganizer( Incidence::Ptr incidence )
00180 {
00181   // Check if I'm the organizer for this incidence
00182 
00183   if ( !incidence ) {
00184     return false;
00185   }
00186 
00187   bool iam = false;
00188   KEMailSettings settings;
00189   QStringList profiles = settings.profiles();
00190   for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
00191     settings.setProfile( *it );
00192     if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer()->email() ) {
00193       iam = true;
00194       break;
00195     }
00196   }
00197   return iam;
00198 }
00199 
00200 static bool senderIsOrganizer( Incidence::Ptr incidence, const QString &sender )
00201 {
00202   // Check if the specified sender is the organizer
00203 
00204   if ( !incidence || sender.isEmpty() ) {
00205     return true;
00206   }
00207 
00208   bool isorg = true;
00209   QString senderName, senderEmail;
00210   if ( KPIMUtils::extractEmailAddressAndName( sender, senderEmail, senderName ) ) {
00211     // for this heuristic, we say the sender is the organizer if either the name or the email match.
00212     if ( incidence->organizer()->email() != senderEmail &&
00213          incidence->organizer()->name() != senderName ) {
00214       isorg = false;
00215     }
00216   }
00217   return isorg;
00218 }
00219 
00220 static bool attendeeIsOrganizer( const Incidence::Ptr &incidence, const Attendee::Ptr &attendee )
00221 {
00222   if ( incidence && attendee &&
00223        ( incidence->organizer()->email() == attendee->email() ) ) {
00224     return true;
00225   } else {
00226     return false;
00227   }
00228 }
00229 
00230 static QString organizerName( const Incidence::Ptr incidence, const QString &defName )
00231 {
00232   QString tName;
00233   if ( !defName.isEmpty() ) {
00234     tName = defName;
00235   } else {
00236     tName = i18n( "Organizer Unknown" );
00237   }
00238 
00239   QString name;
00240   if ( incidence ) {
00241     name = incidence->organizer()->name();
00242     if ( name.isEmpty() ) {
00243       name = incidence->organizer()->email();
00244     }
00245   }
00246   if ( name.isEmpty() ) {
00247     name = tName;
00248   }
00249   return name;
00250 }
00251 
00252 static QString firstAttendeeName( const Incidence::Ptr &incidence, const QString &defName )
00253 {
00254   QString tName;
00255   if ( !defName.isEmpty() ) {
00256     tName = defName;
00257   } else {
00258     tName = i18n( "Sender" );
00259   }
00260 
00261   QString name;
00262   if ( incidence ) {
00263     Attendee::List attendees = incidence->attendees();
00264     if( attendees.count() > 0 ) {
00265       Attendee::Ptr attendee = *attendees.begin();
00266       name = attendee->name();
00267       if ( name.isEmpty() ) {
00268         name = attendee->email();
00269       }
00270     }
00271   }
00272   if ( name.isEmpty() ) {
00273     name = tName;
00274   }
00275   return name;
00276 }
00277 
00278 static QString rsvpStatusIconPath( Attendee::PartStat status )
00279 {
00280   QString iconPath;
00281   switch ( status ) {
00282   case Attendee::Accepted:
00283     iconPath = KIconLoader::global()->iconPath( "dialog-ok-apply", KIconLoader::Small );
00284     break;
00285   case Attendee::Declined:
00286     iconPath = KIconLoader::global()->iconPath( "dialog-cancel", KIconLoader::Small );
00287     break;
00288   case Attendee::NeedsAction:
00289     iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
00290     break;
00291   case Attendee::InProcess:
00292     iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
00293     break;
00294   case Attendee::Tentative:
00295     iconPath = KIconLoader::global()->iconPath( "dialog-ok", KIconLoader::Small );
00296     break;
00297   case Attendee::Delegated:
00298     iconPath = KIconLoader::global()->iconPath( "mail-forward", KIconLoader::Small );
00299     break;
00300   case Attendee::Completed:
00301     iconPath = KIconLoader::global()->iconPath( "mail-mark-read", KIconLoader::Small );
00302   default:
00303     break;
00304   }
00305   return iconPath;
00306 }
00307 
00308 //@endcond
00309 
00310 /*******************************************************************
00311  *  Helper functions for the extensive display (display viewer)
00312  *******************************************************************/
00313 
00314 //@cond PRIVATE
00315 static QString displayViewFormatPerson( const QString &email, const QString &name,
00316                                         const QString &uid, const QString &iconPath )
00317 {
00318   // Search for new print name or uid, if needed.
00319   QPair<QString, QString> s = searchNameAndUid( email, name, uid );
00320   const QString printName = s.first;
00321   const QString printUid = s.second;
00322 
00323   QString personString;
00324   if ( !iconPath.isEmpty() ) {
00325     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
00326   }
00327 
00328   // Make the uid link
00329   if ( !printUid.isEmpty() ) {
00330     personString += htmlAddUidLink( email, printName, printUid );
00331   } else {
00332     // No UID, just show some text
00333     personString += ( printName.isEmpty() ? email : printName );
00334   }
00335 
00336 #ifndef KDEPIM_MOBILE_UI
00337   // Make the mailto link
00338   if ( !email.isEmpty() ) {
00339     personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
00340   }
00341 #endif
00342 
00343   return personString;
00344 }
00345 
00346 static QString displayViewFormatPerson( const QString &email, const QString &name,
00347                                         const QString &uid, Attendee::PartStat status )
00348 {
00349   return displayViewFormatPerson( email, name, uid, rsvpStatusIconPath( status ) );
00350 }
00351 
00352 static bool incOrganizerOwnsCalendar( const Calendar::Ptr &calendar,
00353                                       const Incidence::Ptr &incidence )
00354 {
00355   //PORTME!  Look at e35's CalHelper::incOrganizerOwnsCalendar
00356 
00357   // For now, use iamOrganizer() which is only part of the check
00358   Q_UNUSED( calendar );
00359   return iamOrganizer( incidence );
00360 }
00361 
00362 static QString displayViewFormatAttendeeRoleList( Incidence::Ptr incidence, Attendee::Role role,
00363                                                   bool showStatus )
00364 {
00365   QString tmpStr;
00366   Attendee::List::ConstIterator it;
00367   Attendee::List attendees = incidence->attendees();
00368 
00369   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00370     Attendee::Ptr a = *it;
00371     if ( a->role() != role ) {
00372       // skip this role
00373       continue;
00374     }
00375     if ( attendeeIsOrganizer( incidence, a ) ) {
00376       // skip attendee that is also the organizer
00377       continue;
00378     }
00379     tmpStr += displayViewFormatPerson( a->email(), a->name(), a->uid(),
00380                                        showStatus ? a->status() : Attendee::None );
00381     if ( !a->delegator().isEmpty() ) {
00382       tmpStr += i18n( " (delegated by %1)", a->delegator() );
00383     }
00384     if ( !a->delegate().isEmpty() ) {
00385       tmpStr += i18n( " (delegated to %1)", a->delegate() );
00386     }
00387     tmpStr += "<br>";
00388   }
00389   if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
00390     tmpStr.chop( 4 );
00391   }
00392   return tmpStr;
00393 }
00394 
00395 static QString displayViewFormatAttendees( Calendar::Ptr calendar, Incidence::Ptr incidence )
00396 {
00397   QString tmpStr, str;
00398 
00399   // Add organizer link
00400   int attendeeCount = incidence->attendees().count();
00401   if ( attendeeCount > 1 ||
00402        ( attendeeCount == 1 &&
00403          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
00404 
00405     QPair<QString, QString> s = searchNameAndUid( incidence->organizer()->email(),
00406                                                   incidence->organizer()->name(),
00407                                                   QString() );
00408     tmpStr += "<tr>";
00409     tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00410     const QString iconPath =
00411       KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
00412     tmpStr += "<td>" + displayViewFormatPerson( incidence->organizer()->email(),
00413                                                 s.first, s.second, iconPath ) +
00414               "</td>";
00415     tmpStr += "</tr>";
00416   }
00417 
00418   // Show the attendee status if the incidence's organizer owns the resource calendar,
00419   // which means they are running the show and have all the up-to-date response info.
00420   bool showStatus = incOrganizerOwnsCalendar( calendar, incidence );
00421 
00422   // Add "chair"
00423   str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
00424   if ( !str.isEmpty() ) {
00425     tmpStr += "<tr>";
00426     tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
00427     tmpStr += "<td>" + str + "</td>";
00428     tmpStr += "</tr>";
00429   }
00430 
00431   // Add required participants
00432   str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
00433   if ( !str.isEmpty() ) {
00434     tmpStr += "<tr>";
00435     tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
00436     tmpStr += "<td>" + str + "</td>";
00437     tmpStr += "</tr>";
00438   }
00439 
00440   // Add optional participants
00441   str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
00442   if ( !str.isEmpty() ) {
00443     tmpStr += "<tr>";
00444     tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
00445     tmpStr += "<td>" + str + "</td>";
00446     tmpStr += "</tr>";
00447   }
00448 
00449   // Add observers
00450   str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
00451   if ( !str.isEmpty() ) {
00452     tmpStr += "<tr>";
00453     tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
00454     tmpStr += "<td>" + str + "</td>";
00455     tmpStr += "</tr>";
00456   }
00457 
00458   return tmpStr;
00459 }
00460 
00461 static QString displayViewFormatAttachments( Incidence::Ptr incidence )
00462 {
00463   QString tmpStr;
00464   Attachment::List as = incidence->attachments();
00465   Attachment::List::ConstIterator it;
00466   int count = 0;
00467   for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
00468     count++;
00469     if ( (*it)->isUri() ) {
00470       QString name;
00471       if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) {
00472         name = i18n( "Show mail" );
00473       } else {
00474         if ( (*it)->label().isEmpty() ) {
00475           name = (*it)->uri();
00476         } else {
00477           name = (*it)->label();
00478         }
00479       }
00480       tmpStr += htmlAddLink( (*it)->uri(), name );
00481     } else {
00482       tmpStr += htmlAddLink( QString::fromLatin1( "ATTACH:%1" ).
00483                              arg( QString::fromUtf8( (*it)->label().toUtf8().toBase64() ) ),
00484                              (*it)->label() );
00485     }
00486     if ( count < as.count() ) {
00487       tmpStr += "<br>";
00488     }
00489   }
00490   return tmpStr;
00491 }
00492 
00493 static QString displayViewFormatCategories( Incidence::Ptr incidence )
00494 {
00495   // We do not use Incidence::categoriesStr() since it does not have whitespace
00496   return incidence->categories().join( ", " );
00497 }
00498 
00499 static QString displayViewFormatCreationDate( Incidence::Ptr incidence, KDateTime::Spec spec )
00500 {
00501   KDateTime kdt = incidence->created().toTimeSpec( spec );
00502   return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) );
00503 }
00504 
00505 static QString displayViewFormatBirthday( Event::Ptr event )
00506 {
00507   if ( !event ) {
00508     return QString();
00509   }
00510   if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" &&
00511        event->customProperty( "KABC", "ANNIVERSARY" ) != "YES" ) {
00512     return QString();
00513   }
00514 
00515   QString uid_1 = event->customProperty( "KABC", "UID-1" );
00516   QString name_1 = event->customProperty( "KABC", "NAME-1" );
00517   QString email_1= event->customProperty( "KABC", "EMAIL-1" );
00518 
00519   QString tmpStr = displayViewFormatPerson( email_1, name_1, uid_1, QString() );
00520   return tmpStr;
00521 }
00522 
00523 static QString displayViewFormatHeader( Incidence::Ptr incidence )
00524 {
00525   QString tmpStr = "<table><tr>";
00526 
00527   // show icons
00528   KIconLoader *iconLoader = KIconLoader::global();
00529   tmpStr += "<td>";
00530 
00531   QString iconPath;
00532   if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00533     iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small );
00534   } else if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00535     iconPath = iconLoader->iconPath( "view-calendar-wedding-anniversary", KIconLoader::Small );
00536   } else {
00537     iconPath = iconLoader->iconPath( incidence->iconName(), KIconLoader::Small );
00538   }
00539   tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00540 
00541   if ( incidence->hasEnabledAlarms() ) {
00542     tmpStr += "<img valign=\"top\" src=\"" +
00543               iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
00544               "\">";
00545   }
00546   if ( incidence->recurs() ) {
00547     tmpStr += "<img valign=\"top\" src=\"" +
00548               iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
00549               "\">";
00550   }
00551   if ( incidence->isReadOnly() ) {
00552     tmpStr += "<img valign=\"top\" src=\"" +
00553               iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
00554               "\">";
00555   }
00556   tmpStr += "</td>";
00557 
00558   tmpStr += "<td>";
00559   tmpStr += "<b><u>" + incidence->richSummary() + "</u></b>";
00560   tmpStr += "</td>";
00561 
00562   tmpStr += "</tr></table>";
00563 
00564   return tmpStr;
00565 }
00566 
00567 static QString displayViewFormatEvent( const Calendar::Ptr calendar, const QString &sourceName,
00568                                        const Event::Ptr &event,
00569                                        const QDate &date, KDateTime::Spec spec )
00570 {
00571   if ( !event ) {
00572     return QString();
00573   }
00574 
00575   QString tmpStr = displayViewFormatHeader( event );
00576 
00577   tmpStr += "<table>";
00578   tmpStr += "<col width=\"25%\"/>";
00579   tmpStr += "<col width=\"75%\"/>";
00580 
00581   const QString calStr = calendar ? resourceString( calendar, event ) : sourceName;
00582   if ( !calStr.isEmpty() ) {
00583     tmpStr += "<tr>";
00584     tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00585     tmpStr += "<td>" + calStr + "</td>";
00586     tmpStr += "</tr>";
00587   }
00588 
00589   if ( !event->location().isEmpty() ) {
00590     tmpStr += "<tr>";
00591     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00592     tmpStr += "<td>" + event->richLocation() + "</td>";
00593     tmpStr += "</tr>";
00594   }
00595 
00596   KDateTime startDt = event->dtStart();
00597   KDateTime endDt = event->dtEnd();
00598   if ( event->recurs() ) {
00599     if ( date.isValid() ) {
00600       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
00601       int diffDays = startDt.daysTo( kdt );
00602       kdt = kdt.addSecs( -1 );
00603       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
00604       if ( event->hasEndDate() ) {
00605         endDt = endDt.addDays( diffDays );
00606         if ( startDt > endDt ) {
00607           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
00608           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00609         }
00610       }
00611     }
00612   }
00613 
00614   tmpStr += "<tr>";
00615   if ( event->allDay() ) {
00616     if ( event->isMultiDay() ) {
00617       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00618       tmpStr += "<td>" +
00619                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00620                        dateToString( startDt, false, spec ),
00621                        dateToString( endDt, false, spec ) ) +
00622                 "</td>";
00623     } else {
00624       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00625       tmpStr += "<td>" +
00626                 i18nc( "date as string","%1",
00627                        dateToString( startDt, false, spec ) ) +
00628                 "</td>";
00629     }
00630   } else {
00631     if ( event->isMultiDay() ) {
00632       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00633       tmpStr += "<td>" +
00634                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00635                        dateToString( startDt, false, spec ),
00636                        dateToString( endDt, false, spec ) ) +
00637                 "</td>";
00638     } else {
00639       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00640       tmpStr += "<td>" +
00641                 i18nc( "date as string", "%1",
00642                        dateToString( startDt, false, spec ) ) +
00643                 "</td>";
00644 
00645       tmpStr += "</tr><tr>";
00646       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00647       if ( event->hasEndDate() && startDt != endDt ) {
00648         tmpStr += "<td>" +
00649                   i18nc( "<beginTime> - <endTime>","%1 - %2",
00650                          timeToString( startDt, true, spec ),
00651                          timeToString( endDt, true, spec ) ) +
00652                   "</td>";
00653       } else {
00654         tmpStr += "<td>" +
00655                   timeToString( startDt, true, spec ) +
00656                   "</td>";
00657       }
00658     }
00659   }
00660   tmpStr += "</tr>";
00661 
00662   QString durStr = durationString( event );
00663   if ( !durStr.isEmpty() ) {
00664     tmpStr += "<tr>";
00665     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00666     tmpStr += "<td>" + durStr + "</td>";
00667     tmpStr += "</tr>";
00668   }
00669 
00670   if ( event->recurs() ) {
00671     tmpStr += "<tr>";
00672     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00673     tmpStr += "<td>" +
00674               recurrenceString( event ) +
00675               "</td>";
00676     tmpStr += "</tr>";
00677   }
00678 
00679   const bool isBirthday = event->customProperty( "KABC", "BIRTHDAY" ) == "YES";
00680   const bool isAnniversary = event->customProperty( "KABC", "ANNIVERSARY" ) == "YES";
00681 
00682   if ( isBirthday || isAnniversary ) {
00683     tmpStr += "<tr>";
00684     if ( isAnniversary ) {
00685       tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00686     } else {
00687       tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00688     }
00689     tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00690     tmpStr += "</tr>";
00691     tmpStr += "</table>";
00692     return tmpStr;
00693   }
00694 
00695   if ( !event->description().isEmpty() ) {
00696     tmpStr += "<tr>";
00697     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00698     tmpStr += "<td>" + event->richDescription() + "</td>";
00699     tmpStr += "</tr>";
00700   }
00701 
00702   // TODO: print comments?
00703 
00704   int reminderCount = event->alarms().count();
00705   if ( reminderCount > 0 && event->hasEnabledAlarms() ) {
00706     tmpStr += "<tr>";
00707     tmpStr += "<td><b>" +
00708               i18np( "Reminder:", "Reminders:", reminderCount ) +
00709               "</b></td>";
00710     tmpStr += "<td>" + reminderStringList( event ).join( "<br>" ) + "</td>";
00711     tmpStr += "</tr>";
00712   }
00713 
00714   tmpStr += displayViewFormatAttendees( calendar, event );
00715 
00716   int categoryCount = event->categories().count();
00717   if ( categoryCount > 0 ) {
00718     tmpStr += "<tr>";
00719     tmpStr += "<td><b>";
00720     tmpStr += i18np( "Category:", "Categories:", categoryCount ) +
00721               "</b></td>";
00722     tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00723     tmpStr += "</tr>";
00724   }
00725 
00726   int attachmentCount = event->attachments().count();
00727   if ( attachmentCount > 0 ) {
00728     tmpStr += "<tr>";
00729     tmpStr += "<td><b>" +
00730               i18np( "Attachment:", "Attachments:", attachmentCount ) +
00731               "</b></td>";
00732     tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00733     tmpStr += "</tr>";
00734   }
00735   tmpStr += "</table>";
00736 
00737   tmpStr += "<p><em>" + displayViewFormatCreationDate( event, spec ) + "</em>";
00738 
00739   return tmpStr;
00740 }
00741 
00742 static QString displayViewFormatTodo( const Calendar::Ptr &calendar, const QString &sourceName,
00743                                       const Todo::Ptr &todo,
00744                                       const QDate &date, KDateTime::Spec spec )
00745 {
00746   if ( !todo ) {
00747     kDebug() << "IncidenceFormatter::displayViewFormatTodo was called without to-do, quitting";
00748     return QString();
00749   }
00750 
00751   QString tmpStr = displayViewFormatHeader( todo );
00752 
00753   tmpStr += "<table>";
00754   tmpStr += "<col width=\"25%\"/>";
00755   tmpStr += "<col width=\"75%\"/>";
00756 
00757   const QString calStr = calendar ? resourceString( calendar, todo ) : sourceName;
00758   if ( !calStr.isEmpty() ) {
00759     tmpStr += "<tr>";
00760     tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00761     tmpStr += "<td>" + calStr + "</td>";
00762     tmpStr += "</tr>";
00763   }
00764 
00765   if ( !todo->location().isEmpty() ) {
00766     tmpStr += "<tr>";
00767     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00768     tmpStr += "<td>" + todo->richLocation() + "</td>";
00769     tmpStr += "</tr>";
00770   }
00771 
00772   const bool hastStartDate = todo->hasStartDate() && todo->dtStart().isValid();
00773   const bool hasDueDate = todo->hasDueDate() && todo->dtDue().isValid();
00774 
00775   if ( hastStartDate ) {
00776     KDateTime startDt = todo->dtStart( true );
00777     if ( todo->recurs() ) {
00778       if ( date.isValid() ) {
00779         if ( hasDueDate ) {
00780           // In kdepim all recuring to-dos have due date.
00781           const int length = startDt.daysTo( todo->dtDue( true ) );
00782           if ( length >= 0 ) {
00783             startDt.setDate( date.addDays( -length ) );
00784           } else {
00785             kError() << "DTSTART is bigger than DTDUE, todo->uid() is " << todo->uid();
00786             startDt.setDate( date );
00787           }
00788         } else {
00789           kError() << "To-do is recurring but has no DTDUE set, todo->uid() is " << todo->uid();
00790           startDt.setDate( date );
00791         }
00792       }
00793     }
00794     tmpStr += "<tr>";
00795     tmpStr += "<td><b>" +
00796               i18nc( "to-do start date/time", "Start:" ) +
00797               "</b></td>";
00798     tmpStr += "<td>" +
00799               dateTimeToString( startDt, todo->allDay(), false, spec ) +
00800               "</td>";
00801     tmpStr += "</tr>";
00802   }
00803 
00804   if ( hasDueDate ) {
00805     KDateTime dueDt = todo->dtDue();
00806     if ( todo->recurs() ) {
00807       if ( date.isValid() ) {
00808         KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
00809         kdt = kdt.addSecs( -1 );
00810         dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
00811       }
00812     }
00813     tmpStr += "<tr>";
00814     tmpStr += "<td><b>" +
00815               i18nc( "to-do due date/time", "Due:" ) +
00816               "</b></td>";
00817     tmpStr += "<td>" +
00818               dateTimeToString( dueDt, todo->allDay(), false, spec ) +
00819               "</td>";
00820     tmpStr += "</tr>";
00821   }
00822 
00823   QString durStr = durationString( todo );
00824   if ( !durStr.isEmpty() ) {
00825     tmpStr += "<tr>";
00826     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00827     tmpStr += "<td>" + durStr + "</td>";
00828     tmpStr += "</tr>";
00829   }
00830 
00831   if ( todo->recurs() ) {
00832     tmpStr += "<tr>";
00833     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00834     tmpStr += "<td>" +
00835               recurrenceString( todo ) +
00836               "</td>";
00837     tmpStr += "</tr>";
00838   }
00839 
00840   if ( !todo->description().isEmpty() ) {
00841     tmpStr += "<tr>";
00842     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00843     tmpStr += "<td>" + todo->richDescription() + "</td>";
00844     tmpStr += "</tr>";
00845   }
00846 
00847   // TODO: print comments?
00848 
00849   int reminderCount = todo->alarms().count();
00850   if ( reminderCount > 0 && todo->hasEnabledAlarms() ) {
00851     tmpStr += "<tr>";
00852     tmpStr += "<td><b>" +
00853               i18np( "Reminder:", "Reminders:", reminderCount ) +
00854               "</b></td>";
00855     tmpStr += "<td>" + reminderStringList( todo ).join( "<br>" ) + "</td>";
00856     tmpStr += "</tr>";
00857   }
00858 
00859   tmpStr += displayViewFormatAttendees( calendar, todo );
00860 
00861   int categoryCount = todo->categories().count();
00862   if ( categoryCount > 0 ) {
00863     tmpStr += "<tr>";
00864     tmpStr += "<td><b>" +
00865               i18np( "Category:", "Categories:", categoryCount ) +
00866               "</b></td>";
00867     tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00868     tmpStr += "</tr>";
00869   }
00870 
00871   if ( todo->priority() > 0 ) {
00872     tmpStr += "<tr>";
00873     tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00874     tmpStr += "<td>";
00875     tmpStr += QString::number( todo->priority() );
00876     tmpStr += "</td>";
00877     tmpStr += "</tr>";
00878   }
00879 
00880   tmpStr += "<tr>";
00881   if ( todo->isCompleted() ) {
00882     tmpStr += "<td><b>" + i18nc( "Completed: date", "Completed:" ) + "</b></td>";
00883     tmpStr += "<td>";
00884     tmpStr += Stringify::todoCompletedDateTime( todo );
00885   } else {
00886     tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
00887     tmpStr += "<td>";
00888     tmpStr += i18n( "%1%", todo->percentComplete() );
00889   }
00890   tmpStr += "</td>";
00891   tmpStr += "</tr>";
00892 
00893   int attachmentCount = todo->attachments().count();
00894   if ( attachmentCount > 0 ) {
00895     tmpStr += "<tr>";
00896     tmpStr += "<td><b>" +
00897               i18np( "Attachment:", "Attachments:", attachmentCount ) +
00898               "</b></td>";
00899     tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00900     tmpStr += "</tr>";
00901   }
00902   tmpStr += "</table>";
00903 
00904   tmpStr += "<p><em>" + displayViewFormatCreationDate( todo, spec ) + "</em>";
00905 
00906   return tmpStr;
00907 }
00908 
00909 static QString displayViewFormatJournal( const Calendar::Ptr &calendar, const QString &sourceName,
00910                                          const Journal::Ptr &journal, KDateTime::Spec spec )
00911 {
00912   if ( !journal ) {
00913     return QString();
00914   }
00915 
00916   QString tmpStr = displayViewFormatHeader( journal );
00917 
00918   tmpStr += "<table>";
00919   tmpStr += "<col width=\"25%\"/>";
00920   tmpStr += "<col width=\"75%\"/>";
00921 
00922   const QString calStr = calendar ? resourceString( calendar, journal ) : sourceName;
00923   if ( !calStr.isEmpty() ) {
00924     tmpStr += "<tr>";
00925     tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00926     tmpStr += "<td>" + calStr + "</td>";
00927     tmpStr += "</tr>";
00928   }
00929 
00930   tmpStr += "<tr>";
00931   tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00932   tmpStr += "<td>" +
00933             dateToString( journal->dtStart(), false, spec ) +
00934             "</td>";
00935   tmpStr += "</tr>";
00936 
00937   if ( !journal->description().isEmpty() ) {
00938     tmpStr += "<tr>";
00939     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00940     tmpStr += "<td>" + journal->richDescription() + "</td>";
00941     tmpStr += "</tr>";
00942   }
00943 
00944   int categoryCount = journal->categories().count();
00945   if ( categoryCount > 0 ) {
00946     tmpStr += "<tr>";
00947     tmpStr += "<td><b>" +
00948               i18np( "Category:", "Categories:", categoryCount ) +
00949               "</b></td>";
00950     tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00951     tmpStr += "</tr>";
00952   }
00953 
00954   tmpStr += "</table>";
00955 
00956   tmpStr += "<p><em>" + displayViewFormatCreationDate( journal, spec ) + "</em>";
00957 
00958   return tmpStr;
00959 }
00960 
00961 static QString displayViewFormatFreeBusy( const Calendar::Ptr &calendar, const QString &sourceName,
00962                                           const FreeBusy::Ptr &fb, KDateTime::Spec spec )
00963 {
00964   Q_UNUSED( calendar );
00965   Q_UNUSED( sourceName );
00966   if ( !fb ) {
00967     return QString();
00968   }
00969 
00970   QString tmpStr(
00971     htmlAddTag(
00972       "h2", i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) ) );
00973 
00974   tmpStr += htmlAddTag( "h4",
00975                         i18n( "Busy times in date range %1 - %2:",
00976                               dateToString( fb->dtStart(), true, spec ),
00977                               dateToString( fb->dtEnd(), true, spec ) ) );
00978 
00979   QString text =
00980     htmlAddTag( "em",
00981                 htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
00982 
00983   Period::List periods = fb->busyPeriods();
00984   Period::List::iterator it;
00985   for ( it = periods.begin(); it != periods.end(); ++it ) {
00986     Period per = *it;
00987     if ( per.hasDuration() ) {
00988       int dur = per.duration().asSeconds();
00989       QString cont;
00990       if ( dur >= 3600 ) {
00991         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00992         dur %= 3600;
00993       }
00994       if ( dur >= 60 ) {
00995         cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
00996         dur %= 60;
00997       }
00998       if ( dur > 0 ) {
00999         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
01000       }
01001       text += i18nc( "startDate for duration", "%1 for %2",
01002                      dateTimeToString( per.start(), false, true, spec ),
01003                      cont );
01004       text += "<br>";
01005     } else {
01006       if ( per.start().date() == per.end().date() ) {
01007         text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
01008                        dateToString( per.start(), true, spec ),
01009                        timeToString( per.start(), true, spec ),
01010                        timeToString( per.end(), true, spec ) );
01011       } else {
01012         text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
01013                        dateTimeToString( per.start(), false, true, spec ),
01014                        dateTimeToString( per.end(), false, true, spec ) );
01015       }
01016       text += "<br>";
01017     }
01018   }
01019   tmpStr += htmlAddTag( "p", text );
01020   return tmpStr;
01021 }
01022 //@endcond
01023 
01024 //@cond PRIVATE
01025 class KCalUtils::IncidenceFormatter::EventViewerVisitor : public Visitor
01026 {
01027   public:
01028     EventViewerVisitor()
01029       : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
01030 
01031     bool act( const Calendar::Ptr &calendar, IncidenceBase::Ptr incidence, const QDate &date,
01032               KDateTime::Spec spec=KDateTime::Spec() )
01033     {
01034       mCalendar = calendar;
01035       mSourceName.clear();
01036       mDate = date;
01037       mSpec = spec;
01038       mResult = "";
01039       return incidence->accept( *this, incidence );
01040     }
01041 
01042     bool act( const QString &sourceName, IncidenceBase::Ptr incidence, const QDate &date,
01043               KDateTime::Spec spec=KDateTime::Spec() )
01044     {
01045       mSourceName = sourceName;
01046       mDate = date;
01047       mSpec = spec;
01048       mResult = "";
01049       return incidence->accept( *this, incidence );
01050     }
01051 
01052     QString result() const { return mResult; }
01053 
01054   protected:
01055     bool visit( Event::Ptr event )
01056     {
01057       mResult = displayViewFormatEvent( mCalendar, mSourceName, event, mDate, mSpec );
01058       return !mResult.isEmpty();
01059     }
01060     bool visit( Todo::Ptr todo )
01061     {
01062       mResult = displayViewFormatTodo( mCalendar, mSourceName, todo, mDate, mSpec );
01063       return !mResult.isEmpty();
01064     }
01065     bool visit( Journal::Ptr journal )
01066     {
01067       mResult = displayViewFormatJournal( mCalendar, mSourceName, journal, mSpec );
01068       return !mResult.isEmpty();
01069     }
01070     bool visit( FreeBusy::Ptr fb )
01071     {
01072       mResult = displayViewFormatFreeBusy( mCalendar, mSourceName, fb, mSpec );
01073       return !mResult.isEmpty();
01074     }
01075 
01076   protected:
01077     Calendar::Ptr mCalendar;
01078     QString mSourceName;
01079     QDate mDate;
01080     KDateTime::Spec mSpec;
01081     QString mResult;
01082 };
01083 //@endcond
01084 
01085 QString IncidenceFormatter::extensiveDisplayStr( const Calendar::Ptr &calendar,
01086                                                  const IncidenceBase::Ptr &incidence,
01087                                                  const QDate &date,
01088                                                  KDateTime::Spec spec )
01089 {
01090   if ( !incidence ) {
01091     return QString();
01092   }
01093 
01094   EventViewerVisitor v;
01095   if ( v.act( calendar, incidence, date, spec ) ) {
01096     return v.result();
01097   } else {
01098     return QString();
01099   }
01100 }
01101 
01102 QString IncidenceFormatter::extensiveDisplayStr( const QString &sourceName,
01103                                                  const IncidenceBase::Ptr &incidence,
01104                                                  const QDate &date,
01105                                                  KDateTime::Spec spec )
01106 {
01107   if ( !incidence ) {
01108     return QString();
01109   }
01110 
01111   EventViewerVisitor v;
01112   if ( v.act( sourceName, incidence, date, spec ) ) {
01113     return v.result();
01114   } else {
01115     return QString();
01116   }
01117 }
01118 /***********************************************************************
01119  *  Helper functions for the body part formatter of kmail (Invitations)
01120  ***********************************************************************/
01121 
01122 //@cond PRIVATE
01123 static QString string2HTML( const QString &str )
01124 {
01125   return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
01126 }
01127 
01128 static QString cleanHtml( const QString &html )
01129 {
01130   QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
01131   rx.indexIn( html );
01132   QString body = rx.cap( 1 );
01133 
01134   return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
01135 }
01136 
01137 static QString invitationSummary( const Incidence::Ptr &incidence, bool noHtmlMode )
01138 {
01139   QString summaryStr = i18n( "Summary unspecified" );
01140   if ( !incidence->summary().isEmpty() ) {
01141     if ( !incidence->summaryIsRich() ) {
01142       summaryStr = Qt::escape( incidence->summary() );
01143     } else {
01144       summaryStr = incidence->richSummary();
01145       if ( noHtmlMode ) {
01146         summaryStr = cleanHtml( summaryStr );
01147       }
01148     }
01149   }
01150   return summaryStr;
01151 }
01152 
01153 static QString invitationLocation( const Incidence::Ptr &incidence, bool noHtmlMode )
01154 {
01155   QString locationStr = i18n( "Location unspecified" );
01156   if ( !incidence->location().isEmpty() ) {
01157     if ( !incidence->locationIsRich() ) {
01158       locationStr = Qt::escape( incidence->location() );
01159     } else {
01160       locationStr = incidence->richLocation();
01161       if ( noHtmlMode ) {
01162         locationStr = cleanHtml( locationStr );
01163       }
01164     }
01165   }
01166   return locationStr;
01167 }
01168 
01169 static QString eventStartTimeStr( const Event::Ptr &event )
01170 {
01171   QString tmp;
01172   if ( !event->allDay() ) {
01173     tmp =  i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
01174                   dateToString( event->dtStart(), true, KSystemTimeZones::local() ),
01175                   timeToString( event->dtStart(), true, KSystemTimeZones::local() ) );
01176   } else {
01177     tmp = i18nc( "%1: Start Date", "%1 (all day)",
01178                  dateToString( event->dtStart(), true, KSystemTimeZones::local() ) );
01179   }
01180   return tmp;
01181 }
01182 
01183 static QString eventEndTimeStr( const Event::Ptr &event )
01184 {
01185   QString tmp;
01186   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
01187     if ( !event->allDay() ) {
01188       tmp =  i18nc( "%1: End Date, %2: End Time", "%1 %2",
01189                     dateToString( event->dtEnd(), true, KSystemTimeZones::local() ),
01190                     timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
01191     } else {
01192       tmp = i18nc( "%1: End Date", "%1 (all day)",
01193                    dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
01194     }
01195   }
01196   return tmp;
01197 }
01198 
01199 static QString htmlInvitationDetailsBegin()
01200 {
01201   QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
01202   return QString( "<div dir=\"%1\">\n" ).arg( dir );
01203 }
01204 
01205 static QString htmlInvitationDetailsEnd()
01206 {
01207   return "</div>\n";
01208 }
01209 
01210 static QString htmlInvitationDetailsTableBegin()
01211 {
01212   return "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01213 }
01214 
01215 static QString htmlInvitationDetailsTableEnd()
01216 {
01217   return "</table>\n";
01218 }
01219 
01220 static QString diffColor()
01221 {
01222   // Color for printing comparison differences inside invitations.
01223 
01224 //  return  "#DE8519"; // hard-coded color from Outlook2007
01225   return QColor( Qt::red ).name();  //krazy:exclude=qenums TODO make configurable
01226 }
01227 
01228 static QString noteColor()
01229 {
01230   // Color for printing notes inside invitations.
01231   return qApp->palette().color( QPalette::Active, QPalette::Highlight ).name();
01232 }
01233 
01234 static QString htmlRow( const QString &title, const QString &value )
01235 {
01236   if ( !value.isEmpty() ) {
01237     return "<tr><td>" + title + "</td><td>" + value + "</td></tr>\n";
01238   } else {
01239     return QString();
01240   }
01241 }
01242 
01243 static QString htmlRow( const QString &title, const QString &value, const QString &oldvalue )
01244 {
01245   // if 'value' is empty, then print nothing
01246   if ( value.isEmpty() ) {
01247     return QString();
01248   }
01249 
01250   // if 'value' is new or unchanged, then print normally
01251   if ( oldvalue.isEmpty() || value == oldvalue ) {
01252     return htmlRow( title, value );
01253   }
01254 
01255   // if 'value' has changed, then make a special print
01256   QString color = diffColor();
01257   QString newtitle = "<font color=\"" + color + "\">" + title + "</font>";
01258   QString newvalue = "<font color=\"" + color + "\">" + value + "</font>" +
01259                      "&nbsp;" +
01260                      "(<strike>" + oldvalue + "</strike>)";
01261   return htmlRow( newtitle, newvalue );
01262 
01263 }
01264 
01265 static Attendee::Ptr findDelegatedFromMyAttendee( const Incidence::Ptr &incidence )
01266 {
01267   // Return the first attendee that was delegated-from me
01268 
01269   Attendee::Ptr attendee;
01270   if ( !incidence ) {
01271     return attendee;
01272   }
01273 
01274   KEMailSettings settings;
01275   QStringList profiles = settings.profiles();
01276   for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
01277     settings.setProfile( *it );
01278 
01279     QString delegatorName, delegatorEmail;
01280     Attendee::List attendees = incidence->attendees();
01281     Attendee::List::ConstIterator it2;
01282     for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
01283       Attendee::Ptr a = *it2;
01284       KPIMUtils::extractEmailAddressAndName( a->delegator(), delegatorEmail, delegatorName );
01285       if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
01286         attendee = a;
01287         break;
01288       }
01289     }
01290   }
01291   return attendee;
01292 }
01293 
01294 static Attendee::Ptr findMyAttendee( const Incidence::Ptr &incidence )
01295 {
01296   // Return the attendee for the incidence that is probably me
01297 
01298   Attendee::Ptr attendee;
01299   if ( !incidence ) {
01300     return attendee;
01301   }
01302 
01303   KEMailSettings settings;
01304   QStringList profiles = settings.profiles();
01305   for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
01306     settings.setProfile( *it );
01307 
01308     Attendee::List attendees = incidence->attendees();
01309     Attendee::List::ConstIterator it2;
01310     for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
01311       Attendee::Ptr a = *it2;
01312       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
01313         attendee = a;
01314         break;
01315       }
01316     }
01317   }
01318   return attendee;
01319 }
01320 
01321 static Attendee::Ptr findAttendee( const Incidence::Ptr &incidence,
01322                                    const QString &email )
01323 {
01324   // Search for an attendee by email address
01325 
01326   Attendee::Ptr attendee;
01327   if ( !incidence ) {
01328     return attendee;
01329   }
01330 
01331   Attendee::List attendees = incidence->attendees();
01332   Attendee::List::ConstIterator it;
01333   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
01334     Attendee::Ptr a = *it;
01335     if ( email == a->email() ) {
01336       attendee = a;
01337       break;
01338     }
01339   }
01340   return attendee;
01341 }
01342 
01343 static bool rsvpRequested( const Incidence::Ptr &incidence )
01344 {
01345   if ( !incidence ) {
01346     return false;
01347   }
01348 
01349   //use a heuristic to determine if a response is requested.
01350 
01351   bool rsvp = true; // better send superfluously than not at all
01352   Attendee::List attendees = incidence->attendees();
01353   Attendee::List::ConstIterator it;
01354   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
01355     if ( it == attendees.constBegin() ) {
01356       rsvp = (*it)->RSVP(); // use what the first one has
01357     } else {
01358       if ( (*it)->RSVP() != rsvp ) {
01359         rsvp = true; // they differ, default
01360         break;
01361       }
01362     }
01363   }
01364   return rsvp;
01365 }
01366 
01367 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
01368 {
01369   if ( rsvpRequested ) {
01370     if ( role.isEmpty() ) {
01371       return i18n( "Your response is requested" );
01372     } else {
01373       return i18n( "Your response as <b>%1</b> is requested", role );
01374     }
01375   } else {
01376     if ( role.isEmpty() ) {
01377       return i18n( "No response is necessary" );
01378     } else {
01379       return i18n( "No response as <b>%1</b> is necessary", role );
01380     }
01381   }
01382 }
01383 
01384 static QString myStatusStr( Incidence::Ptr incidence )
01385 {
01386   QString ret;
01387   Attendee::Ptr a = findMyAttendee( incidence );
01388   if ( a &&
01389        a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01390     ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)",
01391                 Stringify::attendeeStatus( a->status() ) );
01392   }
01393   return ret;
01394 }
01395 
01396 static QString invitationNote( const QString &title, const QString &note,
01397                                const QString &tag, const QString &color )
01398 {
01399   QString noteStr;
01400   if ( !note.isEmpty() ) {
01401     noteStr += "<table border=\"0\" style=\"margin-top:4px;\">";
01402     noteStr += "<tr><center><td>";
01403     if ( !color.isEmpty() ) {
01404       noteStr += "<font color=\"" + color + "\">";
01405     }
01406     if ( !title.isEmpty() ) {
01407       if ( !tag.isEmpty() ) {
01408         noteStr += htmlAddTag( tag, title );
01409       } else {
01410         noteStr += title;
01411       }
01412     }
01413     noteStr += "&nbsp;" + note;
01414     if ( !color.isEmpty() ) {
01415       noteStr += "</font>";
01416     }
01417     noteStr += "</td></center></tr>";
01418     noteStr += "</table>";
01419   }
01420   return noteStr;
01421 }
01422 
01423 static QString invitationPerson( const QString &email, const QString &name, const QString &uid,
01424                                  const QString &comment )
01425 {
01426   QPair<QString, QString> s = searchNameAndUid( email, name, uid );
01427   const QString printName = s.first;
01428   const QString printUid = s.second;
01429 
01430   QString personString;
01431   // Make the uid link
01432   if ( !printUid.isEmpty() ) {
01433     personString = htmlAddUidLink( email, printName, printUid );
01434   } else {
01435     // No UID, just show some text
01436     personString = ( printName.isEmpty() ? email : printName );
01437   }
01438   if ( !comment.isEmpty() ) {
01439     personString = i18nc( "name (comment)", "%1 (%2)", personString, comment );
01440   }
01441   personString += '\n';
01442 
01443   // Make the mailto link
01444   if ( !email.isEmpty() ) {
01445     personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
01446   }
01447   personString += '\n';
01448 
01449   return personString;
01450 }
01451 
01452 static QString invitationDetailsIncidence( const Incidence::Ptr &incidence, bool noHtmlMode )
01453 {
01454   // if description and comment -> use both
01455   // if description, but no comment -> use the desc as the comment (and no desc)
01456   // if comment, but no description -> use the comment and no description
01457 
01458   QString html;
01459   QString descr;
01460   QStringList comments;
01461 
01462   if ( incidence->comments().isEmpty() ) {
01463     if ( !incidence->description().isEmpty() ) {
01464       // use description as comments
01465       if ( !incidence->descriptionIsRich() ) {
01466         comments << string2HTML( incidence->description() );
01467       } else {
01468         comments << incidence->richDescription();
01469         if ( noHtmlMode ) {
01470           comments[0] = cleanHtml( comments[0] );
01471         }
01472         comments[0] = htmlAddTag( "p", comments[0] );
01473       }
01474     }
01475     //else desc and comments are empty
01476   } else {
01477     // non-empty comments
01478     foreach ( const QString &c, incidence->comments() ) {
01479       if ( !c.isEmpty() ) {
01480         // kcalutils doesn't know about richtext comments, so we need to guess
01481         if ( !Qt::mightBeRichText( c ) ) {
01482           comments << string2HTML( c );
01483         } else {
01484           if ( noHtmlMode ) {
01485             comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) );
01486           } else {
01487             comments << c;
01488           }
01489         }
01490       }
01491     }
01492     if ( !incidence->description().isEmpty() ) {
01493       // use description too
01494       if ( !incidence->descriptionIsRich() ) {
01495         descr = string2HTML( incidence->description() );
01496       } else {
01497         descr = incidence->richDescription();
01498         if ( noHtmlMode ) {
01499           descr = cleanHtml( descr );
01500         }
01501         descr = htmlAddTag( "p", descr );
01502       }
01503     }
01504   }
01505 
01506   if( !descr.isEmpty() ) {
01507     html += "<p>";
01508     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01509     html += "<tr><td><center>" +
01510             htmlAddTag( "u", i18n( "Description:" ) ) +
01511             "</center></td></tr>";
01512     html += "<tr><td>" + descr + "</td></tr>";
01513     html += "</table>";
01514   }
01515 
01516   if ( !comments.isEmpty() ) {
01517     html += "<p>";
01518     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01519     html += "<tr><td><center>" +
01520             htmlAddTag( "u", i18n( "Comments:" ) ) +
01521             "</center></td></tr>";
01522     html += "<tr><td>";
01523     if ( comments.count() > 1 ) {
01524       html += "<ul>";
01525       for ( int i=0; i < comments.count(); ++i ) {
01526         html += "<li>" + comments[i] + "</li>";
01527       }
01528       html += "</ul>";
01529     } else {
01530       html += comments[0];
01531     }
01532     html += "</td></tr>";
01533     html += "</table>";
01534   }
01535   return html;
01536 }
01537 
01538 static QString invitationDetailsEvent( const Event::Ptr &event, bool noHtmlMode,
01539                                        KDateTime::Spec spec )
01540 {
01541   // Invitation details are formatted into an HTML table
01542   if ( !event ) {
01543     return QString();
01544   }
01545 
01546   QString html = htmlInvitationDetailsBegin();
01547   html += htmlInvitationDetailsTableBegin();
01548 
01549   // Invitation summary & location rows
01550   html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
01551   html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
01552 
01553   // If a 1 day event
01554   if ( event->dtStart().date() == event->dtEnd().date() ) {
01555     html += htmlRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
01556     if ( !event->allDay() ) {
01557       html += htmlRow( i18n( "Time:" ),
01558                        timeToString( event->dtStart(), true, spec ) +
01559                        " - " +
01560                        timeToString( event->dtEnd(), true, spec ) );
01561     }
01562   } else {
01563     html += htmlRow( i18nc( "starting date", "From:" ),
01564                      dateToString( event->dtStart(), false, spec ) );
01565     if ( !event->allDay() ) {
01566       html += htmlRow( i18nc( "starting time", "At:" ),
01567                        timeToString( event->dtStart(), true, spec ) );
01568     }
01569     if ( event->hasEndDate() ) {
01570       html += htmlRow( i18nc( "ending date", "To:" ),
01571                        dateToString( event->dtEnd(), false, spec ) );
01572       if ( !event->allDay() ) {
01573         html += htmlRow( i18nc( "ending time", "At:" ),
01574                          timeToString( event->dtEnd(), true, spec ) );
01575       }
01576     } else {
01577       html += htmlRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) );
01578     }
01579   }
01580 
01581   // Invitation Duration Row
01582   html += htmlRow( i18n( "Duration:" ), durationString( event ) );
01583 
01584   // Invitation Recurrence Row
01585   if ( event->recurs() ) {
01586     html += htmlRow( i18n( "Recurrence:" ), recurrenceString( event ) );
01587   }
01588 
01589   html += htmlInvitationDetailsTableEnd();
01590   html += invitationDetailsIncidence( event, noHtmlMode );
01591   html += htmlInvitationDetailsEnd();
01592 
01593   return html;
01594 }
01595 
01596 static QString invitationDetailsEvent( const Event::Ptr &event, const Event::Ptr &oldevent,
01597                                        const ScheduleMessage::Ptr message, bool noHtmlMode,
01598                                        KDateTime::Spec spec )
01599 {
01600   if ( !oldevent ) {
01601     return invitationDetailsEvent( event, noHtmlMode, spec );
01602   }
01603 
01604   QString html;
01605 
01606   // Print extra info typically dependent on the iTIP
01607   if ( message->method() == iTIPDeclineCounter ) {
01608     html += "<br>";
01609     html += invitationNote( QString(),
01610                             i18n( "Please respond again to the original proposal." ),
01611                             QString(), noteColor() );
01612   }
01613 
01614   html += htmlInvitationDetailsBegin();
01615   html += htmlInvitationDetailsTableBegin();
01616 
01617   html += htmlRow( i18n( "What:" ),
01618                    invitationSummary( event, noHtmlMode ),
01619                    invitationSummary( oldevent, noHtmlMode ) );
01620 
01621   html += htmlRow( i18n( "Where:" ),
01622                    invitationLocation( event, noHtmlMode ),
01623                    invitationLocation( oldevent, noHtmlMode ) );
01624 
01625   // If a 1 day event
01626   if ( event->dtStart().date() == event->dtEnd().date() ) {
01627     html += htmlRow( i18n( "Date:" ),
01628                      dateToString( event->dtStart(), false ),
01629                      dateToString( oldevent->dtStart(), false ) );
01630     QString spanStr, oldspanStr;
01631     if ( !event->allDay() ) {
01632       spanStr = timeToString( event->dtStart(), true ) +
01633                 " - " +
01634                 timeToString( event->dtEnd(), true );
01635     }
01636     if ( !oldevent->allDay() ) {
01637       oldspanStr = timeToString( oldevent->dtStart(), true ) +
01638                    " - " +
01639                    timeToString( oldevent->dtEnd(), true );
01640     }
01641     html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
01642   } else {
01643     html += htmlRow( i18nc( "Starting date of an event", "From:" ),
01644                      dateToString( event->dtStart(), false ),
01645                      dateToString( oldevent->dtStart(), false ) );
01646     QString startStr, oldstartStr;
01647     if ( !event->allDay() ) {
01648       startStr = timeToString( event->dtStart(), true );
01649     }
01650     if ( !oldevent->allDay() ) {
01651       oldstartStr = timeToString( oldevent->dtStart(), true );
01652     }
01653     html += htmlRow( i18nc( "Starting time of an event", "At:" ), startStr, oldstartStr );
01654     if ( event->hasEndDate() ) {
01655       html += htmlRow( i18nc( "Ending date of an event", "To:" ),
01656                        dateToString( event->dtEnd(), false ),
01657                        dateToString( oldevent->dtEnd(), false ) );
01658       QString endStr, oldendStr;
01659       if ( !event->allDay() ) {
01660         endStr = timeToString( event->dtEnd(), true );
01661       }
01662       if ( !oldevent->allDay() ) {
01663         oldendStr = timeToString( oldevent->dtEnd(), true );
01664       }
01665       html += htmlRow( i18nc( "Starting time of an event", "At:" ), endStr, oldendStr );
01666     } else {
01667       QString endStr = i18n( "no end date specified" );
01668       QString oldendStr;
01669       if ( !oldevent->hasEndDate() ) {
01670         oldendStr = i18n( "no end date specified" );
01671       } else {
01672         oldendStr = dateTimeToString( oldevent->dtEnd(), oldevent->allDay(), false );
01673       }
01674       html += htmlRow( i18nc( "Ending date of an event", "To:" ), endStr, oldendStr );
01675     }
01676   }
01677 
01678   html += htmlRow( i18n( "Duration:" ), durationString( event ), durationString( oldevent ) );
01679 
01680   QString recurStr, oldrecurStr;
01681   if ( event->recurs() ||  oldevent->recurs() ) {
01682     recurStr = recurrenceString( event );
01683     oldrecurStr = recurrenceString( oldevent );
01684   }
01685   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01686 
01687   html += htmlInvitationDetailsTableEnd();
01688   html += invitationDetailsIncidence( event, noHtmlMode );
01689   html += htmlInvitationDetailsEnd();
01690 
01691   return html;
01692 }
01693 
01694 static QString invitationDetailsTodo( const Todo::Ptr &todo, bool noHtmlMode,
01695                                       KDateTime::Spec spec )
01696 {
01697   // To-do details are formatted into an HTML table
01698   if ( !todo ) {
01699     return QString();
01700   }
01701 
01702   QString html = htmlInvitationDetailsBegin();
01703   html += htmlInvitationDetailsTableBegin();
01704 
01705   // Invitation summary & location rows
01706   html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
01707   html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
01708 
01709   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01710     html += htmlRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
01711     if ( !todo->allDay() ) {
01712       html += htmlRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) );
01713     }
01714   }
01715   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01716     html += htmlRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
01717     if ( !todo->allDay() ) {
01718       html += htmlRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) );
01719     }
01720   } else {
01721     html += htmlRow( i18n( "Due Date:" ), i18nc( "Due Date: None", "None" ) );
01722   }
01723 
01724   // Invitation Duration Row
01725   html += htmlRow( i18n( "Duration:" ), durationString( todo ) );
01726 
01727   // Completeness
01728   if ( todo->percentComplete() > 0 ) {
01729     html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) );
01730   }
01731 
01732   // Invitation Recurrence Row
01733   if ( todo->recurs() ) {
01734     html += htmlRow( i18n( "Recurrence:" ), recurrenceString( todo ) );
01735   }
01736 
01737   html += htmlInvitationDetailsTableEnd();
01738   html += invitationDetailsIncidence( todo, noHtmlMode );
01739   html += htmlInvitationDetailsEnd();
01740 
01741   return html;
01742 }
01743 
01744 static QString invitationDetailsTodo( const Todo::Ptr &todo, const Todo::Ptr &oldtodo,
01745                                       const ScheduleMessage::Ptr message, bool noHtmlMode,
01746                                       KDateTime::Spec spec )
01747 {
01748   if ( !oldtodo ) {
01749     return invitationDetailsTodo( todo, noHtmlMode, spec );
01750   }
01751 
01752   QString html;
01753 
01754   // Print extra info typically dependent on the iTIP
01755   if ( message->method() == iTIPDeclineCounter ) {
01756     html += "<br>";
01757     html += invitationNote( QString(),
01758                             i18n( "Please respond again to the original proposal." ),
01759                             QString(), noteColor() );
01760   }
01761 
01762   html += htmlInvitationDetailsBegin();
01763   html += htmlInvitationDetailsTableBegin();
01764 
01765   html += htmlRow( i18n( "What:" ),
01766                    invitationSummary( todo, noHtmlMode ),
01767                    invitationSummary( todo, noHtmlMode ) );
01768 
01769   html += htmlRow( i18n( "Where:" ),
01770                    invitationLocation( todo, noHtmlMode ),
01771                    invitationLocation( oldtodo, noHtmlMode ) );
01772 
01773   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01774     html += htmlRow( i18n( "Start Date:" ),
01775                      dateToString( todo->dtStart(), false ),
01776                      dateToString( oldtodo->dtStart(), false ) );
01777     QString startTimeStr, oldstartTimeStr;
01778     if ( !todo->allDay() || !oldtodo->allDay() ) {
01779       startTimeStr = todo->allDay() ?
01780                      i18n( "All day" ) : timeToString( todo->dtStart(), false );
01781       oldstartTimeStr = oldtodo->allDay() ?
01782                         i18n( "All day" ) : timeToString( oldtodo->dtStart(), false );
01783     }
01784     html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
01785   }
01786   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01787     html += htmlRow( i18n( "Due Date:" ),
01788                      dateToString( todo->dtDue(), false ),
01789                      dateToString( oldtodo->dtDue(), false ) );
01790     QString endTimeStr, oldendTimeStr;
01791     if ( !todo->allDay() || !oldtodo->allDay() ) {
01792       endTimeStr = todo->allDay() ?
01793                    i18n( "All day" ) : timeToString( todo->dtDue(), false );
01794       oldendTimeStr = oldtodo->allDay() ?
01795                       i18n( "All day" ) : timeToString( oldtodo->dtDue(), false );
01796     }
01797     html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
01798   } else {
01799     QString dueStr = i18nc( "Due Date: None", "None" );
01800     QString olddueStr;
01801     if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
01802       olddueStr = i18nc( "Due Date: None", "None" );
01803    } else {
01804       olddueStr = dateTimeToString( oldtodo->dtDue(), oldtodo->allDay(), false );
01805     }
01806     html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
01807   }
01808 
01809   html += htmlRow( i18n( "Duration:" ), durationString( todo ), durationString( oldtodo ) );
01810 
01811   QString completionStr, oldcompletionStr;
01812   if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) {
01813     completionStr = i18n( "%1%", todo->percentComplete() );
01814     oldcompletionStr = i18n( "%1%", oldtodo->percentComplete() );
01815   }
01816   html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr );
01817 
01818   QString recurStr, oldrecurStr;
01819   if ( todo->recurs() || oldtodo->recurs() ) {
01820     recurStr = recurrenceString( todo );
01821     oldrecurStr = recurrenceString( oldtodo );
01822   }
01823   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01824 
01825   html += htmlInvitationDetailsTableEnd();
01826   html += invitationDetailsIncidence( todo, noHtmlMode );
01827 
01828   html += htmlInvitationDetailsEnd();
01829 
01830   return html;
01831 }
01832 
01833 static QString invitationDetailsJournal( const Journal::Ptr &journal, bool noHtmlMode,
01834                                          KDateTime::Spec spec )
01835 {
01836   if ( !journal ) {
01837     return QString();
01838   }
01839 
01840   QString html = htmlInvitationDetailsBegin();
01841   html += htmlInvitationDetailsTableBegin();
01842 
01843   html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
01844   html += htmlRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
01845 
01846   html += htmlInvitationDetailsTableEnd();
01847   html += invitationDetailsIncidence( journal, noHtmlMode );
01848   html += htmlInvitationDetailsEnd();
01849 
01850   return html;
01851 }
01852 
01853 static QString invitationDetailsJournal( const Journal::Ptr &journal,
01854                                          const Journal::Ptr &oldjournal,
01855                                          bool noHtmlMode, KDateTime::Spec spec )
01856 {
01857   if ( !oldjournal ) {
01858     return invitationDetailsJournal( journal, noHtmlMode, spec );
01859   }
01860 
01861   QString html = htmlInvitationDetailsBegin();
01862   html += htmlInvitationDetailsTableBegin();
01863 
01864   html += htmlRow( i18n( "What:" ),
01865                    invitationSummary( journal, noHtmlMode ),
01866                    invitationSummary( oldjournal, noHtmlMode ) );
01867 
01868   html += htmlRow( i18n( "Date:" ),
01869                    dateToString( journal->dtStart(), false, spec ),
01870                    dateToString( oldjournal->dtStart(), false, spec ) );
01871 
01872   html += htmlInvitationDetailsTableEnd();
01873   html += invitationDetailsIncidence( journal, noHtmlMode );
01874   html += htmlInvitationDetailsEnd();
01875 
01876   return html;
01877 }
01878 
01879 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, bool noHtmlMode,
01880                                           KDateTime::Spec spec )
01881 {
01882   Q_UNUSED( noHtmlMode );
01883 
01884   if ( !fb ) {
01885     return QString();
01886   }
01887 
01888   QString html = htmlInvitationDetailsTableBegin();
01889 
01890   html += htmlRow( i18n( "Person:" ), fb->organizer()->fullName() );
01891   html += htmlRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
01892   html += htmlRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
01893 
01894   html += "<tr><td colspan=2><hr></td></tr>\n";
01895   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01896 
01897   Period::List periods = fb->busyPeriods();
01898   Period::List::iterator it;
01899   for ( it = periods.begin(); it != periods.end(); ++it ) {
01900     Period per = *it;
01901     if ( per.hasDuration() ) {
01902       int dur = per.duration().asSeconds();
01903       QString cont;
01904       if ( dur >= 3600 ) {
01905         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
01906         dur %= 3600;
01907       }
01908       if ( dur >= 60 ) {
01909         cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
01910         dur %= 60;
01911       }
01912       if ( dur > 0 ) {
01913         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
01914       }
01915       html += htmlRow( QString(),
01916                        i18nc( "startDate for duration", "%1 for %2",
01917                               KGlobal::locale()->formatDateTime(
01918                                 per.start().dateTime(), KLocale::LongDate ),
01919                               cont ) );
01920     } else {
01921       QString cont;
01922       if ( per.start().date() == per.end().date() ) {
01923         cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
01924                       KGlobal::locale()->formatDate( per.start().date() ),
01925                       KGlobal::locale()->formatTime( per.start().time() ),
01926                       KGlobal::locale()->formatTime( per.end().time() ) );
01927       } else {
01928         cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
01929                       KGlobal::locale()->formatDateTime(
01930                         per.start().dateTime(), KLocale::LongDate ),
01931                       KGlobal::locale()->formatDateTime(
01932                         per.end().dateTime(), KLocale::LongDate ) );
01933       }
01934 
01935       html += htmlRow( QString(), cont );
01936     }
01937   }
01938 
01939   html += htmlInvitationDetailsTableEnd();
01940   return html;
01941 }
01942 
01943 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, const FreeBusy::Ptr &oldfb,
01944                                           bool noHtmlMode, KDateTime::Spec spec )
01945 {
01946   Q_UNUSED( oldfb );
01947   return invitationDetailsFreeBusy( fb, noHtmlMode, spec );
01948 }
01949 
01950 static bool replyMeansCounter( const Incidence::Ptr &incidence )
01951 {
01952   Q_UNUSED( incidence );
01953   return false;
01968 }
01969 
01970 static QString invitationHeaderEvent( const Event::Ptr &event,
01971                                       const Incidence::Ptr &existingIncidence,
01972                                       ScheduleMessage::Ptr msg, const QString &sender )
01973 {
01974   if ( !msg || !event ) {
01975     return QString();
01976   }
01977 
01978   switch ( msg->method() ) {
01979   case iTIPPublish:
01980     return i18n( "This invitation has been published" );
01981   case iTIPRequest:
01982     if ( existingIncidence && event->revision() > 0 ) {
01983       QString orgStr = organizerName( event, sender );
01984       if ( senderIsOrganizer( event, sender ) ) {
01985         return i18n( "This invitation has been updated by the organizer %1", orgStr );
01986       } else {
01987         return i18n( "This invitation has been updated by %1 as a representative of %2",
01988                      sender, orgStr );
01989       }
01990     }
01991     if ( iamOrganizer( event ) ) {
01992       return i18n( "I created this invitation" );
01993     } else {
01994       QString orgStr = organizerName( event, sender );
01995       if ( senderIsOrganizer( event, sender ) ) {
01996         return i18n( "You received an invitation from %1", orgStr );
01997       } else {
01998         return i18n( "You received an invitation from %1 as a representative of %2",
01999                      sender, orgStr );
02000       }
02001     }
02002   case iTIPRefresh:
02003     return i18n( "This invitation was refreshed" );
02004   case iTIPCancel:
02005     if ( iamOrganizer( event ) ) {
02006       return i18n( "This invitation has been canceled" );
02007     } else {
02008       return i18n( "The organizer has revoked the invitation" );
02009     }
02010   case iTIPAdd:
02011     return i18n( "Addition to the invitation" );
02012   case iTIPReply:
02013   {
02014     if ( replyMeansCounter( event ) ) {
02015       return i18n( "%1 makes this counter proposal", firstAttendeeName( event, sender ) );
02016     }
02017 
02018     Attendee::List attendees = event->attendees();
02019     if( attendees.count() == 0 ) {
02020       kDebug() << "No attendees in the iCal reply!";
02021       return QString();
02022     }
02023     if ( attendees.count() != 1 ) {
02024       kDebug() << "Warning: attendeecount in the reply should be 1"
02025                << "but is" << attendees.count();
02026     }
02027     QString attendeeName = firstAttendeeName( event, sender );
02028 
02029     QString delegatorName, dummy;
02030     Attendee::Ptr attendee = *attendees.begin();
02031     KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
02032     if ( delegatorName.isEmpty() ) {
02033       delegatorName = attendee->delegator();
02034     }
02035 
02036     switch( attendee->status() ) {
02037     case Attendee::NeedsAction:
02038       return i18n( "%1 indicates this invitation still needs some action", attendeeName );
02039     case Attendee::Accepted:
02040       if ( event->revision() > 0 ) {
02041         if ( !sender.isEmpty() ) {
02042           return i18n( "This invitation has been updated by attendee %1", sender );
02043         } else {
02044           return i18n( "This invitation has been updated by an attendee" );
02045         }
02046       } else {
02047         if ( delegatorName.isEmpty() ) {
02048           return i18n( "%1 accepts this invitation", attendeeName );
02049         } else {
02050           return i18n( "%1 accepts this invitation on behalf of %2",
02051                        attendeeName, delegatorName );
02052         }
02053       }
02054     case Attendee::Tentative:
02055       if ( delegatorName.isEmpty() ) {
02056         return i18n( "%1 tentatively accepts this invitation", attendeeName );
02057       } else {
02058         return i18n( "%1 tentatively accepts this invitation on behalf of %2",
02059                      attendeeName, delegatorName );
02060       }
02061     case Attendee::Declined:
02062       if ( delegatorName.isEmpty() ) {
02063         return i18n( "%1 declines this invitation", attendeeName );
02064       } else {
02065         return i18n( "%1 declines this invitation on behalf of %2",
02066                      attendeeName, delegatorName );
02067       }
02068     case Attendee::Delegated:
02069     {
02070       QString delegate, dummy;
02071       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02072       if ( delegate.isEmpty() ) {
02073         delegate = attendee->delegate();
02074       }
02075       if ( !delegate.isEmpty() ) {
02076         return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
02077       } else {
02078         return i18n( "%1 has delegated this invitation", attendeeName );
02079       }
02080     }
02081     case Attendee::Completed:
02082       return i18n( "This invitation is now completed" );
02083     case Attendee::InProcess:
02084       return i18n( "%1 is still processing the invitation", attendeeName );
02085     case Attendee::None:
02086       return i18n( "Unknown response to this invitation" );
02087     }
02088     break;
02089   }
02090   case iTIPCounter:
02091     return i18n( "%1 makes this counter proposal",
02092                  firstAttendeeName( event, i18n( "Sender" ) ) );
02093 
02094   case iTIPDeclineCounter:
02095   {
02096     QString orgStr = organizerName( event, sender );
02097     if ( senderIsOrganizer( event, sender ) ) {
02098       return i18n( "%1 declines your counter proposal", orgStr );
02099     } else {
02100       return i18n( "%1 declines your counter proposal on behalf of %2", sender, orgStr );
02101     }
02102   }
02103 
02104   case iTIPNoMethod:
02105     return i18n( "Error: Event iTIP message with unknown method" );
02106   }
02107   kError() << "encountered an iTIP method that we do not support";
02108   return QString();
02109 }
02110 
02111 static QString invitationHeaderTodo( const Todo::Ptr &todo,
02112                                      const Incidence::Ptr &existingIncidence,
02113                                      ScheduleMessage::Ptr msg, const QString &sender )
02114 {
02115   if ( !msg || !todo ) {
02116     return QString();
02117   }
02118 
02119   switch ( msg->method() ) {
02120   case iTIPPublish:
02121     return i18n( "This to-do has been published" );
02122   case iTIPRequest:
02123     if ( existingIncidence && todo->revision() > 0 ) {
02124       QString orgStr = organizerName( todo, sender );
02125       if ( senderIsOrganizer( todo, sender ) ) {
02126         return i18n( "This to-do has been updated by the organizer %1", orgStr );
02127       } else {
02128         return i18n( "This to-do has been updated by %1 as a representative of %2",
02129                      sender, orgStr );
02130       }
02131     } else {
02132       if ( iamOrganizer( todo ) ) {
02133         return i18n( "I created this to-do" );
02134       } else {
02135         QString orgStr = organizerName( todo, sender );
02136         if ( senderIsOrganizer( todo, sender ) ) {
02137           return i18n( "You have been assigned this to-do by %1", orgStr );
02138         } else {
02139           return i18n( "You have been assigned this to-do by %1 as a representative of %2",
02140                        sender, orgStr );
02141         }
02142       }
02143     }
02144   case iTIPRefresh:
02145     return i18n( "This to-do was refreshed" );
02146   case iTIPCancel:
02147     if ( iamOrganizer( todo ) ) {
02148       return i18n( "This to-do was canceled" );
02149     } else {
02150       return i18n( "The organizer has revoked this to-do" );
02151     }
02152   case iTIPAdd:
02153     return i18n( "Addition to the to-do" );
02154   case iTIPReply:
02155   {
02156     if ( replyMeansCounter( todo ) ) {
02157       return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02158     }
02159 
02160     Attendee::List attendees = todo->attendees();
02161     if ( attendees.count() == 0 ) {
02162       kDebug() << "No attendees in the iCal reply!";
02163       return QString();
02164     }
02165     if ( attendees.count() != 1 ) {
02166       kDebug() << "Warning: attendeecount in the reply should be 1"
02167                << "but is" << attendees.count();
02168     }
02169     QString attendeeName = firstAttendeeName( todo, sender );
02170 
02171     QString delegatorName, dummy;
02172     Attendee::Ptr attendee = *attendees.begin();
02173     KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName );
02174     if ( delegatorName.isEmpty() ) {
02175       delegatorName = attendee->delegator();
02176     }
02177 
02178     switch( attendee->status() ) {
02179     case Attendee::NeedsAction:
02180       return i18n( "%1 indicates this to-do assignment still needs some action",
02181                    attendeeName );
02182     case Attendee::Accepted:
02183       if ( todo->revision() > 0 ) {
02184         if ( !sender.isEmpty() ) {
02185           if ( todo->isCompleted() ) {
02186             return i18n( "This to-do has been completed by assignee %1", sender );
02187           } else {
02188             return i18n( "This to-do has been updated by assignee %1", sender );
02189           }
02190         } else {
02191           if ( todo->isCompleted() ) {
02192             return i18n( "This to-do has been completed by an assignee" );
02193           } else {
02194             return i18n( "This to-do has been updated by an assignee" );
02195           }
02196         }
02197       } else {
02198         if ( delegatorName.isEmpty() ) {
02199           return i18n( "%1 accepts this to-do", attendeeName );
02200         } else {
02201           return i18n( "%1 accepts this to-do on behalf of %2",
02202                        attendeeName, delegatorName );
02203         }
02204       }
02205     case Attendee::Tentative:
02206       if ( delegatorName.isEmpty() ) {
02207         return i18n( "%1 tentatively accepts this to-do", attendeeName );
02208       } else {
02209         return i18n( "%1 tentatively accepts this to-do on behalf of %2",
02210                      attendeeName, delegatorName );
02211       }
02212     case Attendee::Declined:
02213       if ( delegatorName.isEmpty() ) {
02214         return i18n( "%1 declines this to-do", attendeeName );
02215       } else {
02216         return i18n( "%1 declines this to-do on behalf of %2",
02217                      attendeeName, delegatorName );
02218       }
02219     case Attendee::Delegated:
02220     {
02221       QString delegate, dummy;
02222       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02223       if ( delegate.isEmpty() ) {
02224         delegate = attendee->delegate();
02225       }
02226       if ( !delegate.isEmpty() ) {
02227         return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate );
02228       } else {
02229         return i18n( "%1 has delegated this to-do", attendeeName );
02230       }
02231     }
02232     case Attendee::Completed:
02233       return i18n( "The request for this to-do is now completed" );
02234     case Attendee::InProcess:
02235       return i18n( "%1 is still processing the to-do", attendeeName );
02236     case Attendee::None:
02237       return i18n( "Unknown response to this to-do" );
02238     }
02239     break;
02240   }
02241   case iTIPCounter:
02242     return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02243 
02244   case iTIPDeclineCounter:
02245   {
02246     QString orgStr = organizerName( todo, sender );
02247     if ( senderIsOrganizer( todo, sender ) ) {
02248       return i18n( "%1 declines the counter proposal", orgStr );
02249     } else {
02250       return i18n( "%1 declines the counter proposal on behalf of %2", sender, orgStr );
02251     }
02252   }
02253 
02254   case iTIPNoMethod:
02255     return i18n( "Error: To-do iTIP message with unknown method" );
02256   }
02257   kError() << "encountered an iTIP method that we do not support";
02258   return QString();
02259 }
02260 
02261 static QString invitationHeaderJournal( const Journal::Ptr &journal,
02262                                         ScheduleMessage::Ptr msg )
02263 {
02264   if ( !msg || !journal ) {
02265     return QString();
02266   }
02267 
02268   switch ( msg->method() ) {
02269   case iTIPPublish:
02270     return i18n( "This journal has been published" );
02271   case iTIPRequest:
02272     return i18n( "You have been assigned this journal" );
02273   case iTIPRefresh:
02274     return i18n( "This journal was refreshed" );
02275   case iTIPCancel:
02276     return i18n( "This journal was canceled" );
02277   case iTIPAdd:
02278     return i18n( "Addition to the journal" );
02279   case iTIPReply:
02280   {
02281     if ( replyMeansCounter( journal ) ) {
02282       return i18n( "Sender makes this counter proposal" );
02283     }
02284 
02285     Attendee::List attendees = journal->attendees();
02286     if ( attendees.count() == 0 ) {
02287       kDebug() << "No attendees in the iCal reply!";
02288       return QString();
02289     }
02290     if( attendees.count() != 1 ) {
02291       kDebug() << "Warning: attendeecount in the reply should be 1 "
02292                << "but is " << attendees.count();
02293     }
02294     Attendee::Ptr attendee = *attendees.begin();
02295 
02296     switch( attendee->status() ) {
02297     case Attendee::NeedsAction:
02298       return i18n( "Sender indicates this journal assignment still needs some action" );
02299     case Attendee::Accepted:
02300       return i18n( "Sender accepts this journal" );
02301     case Attendee::Tentative:
02302       return i18n( "Sender tentatively accepts this journal" );
02303     case Attendee::Declined:
02304       return i18n( "Sender declines this journal" );
02305     case Attendee::Delegated:
02306       return i18n( "Sender has delegated this request for the journal" );
02307     case Attendee::Completed:
02308       return i18n( "The request for this journal is now completed" );
02309     case Attendee::InProcess:
02310       return i18n( "Sender is still processing the invitation" );
02311     case Attendee::None:
02312       return i18n( "Unknown response to this journal" );
02313     }
02314     break;
02315   }
02316   case iTIPCounter:
02317     return i18n( "Sender makes this counter proposal" );
02318   case iTIPDeclineCounter:
02319     return i18n( "Sender declines the counter proposal" );
02320   case iTIPNoMethod:
02321     return i18n( "Error: Journal iTIP message with unknown method" );
02322   }
02323   kError() << "encountered an iTIP method that we do not support";
02324   return QString();
02325 }
02326 
02327 static QString invitationHeaderFreeBusy( const FreeBusy::Ptr &fb,
02328                                          ScheduleMessage::Ptr msg )
02329 {
02330   if ( !msg || !fb ) {
02331     return QString();
02332   }
02333 
02334   switch ( msg->method() ) {
02335   case iTIPPublish:
02336     return i18n( "This free/busy list has been published" );
02337   case iTIPRequest:
02338     return i18n( "The free/busy list has been requested" );
02339   case iTIPRefresh:
02340     return i18n( "This free/busy list was refreshed" );
02341   case iTIPCancel:
02342     return i18n( "This free/busy list was canceled" );
02343   case iTIPAdd:
02344     return i18n( "Addition to the free/busy list" );
02345   case iTIPReply:
02346     return i18n( "Reply to the free/busy list" );
02347   case iTIPCounter:
02348     return i18n( "Sender makes this counter proposal" );
02349   case iTIPDeclineCounter:
02350     return i18n( "Sender declines the counter proposal" );
02351   case iTIPNoMethod:
02352     return i18n( "Error: Free/Busy iTIP message with unknown method" );
02353   }
02354   kError() << "encountered an iTIP method that we do not support";
02355   return QString();
02356 }
02357 //@endcond
02358 
02359 static QString invitationAttendeeList( const Incidence::Ptr &incidence )
02360 {
02361   QString tmpStr;
02362   if ( !incidence ) {
02363     return tmpStr;
02364   }
02365   if ( incidence->type() == Incidence::TypeTodo ) {
02366     tmpStr += i18n( "Assignees" );
02367   } else {
02368     tmpStr += i18n( "Invitation List" );
02369   }
02370 
02371   int count=0;
02372   Attendee::List attendees = incidence->attendees();
02373   if ( !attendees.isEmpty() ) {
02374     QStringList comments;
02375     Attendee::List::ConstIterator it;
02376     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02377       Attendee::Ptr a = *it;
02378       if ( !iamAttendee( a ) ) {
02379         count++;
02380         if ( count == 1 ) {
02381           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02382         }
02383         tmpStr += "<tr>";
02384         tmpStr += "<td>";
02385         comments.clear();
02386         if ( attendeeIsOrganizer( incidence, a ) ) {
02387           comments << i18n( "organizer" );
02388         }
02389         if ( !a->delegator().isEmpty() ) {
02390           comments << i18n( " (delegated by %1)", a->delegator() );
02391         }
02392         if ( !a->delegate().isEmpty() ) {
02393           comments << i18n( " (delegated to %1)", a->delegate() );
02394         }
02395         tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02396         tmpStr += "</td>";
02397         tmpStr += "</tr>";
02398       }
02399     }
02400   }
02401   if ( count ) {
02402     tmpStr += "</table>";
02403   } else {
02404     tmpStr.clear();
02405   }
02406 
02407   return tmpStr;
02408 }
02409 
02410 static QString invitationRsvpList( const Incidence::Ptr &incidence, const Attendee::Ptr &sender )
02411 {
02412   QString tmpStr;
02413   if ( !incidence ) {
02414     return tmpStr;
02415   }
02416   if ( incidence->type() == Incidence::TypeTodo ) {
02417     tmpStr += i18n( "Assignees" );
02418   } else {
02419     tmpStr += i18n( "Invitation List" );
02420   }
02421 
02422   int count=0;
02423   Attendee::List attendees = incidence->attendees();
02424   if ( !attendees.isEmpty() ) {
02425     QStringList comments;
02426     Attendee::List::ConstIterator it;
02427     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02428       Attendee::Ptr a = *it;
02429       if ( !attendeeIsOrganizer( incidence, a ) ) {
02430         QString statusStr = Stringify::attendeeStatus( a->status () );
02431         if ( sender && ( a->email() == sender->email() ) ) {
02432           // use the attendee taken from the response incidence,
02433           // rather than the attendee from the calendar incidence.
02434           if ( a->status() != sender->status() ) {
02435             statusStr = i18n( "%1 (<i>unrecorded</i>)",
02436                               Stringify::attendeeStatus( sender->status() ) );
02437           }
02438           a = sender;
02439         }
02440         count++;
02441         if ( count == 1 ) {
02442           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02443         }
02444         tmpStr += "<tr>";
02445         tmpStr += "<td>";
02446         comments.clear();
02447         if ( iamAttendee( a ) ) {
02448           comments << i18n( "myself" );
02449         }
02450         if ( !a->delegator().isEmpty() ) {
02451           comments << i18n( " (delegated by %1)", a->delegator() );
02452         }
02453         if ( !a->delegate().isEmpty() ) {
02454           comments << i18n( " (delegated to %1)", a->delegate() );
02455         }
02456         tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02457         tmpStr += "</td>";
02458         tmpStr += "<td>" + statusStr + "</td>";
02459         tmpStr += "</tr>";
02460       }
02461     }
02462   }
02463   if ( count ) {
02464     tmpStr += "</table>";
02465   } else {
02466     tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>";
02467   }
02468 
02469   return tmpStr;
02470 }
02471 
02472 static QString invitationAttachments( InvitationFormatterHelper *helper,
02473                                       const Incidence::Ptr &incidence )
02474 {
02475   QString tmpStr;
02476   if ( !incidence ) {
02477     return tmpStr;
02478   }
02479 
02480   Attachment::List attachments = incidence->attachments();
02481   if ( !attachments.isEmpty() ) {
02482     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02483 
02484     Attachment::List::ConstIterator it;
02485     for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
02486       Attachment::Ptr a = *it;
02487       tmpStr += "<li>";
02488       // Attachment icon
02489       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02490       const QString iconStr = ( mimeType ?
02491                                 mimeType->iconName( a->uri() ) :
02492                                 QString( "application-octet-stream" ) );
02493       const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
02494       if ( !iconPath.isEmpty() ) {
02495         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02496       }
02497       tmpStr += helper->makeLink( "ATTACH:" + a->label().toUtf8().toBase64(), a->label() );
02498       tmpStr += "</li>";
02499     }
02500     tmpStr += "</ol>";
02501   }
02502 
02503   return tmpStr;
02504 }
02505 
02506 //@cond PRIVATE
02507 class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor
02508 {
02509   public:
02510     ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; }
02511     bool act( const IncidenceBase::Ptr &incidence,
02512               const Incidence::Ptr &existingIncidence,
02513               ScheduleMessage::Ptr msg, const QString &sender )
02514     {
02515       mExistingIncidence = existingIncidence;
02516       mMessage = msg;
02517       mSender = sender;
02518       return incidence->accept( *this, incidence );
02519     }
02520     QString result() const { return mResult; }
02521 
02522   protected:
02523     QString mResult;
02524     Incidence::Ptr mExistingIncidence;
02525     ScheduleMessage::Ptr mMessage;
02526     QString mSender;
02527 };
02528 
02529 class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor :
02530       public IncidenceFormatter::ScheduleMessageVisitor
02531 {
02532   protected:
02533     bool visit( Event::Ptr event )
02534     {
02535       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02536       return !mResult.isEmpty();
02537     }
02538     bool visit( Todo::Ptr todo )
02539     {
02540       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02541       return !mResult.isEmpty();
02542     }
02543     bool visit( Journal::Ptr journal )
02544     {
02545       mResult = invitationHeaderJournal( journal, mMessage );
02546       return !mResult.isEmpty();
02547     }
02548     bool visit( FreeBusy::Ptr fb )
02549     {
02550       mResult = invitationHeaderFreeBusy( fb, mMessage );
02551       return !mResult.isEmpty();
02552     }
02553 };
02554 
02555 class KCalUtils::IncidenceFormatter::InvitationBodyVisitor
02556   : public IncidenceFormatter::ScheduleMessageVisitor
02557 {
02558   public:
02559     InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
02560       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
02561 
02562   protected:
02563     bool visit( Event::Ptr event )
02564     {
02565       Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>();
02566       mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec );
02567       return !mResult.isEmpty();
02568     }
02569     bool visit( Todo::Ptr todo )
02570     {
02571       Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>();
02572       mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec );
02573       return !mResult.isEmpty();
02574     }
02575     bool visit( Journal::Ptr journal )
02576     {
02577       Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>();
02578       mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec );
02579       return !mResult.isEmpty();
02580     }
02581     bool visit( FreeBusy::Ptr fb )
02582     {
02583       mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec );
02584       return !mResult.isEmpty();
02585     }
02586 
02587   private:
02588     bool mNoHtmlMode;
02589     KDateTime::Spec mSpec;
02590 };
02591 //@endcond
02592 
02593 InvitationFormatterHelper::InvitationFormatterHelper()
02594   : d( 0 )
02595 {
02596 }
02597 
02598 InvitationFormatterHelper::~InvitationFormatterHelper()
02599 {
02600 }
02601 
02602 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
02603 {
02604   return id;
02605 }
02606 
02607 //@cond PRIVATE
02608 class IncidenceFormatter::IncidenceCompareVisitor : public Visitor
02609 {
02610   public:
02611     IncidenceCompareVisitor() {}
02612     bool act( const IncidenceBase::Ptr &incidence,
02613               const Incidence::Ptr &existingIncidence )
02614     {
02615       if ( !existingIncidence ) {
02616         return false;
02617       }
02618       Incidence::Ptr inc = incidence.staticCast<Incidence>();
02619       if ( !inc || !existingIncidence ||
02620            inc->revision() <= existingIncidence->revision() ) {
02621         return false;
02622       }
02623       mExistingIncidence = existingIncidence;
02624       return incidence->accept( *this, incidence );
02625     }
02626 
02627     QString result() const
02628     {
02629       if ( mChanges.isEmpty() ) {
02630         return QString();
02631       }
02632       QString html = "<div align=\"left\"><ul><li>";
02633       html += mChanges.join( "</li><li>" );
02634       html += "</li><ul></div>";
02635       return html;
02636     }
02637 
02638   protected:
02639     bool visit( Event::Ptr event )
02640     {
02641       compareEvents( event, mExistingIncidence.dynamicCast<Event>() );
02642       compareIncidences( event, mExistingIncidence );
02643       return !mChanges.isEmpty();
02644     }
02645     bool visit( Todo::Ptr todo )
02646     {
02647       compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() );
02648       compareIncidences( todo, mExistingIncidence );
02649       return !mChanges.isEmpty();
02650     }
02651     bool visit( Journal::Ptr journal )
02652     {
02653       compareIncidences( journal, mExistingIncidence );
02654       return !mChanges.isEmpty();
02655     }
02656     bool visit( FreeBusy::Ptr fb )
02657     {
02658       Q_UNUSED( fb );
02659       return !mChanges.isEmpty();
02660     }
02661 
02662   private:
02663     void compareEvents( const Event::Ptr &newEvent,
02664                         const Event::Ptr &oldEvent )
02665     {
02666       if ( !oldEvent || !newEvent ) {
02667         return;
02668       }
02669       if ( oldEvent->dtStart() != newEvent->dtStart() ||
02670            oldEvent->allDay() != newEvent->allDay() ) {
02671         mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
02672                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
02673       }
02674       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
02675            oldEvent->allDay() != newEvent->allDay() ) {
02676         mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
02677                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
02678       }
02679     }
02680 
02681     void compareTodos( const Todo::Ptr &newTodo,
02682                        const Todo::Ptr &oldTodo )
02683     {
02684       if ( !oldTodo || !newTodo ) {
02685         return;
02686       }
02687 
02688       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02689         mChanges += i18n( "The to-do has been completed" );
02690       }
02691       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02692         mChanges += i18n( "The to-do is no longer completed" );
02693       }
02694       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02695         const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
02696         const QString newPer = i18n( "%1%", newTodo->percentComplete() );
02697         mChanges += i18n( "The task completed percentage has changed from %1 to %2",
02698                           oldPer, newPer );
02699       }
02700 
02701       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02702         mChanges += i18n( "A to-do starting time has been added" );
02703       }
02704       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02705         mChanges += i18n( "The to-do starting time has been removed" );
02706       }
02707       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02708            oldTodo->dtStart() != newTodo->dtStart() ) {
02709         mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
02710                           dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
02711                           dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
02712       }
02713 
02714       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02715         mChanges += i18n( "A to-do due time has been added" );
02716       }
02717       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02718         mChanges += i18n( "The to-do due time has been removed" );
02719       }
02720       if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
02721            oldTodo->dtDue() != newTodo->dtDue() ) {
02722         mChanges += i18n( "The to-do due time has been changed from %1 to %2",
02723                           dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
02724                           dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
02725       }
02726     }
02727 
02728     void compareIncidences( const Incidence::Ptr &newInc,
02729                             const Incidence::Ptr &oldInc )
02730     {
02731       if ( !oldInc || !newInc ) {
02732         return;
02733       }
02734 
02735       if ( oldInc->summary() != newInc->summary() ) {
02736         mChanges += i18n( "The summary has been changed to: \"%1\"",
02737                           newInc->richSummary() );
02738       }
02739 
02740       if ( oldInc->location() != newInc->location() ) {
02741         mChanges += i18n( "The location has been changed to: \"%1\"",
02742                           newInc->richLocation() );
02743       }
02744 
02745       if ( oldInc->description() != newInc->description() ) {
02746         mChanges += i18n( "The description has been changed to: \"%1\"",
02747                           newInc->richDescription() );
02748       }
02749 
02750       Attendee::List oldAttendees = oldInc->attendees();
02751       Attendee::List newAttendees = newInc->attendees();
02752       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02753             it != newAttendees.constEnd(); ++it ) {
02754         Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() );
02755         if ( !oldAtt ) {
02756           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
02757         } else {
02758           if ( oldAtt->status() != (*it)->status() ) {
02759             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
02760                               (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) );
02761           }
02762         }
02763       }
02764 
02765       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02766             it != oldAttendees.constEnd(); ++it ) {
02767         if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
02768           Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() );
02769           if ( !newAtt ) {
02770             mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
02771           }
02772         }
02773       }
02774     }
02775 
02776   private:
02777     Incidence::Ptr mExistingIncidence;
02778     QStringList mChanges;
02779 };
02780 //@endcond
02781 
02782 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02783 {
02784   if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
02785     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02786                   arg( generateLinkURL( id ), text );
02787     return res;
02788   } else {
02789     // draw the attachment links in non-bold face
02790     QString res = QString( "<a href=\"%1\">%2</a>" ).
02791                   arg( generateLinkURL( id ), text );
02792     return res;
02793   }
02794 }
02795 
02796 // Check if the given incidence is likely one that we own instead one from
02797 // a shared calendar (Kolab-specific)
02798 static bool incidenceOwnedByMe( const Calendar::Ptr &calendar,
02799                                 const Incidence::Ptr &incidence )
02800 {
02801   Q_UNUSED( calendar );
02802   Q_UNUSED( incidence );
02803   return true;
02804 }
02805 
02806 // The open & close table cell tags for the invitation buttons
02807 static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
02808 static QString tdClose = "</td>";
02809 
02810 static QString responseButtons( const Incidence::Ptr &inc,
02811                                 bool rsvpReq, bool rsvpRec,
02812                                 InvitationFormatterHelper *helper )
02813 {
02814   QString html;
02815   if ( !helper ) {
02816     return html;
02817   }
02818 
02819   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02820     // Record only
02821     html += tdOpen;
02822     html += helper->makeLink( "record", i18n( "[Record]" ) );
02823     html += tdClose;
02824 
02825     // Move to trash
02826     html += tdOpen;
02827     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02828     html += tdClose;
02829 
02830   } else {
02831 
02832     // Accept
02833     html += tdOpen;
02834     html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
02835     html += tdClose;
02836 
02837     // Tentative
02838     html += tdOpen;
02839     html += helper->makeLink( "accept_conditionally",
02840                               i18nc( "Accept invitation conditionally", "Accept cond." ) );
02841     html += tdClose;
02842 
02843     // Counter proposal
02844     html += tdOpen;
02845     html += helper->makeLink( "counter",
02846                               i18nc( "invitation counter proposal", "Counter proposal" ) );
02847     html += tdClose;
02848 
02849     // Decline
02850     html += tdOpen;
02851     html += helper->makeLink( "decline",
02852                               i18nc( "decline invitation", "Decline" ) );
02853     html += tdClose;
02854   }
02855 
02856   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02857     // Delegate
02858     html += tdOpen;
02859     html += helper->makeLink( "delegate",
02860                               i18nc( "delegate inviation to another", "Delegate" ) );
02861     html += tdClose;
02862 
02863     // Forward
02864     html += tdOpen;
02865     html += helper->makeLink( "forward",
02866                               i18nc( "forward request to another", "Forward" ) );
02867     html += tdClose;
02868 
02869     // Check calendar
02870     if ( inc && inc->type() == Incidence::TypeEvent ) {
02871       html += tdOpen;
02872       html += helper->makeLink( "check_calendar",
02873                                 i18nc( "look for scheduling conflicts", "Check my calendar" ) );
02874       html += tdClose;
02875     }
02876   }
02877   return html;
02878 }
02879 
02880 static QString counterButtons( const Incidence::Ptr &incidence,
02881                                InvitationFormatterHelper *helper )
02882 {
02883   QString html;
02884   if ( !helper ) {
02885     return html;
02886   }
02887 
02888   // Accept proposal
02889   html += tdOpen;
02890   html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
02891   html += tdClose;
02892 
02893   // Decline proposal
02894   html += tdOpen;
02895   html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
02896   html += tdClose;
02897 
02898   // Check calendar
02899   if ( incidence && incidence->type() == Incidence::TypeEvent ) {
02900     html += tdOpen;
02901     html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
02902     html += tdClose;
02903   }
02904   return html;
02905 }
02906 
02907 Calendar::Ptr InvitationFormatterHelper::calendar() const
02908 {
02909   return Calendar::Ptr();
02910 }
02911 
02912 static QString formatICalInvitationHelper( QString invitation,
02913                                            const MemoryCalendar::Ptr &mCalendar,
02914                                            InvitationFormatterHelper *helper,
02915                                            bool noHtmlMode,
02916                                            KDateTime::Spec spec,
02917                                            const QString &sender,
02918                                            bool outlookCompareStyle )
02919 {
02920   if ( invitation.isEmpty() ) {
02921     return QString();
02922   }
02923 
02924   ICalFormat format;
02925   // parseScheduleMessage takes the tz from the calendar,
02926   // no need to set it manually here for the format!
02927   ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation );
02928 
02929   if( !msg ) {
02930     kDebug() << "Failed to parse the scheduling message";
02931     Q_ASSERT( format.exception() );
02932     kDebug() << Stringify::errorMessage( *format.exception() ); //krazy:exclude=kdebug
02933     return QString();
02934   }
02935 
02936   IncidenceBase::Ptr incBase = msg->event();
02937 
02938   incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
02939 
02940   // Determine if this incidence is in my calendar (and owned by me)
02941   Incidence::Ptr existingIncidence;
02942   if ( incBase && helper->calendar() ) {
02943     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02944 
02945     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02946       existingIncidence.clear();
02947     }
02948     if ( !existingIncidence ) {
02949       const Incidence::List list = helper->calendar()->incidences();
02950       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02951         if ( (*it)->schedulingID() == incBase->uid() &&
02952              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02953           existingIncidence = *it;
02954           break;
02955         }
02956       }
02957     }
02958   }
02959 
02960   Incidence::Ptr inc = incBase.staticCast<Incidence>();  // the incidence in the invitation email
02961 
02962   // First make the text of the message
02963   QString html;
02964   html += "<div align=\"center\" style=\"border:solid 1px;\">";
02965 
02966   IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
02967   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02968   if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02969     return QString();
02970   }
02971   html += htmlAddTag( "h3", headerVisitor.result() );
02972 
02973   if ( outlookCompareStyle ||
02974        msg->method() == iTIPDeclineCounter ) { //use Outlook style for decline
02975     // use the Outlook 2007 Comparison Style
02976     IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02977     bool bodyOk;
02978     if ( msg->method() == iTIPRequest || msg->method() == iTIPReply ||
02979          msg->method() == iTIPDeclineCounter ) {
02980       if ( inc && existingIncidence &&
02981            inc->revision() < existingIncidence->revision() ) {
02982         bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02983       } else {
02984         bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02985       }
02986     } else {
02987       bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender );
02988     }
02989     if ( bodyOk ) {
02990       html += bodyVisitor.result();
02991     } else {
02992       return QString();
02993     }
02994   } else {
02995     // use our "Classic" Comparison Style
02996     InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02997     if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) {
02998       return QString();
02999     }
03000     html += bodyVisitor.result();
03001 
03002     if ( msg->method() == iTIPRequest ) {
03003       IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
03004       if ( compareVisitor.act( inc, existingIncidence ) ) {
03005         html += "<p align=\"left\">";
03006         if ( senderIsOrganizer( inc, sender ) ) {
03007           html += i18n( "The following changes have been made by the organizer:" );
03008         } else if ( !sender.isEmpty() ) {
03009           html += i18n( "The following changes have been made by %1:", sender );
03010         } else {
03011           html += i18n( "The following changes have been made:" );
03012         }
03013         html += "</p>";
03014         html += compareVisitor.result();
03015       }
03016     }
03017     if ( msg->method() == iTIPReply ) {
03018       IncidenceCompareVisitor compareVisitor;
03019       if ( compareVisitor.act( inc, existingIncidence ) ) {
03020         html += "<p align=\"left\">";
03021         if ( !sender.isEmpty() ) {
03022           html += i18n( "The following changes have been made by %1:", sender );
03023         } else {
03024           html += i18n( "The following changes have been made by an attendee:" );
03025         }
03026         html += "</p>";
03027         html += compareVisitor.result();
03028       }
03029     }
03030   }
03031 
03032   // determine if I am the organizer for this invitation
03033   bool myInc = iamOrganizer( inc );
03034 
03035   // determine if the invitation response has already been recorded
03036   bool rsvpRec = false;
03037   Attendee::Ptr ea;
03038   if ( !myInc ) {
03039     Incidence::Ptr rsvpIncidence = existingIncidence;
03040     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
03041       rsvpIncidence = inc;
03042     }
03043     if ( rsvpIncidence ) {
03044       ea = findMyAttendee( rsvpIncidence );
03045     }
03046     if ( ea &&
03047          ( ea->status() == Attendee::Accepted ||
03048            ea->status() == Attendee::Declined ||
03049            ea->status() == Attendee::Tentative ) ) {
03050       rsvpRec = true;
03051     }
03052   }
03053 
03054   // determine invitation role
03055   QString role;
03056   bool isDelegated = false;
03057   Attendee::Ptr a = findMyAttendee( inc );
03058   if ( !a && inc ) {
03059     if ( !inc->attendees().isEmpty() ) {
03060       a = inc->attendees().first();
03061     }
03062   }
03063   if ( a ) {
03064     isDelegated = ( a->status() == Attendee::Delegated );
03065     role = Stringify::attendeeRole( a->role() );
03066   }
03067 
03068   // determine if RSVP needed, not-needed, or response already recorded
03069   bool rsvpReq = rsvpRequested( inc );
03070   if ( !myInc && a ) {
03071     html += "<br/>";
03072     html += "<i><u>";
03073     if ( rsvpRec && inc ) {
03074       if ( inc->revision() == 0 ) {
03075         html += i18n( "Your <b>%1</b> response has been recorded",
03076                       Stringify::attendeeStatus( ea->status() ) );
03077       } else {
03078         html += i18n( "Your status for this invitation is <b>%1</b>",
03079                       Stringify::attendeeStatus( ea->status() ) );
03080       }
03081       rsvpReq = false;
03082     } else if ( msg->method() == iTIPCancel ) {
03083       html += i18n( "This invitation was canceled" );
03084     } else if ( msg->method() == iTIPAdd ) {
03085       html += i18n( "This invitation was accepted" );
03086     } else if ( msg->method() == iTIPDeclineCounter ) {
03087       rsvpReq = true;
03088       html += rsvpRequestedStr( rsvpReq, role );
03089     } else {
03090       if ( !isDelegated ) {
03091         html += rsvpRequestedStr( rsvpReq, role );
03092       } else {
03093         html += i18n( "Awaiting delegation response" );
03094       }
03095     }
03096     html += "</u></i>";
03097   }
03098 
03099   // Print if the organizer gave you a preset status
03100   if ( !myInc ) {
03101     if ( inc && inc->revision() == 0 ) {
03102       QString statStr = myStatusStr( inc );
03103       if ( !statStr.isEmpty() ) {
03104         html += "<br/>";
03105         html += "<i>";
03106         html += statStr;
03107         html += "</i>";
03108       }
03109     }
03110   }
03111 
03112   // Add groupware links
03113 
03114   html += "<p>";
03115   html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
03116 
03117   switch ( msg->method() ) {
03118     case iTIPPublish:
03119     case iTIPRequest:
03120     case iTIPRefresh:
03121     case iTIPAdd:
03122     {
03123       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03124         if ( inc->type() == Incidence::TypeTodo ) {
03125           html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
03126         } else {
03127           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
03128         }
03129       }
03130 
03131       if ( !myInc && a ) {
03132         html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03133       }
03134       break;
03135     }
03136 
03137     case iTIPCancel:
03138       // Remove invitation
03139       if ( inc ) {
03140         html += tdOpen;
03141         if ( inc->type() == Incidence::TypeTodo ) {
03142           html += helper->makeLink( "cancel",
03143                                     i18n( "Remove invitation from my to-do list" ) );
03144         } else {
03145           html += helper->makeLink( "cancel",
03146                                     i18n( "Remove invitation from my calendar" ) );
03147         }
03148         html += tdClose;
03149       }
03150       break;
03151 
03152     case iTIPReply:
03153     {
03154       // Record invitation response
03155       Attendee::Ptr a;
03156       Attendee::Ptr ea;
03157       if ( inc ) {
03158         // First, determine if this reply is really a counter in disguise.
03159         if ( replyMeansCounter( inc ) ) {
03160           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03161           break;
03162         }
03163 
03164         // Next, maybe this is a declined reply that was delegated from me?
03165         // find first attendee who is delegated-from me
03166         // look a their PARTSTAT response, if the response is declined,
03167         // then we need to start over which means putting all the action
03168         // buttons and NOT putting on the [Record response..] button
03169         a = findDelegatedFromMyAttendee( inc );
03170         if ( a ) {
03171           if ( a->status() != Attendee::Accepted ||
03172                a->status() != Attendee::Tentative ) {
03173             html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03174             break;
03175           }
03176         }
03177 
03178         // Finally, simply allow a Record of the reply
03179         if ( !inc->attendees().isEmpty() ) {
03180           a = inc->attendees().first();
03181         }
03182         if ( a && helper->calendar() ) {
03183           ea = findAttendee( existingIncidence, a->email() );
03184         }
03185       }
03186       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03187         html += tdOpen;
03188         html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded",
03189                                        Stringify::attendeeStatus( ea->status() ) ) );
03190         html += tdClose;
03191       } else {
03192         if ( inc ) {
03193           if ( inc->type() == Incidence::TypeTodo ) {
03194             html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
03195           } else {
03196             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03197           }
03198         }
03199       }
03200       break;
03201     }
03202 
03203     case iTIPCounter:
03204       // Counter proposal
03205       html += counterButtons( inc, helper );
03206       break;
03207 
03208     case iTIPDeclineCounter:
03209       html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03210       break;
03211 
03212     case iTIPNoMethod:
03213       break;
03214   }
03215 
03216   // close the groupware table
03217   html += "</tr></table>";
03218 
03219   // Add the attendee list
03220   if ( myInc ) {
03221     html += invitationRsvpList( existingIncidence, a );
03222   } else {
03223     html += invitationAttendeeList( inc );
03224   }
03225 
03226   // close the top-level table
03227   html += "</div>";
03228 
03229   // Add the attachment list
03230   html += invitationAttachments( helper, inc );
03231 
03232   return html;
03233 }
03234 //@endcond
03235 
03236 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03237                                                   const MemoryCalendar::Ptr &calendar,
03238                                                   InvitationFormatterHelper *helper,
03239                                                   bool outlookCompareStyle )
03240 {
03241   return formatICalInvitationHelper( invitation, calendar, helper, false,
03242                                      KSystemTimeZones::local(), QString(),
03243                                      outlookCompareStyle );
03244 }
03245 
03246 QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
03247                                                         const MemoryCalendar::Ptr &calendar,
03248                                                         InvitationFormatterHelper *helper,
03249                                                         const QString &sender,
03250                                                         bool outlookCompareStyle )
03251 {
03252   return formatICalInvitationHelper( invitation, calendar, helper, true,
03253                                      KSystemTimeZones::local(), sender,
03254                                      outlookCompareStyle );
03255 }
03256 
03257 /*******************************************************************
03258  *  Helper functions for the Incidence tooltips
03259  *******************************************************************/
03260 
03261 //@cond PRIVATE
03262 class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor
03263 {
03264   public:
03265     ToolTipVisitor()
03266       : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
03267 
03268     bool act( const MemoryCalendar::Ptr &calendar,
03269               const IncidenceBase::Ptr &incidence,
03270               const QDate &date=QDate(), bool richText=true,
03271               KDateTime::Spec spec=KDateTime::Spec() )
03272     {
03273       mCalendar = calendar;
03274       mLocation.clear();
03275       mDate = date;
03276       mRichText = richText;
03277       mSpec = spec;
03278       mResult = "";
03279       return incidence ? incidence->accept( *this, incidence ) : false;
03280     }
03281 
03282     bool act( const QString &location, const IncidenceBase::Ptr &incidence,
03283               const QDate &date=QDate(), bool richText=true,
03284               KDateTime::Spec spec=KDateTime::Spec() )
03285     {
03286       mLocation = location;
03287       mDate = date;
03288       mRichText = richText;
03289       mSpec = spec;
03290       mResult = "";
03291       return incidence ? incidence->accept( *this, incidence ) : false;
03292     }
03293 
03294     QString result() const { return mResult; }
03295 
03296   protected:
03297     bool visit( Event::Ptr event );
03298     bool visit( Todo::Ptr todo );
03299     bool visit( Journal::Ptr journal );
03300     bool visit( FreeBusy::Ptr fb );
03301 
03302     QString dateRangeText( const Event::Ptr &event, const QDate &date );
03303     QString dateRangeText( const Todo::Ptr &todo, const QDate &date );
03304     QString dateRangeText( const Journal::Ptr &journal );
03305     QString dateRangeText( const FreeBusy::Ptr &fb );
03306 
03307     QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText );
03308 
03309   protected:
03310     MemoryCalendar::Ptr mCalendar;
03311     QString mLocation;
03312     QDate mDate;
03313     bool mRichText;
03314     KDateTime::Spec mSpec;
03315     QString mResult;
03316 };
03317 
03318 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event,
03319                                                            const QDate &date )
03320 {
03321   //FIXME: support mRichText==false
03322   QString ret;
03323   QString tmp;
03324 
03325   KDateTime startDt = event->dtStart();
03326   KDateTime endDt = event->dtEnd();
03327   if ( event->recurs() ) {
03328     if ( date.isValid() ) {
03329       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03330       int diffDays = startDt.daysTo( kdt );
03331       kdt = kdt.addSecs( -1 );
03332       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
03333       if ( event->hasEndDate() ) {
03334         endDt = endDt.addDays( diffDays );
03335         if ( startDt > endDt ) {
03336           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
03337           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03338         }
03339       }
03340     }
03341   }
03342 
03343   if ( event->isMultiDay() ) {
03344     tmp = dateToString( startDt, true, mSpec );
03345     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
03346 
03347     tmp = dateToString( endDt, true, mSpec );
03348     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
03349 
03350   } else {
03351 
03352     ret += "<br>" +
03353            i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
03354     if ( !event->allDay() ) {
03355       const QString dtStartTime = timeToString( startDt, true, mSpec );
03356       const QString dtEndTime = timeToString( endDt, true, mSpec );
03357       if ( dtStartTime == dtEndTime ) {
03358         // to prevent 'Time: 17:00 - 17:00'
03359         tmp = "<br>" +
03360               i18nc( "time for event", "<i>Time:</i> %1",
03361                      dtStartTime );
03362       } else {
03363         tmp = "<br>" +
03364               i18nc( "time range for event",
03365                      "<i>Time:</i> %1 - %2",
03366                      dtStartTime, dtEndTime );
03367       }
03368       ret += tmp;
03369     }
03370   }
03371   return ret.replace( ' ', "&nbsp;" );
03372 }
03373 
03374 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo,
03375                                                            const QDate &date )
03376 {
03377   //FIXME: support mRichText==false
03378   QString ret;
03379   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03380     KDateTime startDt = todo->dtStart();
03381     if ( todo->recurs() ) {
03382       if ( date.isValid() ) {
03383         startDt.setDate( date );
03384       }
03385     }
03386     ret += "<br>" +
03387            i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
03388   }
03389 
03390   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03391     KDateTime dueDt = todo->dtDue();
03392     if ( todo->recurs() ) {
03393       if ( date.isValid() ) {
03394         KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03395         kdt = kdt.addSecs( -1 );
03396         dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
03397       }
03398     }
03399     ret += "<br>" +
03400            i18n( "<i>Due:</i> %1",
03401                  dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
03402   }
03403 
03404   // Print priority and completed info here, for lack of a better place
03405 
03406   if ( todo->priority() > 0 ) {
03407     ret += "<br>";
03408     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03409     ret += QString::number( todo->priority() );
03410   }
03411 
03412   ret += "<br>";
03413   if ( todo->isCompleted() ) {
03414     ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + "&nbsp;";
03415     ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', "&nbsp;" );
03416   } else {
03417     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03418     ret += i18n( "%1%", todo->percentComplete() );
03419   }
03420 
03421   return ret.replace( ' ', "&nbsp;" );
03422 }
03423 
03424 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal )
03425 {
03426   //FIXME: support mRichText==false
03427   QString ret;
03428   if ( journal->dtStart().isValid() ) {
03429     ret += "<br>" +
03430            i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
03431   }
03432   return ret.replace( ' ', "&nbsp;" );
03433 }
03434 
03435 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb )
03436 {
03437   //FIXME: support mRichText==false
03438   QString ret;
03439   ret = "<br>" +
03440         i18n( "<i>Period start:</i> %1",
03441               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
03442   ret += "<br>" +
03443          i18n( "<i>Period start:</i> %1",
03444                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
03445   return ret.replace( ' ', "&nbsp;" );
03446 }
03447 
03448 bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event )
03449 {
03450   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03451   return !mResult.isEmpty();
03452 }
03453 
03454 bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo )
03455 {
03456   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03457   return !mResult.isEmpty();
03458 }
03459 
03460 bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal )
03461 {
03462   mResult = generateToolTip( journal, dateRangeText( journal ) );
03463   return !mResult.isEmpty();
03464 }
03465 
03466 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb )
03467 {
03468   //FIXME: support mRichText==false
03469   mResult = "<qt><b>" +
03470             i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) +
03471             "</b>";
03472   mResult += dateRangeText( fb );
03473   mResult += "</qt>";
03474   return !mResult.isEmpty();
03475 }
03476 
03477 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03478 {
03479   // Search for a new print name, if needed.
03480   const QString printName = searchName( email, name );
03481 
03482   // Get the icon corresponding to the attendee participation status.
03483   const QString iconPath = rsvpStatusIconPath( status );
03484 
03485   // Make the return string.
03486   QString personString;
03487   if ( !iconPath.isEmpty() ) {
03488     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03489   }
03490   if ( status != Attendee::None ) {
03491     personString += i18nc( "attendee name (attendee status)", "%1 (%2)",
03492                            printName.isEmpty() ? email : printName,
03493                            Stringify::attendeeStatus( status ) );
03494   } else {
03495     personString += i18n( "%1", printName.isEmpty() ? email : printName );
03496   }
03497   return personString;
03498 }
03499 
03500 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03501 {
03502   // Search for a new print name, if needed
03503   const QString printName = searchName( email, name );
03504 
03505   // Get the icon for organizer
03506   const QString iconPath =
03507     KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
03508 
03509   // Make the return string.
03510   QString personString;
03511   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03512   personString += ( printName.isEmpty() ? email : printName );
03513   return personString;
03514 }
03515 
03516 static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence,
03517                                               Attendee::Role role, bool showStatus )
03518 {
03519   int maxNumAtts = 8; // maximum number of people to print per attendee role
03520   const QString etc = i18nc( "elipsis", "..." );
03521 
03522   int i = 0;
03523   QString tmpStr;
03524   Attendee::List::ConstIterator it;
03525   Attendee::List attendees = incidence->attendees();
03526 
03527   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
03528     Attendee::Ptr a = *it;
03529     if ( a->role() != role ) {
03530       // skip not this role
03531       continue;
03532     }
03533     if ( attendeeIsOrganizer( incidence, a ) ) {
03534       // skip attendee that is also the organizer
03535       continue;
03536     }
03537     if ( i == maxNumAtts ) {
03538       tmpStr += "&nbsp;&nbsp;" + etc;
03539       break;
03540     }
03541     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
03542                                               showStatus ? a->status() : Attendee::None );
03543     if ( !a->delegator().isEmpty() ) {
03544       tmpStr += i18n( " (delegated by %1)", a->delegator() );
03545     }
03546     if ( !a->delegate().isEmpty() ) {
03547       tmpStr += i18n( " (delegated to %1)", a->delegate() );
03548     }
03549     tmpStr += "<br>";
03550     i++;
03551   }
03552   if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
03553     tmpStr.chop( 4 );
03554   }
03555   return tmpStr;
03556 }
03557 
03558 static QString tooltipFormatAttendees( const Calendar::Ptr &calendar,
03559                                        const Incidence::Ptr &incidence )
03560 {
03561   QString tmpStr, str;
03562 
03563   // Add organizer link
03564   int attendeeCount = incidence->attendees().count();
03565   if ( attendeeCount > 1 ||
03566        ( attendeeCount == 1 &&
03567          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03568     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03569     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer()->email(),
03570                                                        incidence->organizer()->name() );
03571   }
03572 
03573   // Show the attendee status if the incidence's organizer owns the resource calendar,
03574   // which means they are running the show and have all the up-to-date response info.
03575   const bool showStatus = attendeeCount > 0 && incOrganizerOwnsCalendar( calendar, incidence );
03576 
03577   // Add "chair"
03578   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
03579   if ( !str.isEmpty() ) {
03580     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
03581     tmpStr += str;
03582   }
03583 
03584   // Add required participants
03585   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
03586   if ( !str.isEmpty() ) {
03587     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
03588     tmpStr += str;
03589   }
03590 
03591   // Add optional participants
03592   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
03593   if ( !str.isEmpty() ) {
03594     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
03595     tmpStr += str;
03596   }
03597 
03598   // Add observers
03599   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
03600   if ( !str.isEmpty() ) {
03601     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
03602     tmpStr += str;
03603   }
03604 
03605   return tmpStr;
03606 }
03607 
03608 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence,
03609                                                              QString dtRangeText )
03610 {
03611   int maxDescLen = 120; // maximum description chars to print (before elipsis)
03612 
03613   //FIXME: support mRichText==false
03614   if ( !incidence ) {
03615     return QString();
03616   }
03617 
03618   QString tmp = "<qt>";
03619 
03620   // header
03621   tmp += "<b>" + incidence->richSummary() + "</b>";
03622   tmp += "<hr>";
03623 
03624   QString calStr = mLocation;
03625   if ( mCalendar ) {
03626     calStr = resourceString( mCalendar, incidence );
03627   }
03628   if ( !calStr.isEmpty() ) {
03629     tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03630     tmp += calStr;
03631   }
03632 
03633   tmp += dtRangeText;
03634 
03635   if ( !incidence->location().isEmpty() ) {
03636     tmp += "<br>";
03637     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03638     tmp += incidence->richLocation();
03639   }
03640 
03641   QString durStr = durationString( incidence );
03642   if ( !durStr.isEmpty() ) {
03643     tmp += "<br>";
03644     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03645     tmp += durStr;
03646   }
03647 
03648   if ( incidence->recurs() ) {
03649     tmp += "<br>";
03650     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03651     tmp += recurrenceString( incidence );
03652   }
03653 
03654   if ( !incidence->description().isEmpty() ) {
03655     QString desc( incidence->description() );
03656     if ( !incidence->descriptionIsRich() ) {
03657       if ( desc.length() > maxDescLen ) {
03658         desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." );
03659       }
03660       desc = Qt::escape( desc ).replace( '\n', "<br>" );
03661     } else {
03662       // TODO: truncate the description when it's rich text
03663     }
03664     tmp += "<hr>";
03665     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03666     tmp += desc;
03667     tmp += "<hr>";
03668   }
03669 
03670   int reminderCount = incidence->alarms().count();
03671   if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) {
03672     tmp += "<br>";
03673     tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + "&nbsp;";
03674     tmp += reminderStringList( incidence ).join( ", " );
03675   }
03676 
03677   tmp += "<br>";
03678   tmp += tooltipFormatAttendees( mCalendar, incidence );
03679 
03680   int categoryCount = incidence->categories().count();
03681   if ( categoryCount > 0 ) {
03682     tmp += "<br>";
03683     tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + "&nbsp;";
03684     tmp += incidence->categories().join( ", " );
03685   }
03686 
03687   tmp += "</qt>";
03688   return tmp;
03689 }
03690 //@endcond
03691 
03692 QString IncidenceFormatter::toolTipStr( const QString &sourceName,
03693                                         const IncidenceBase::Ptr &incidence,
03694                                         const QDate &date,
03695                                         bool richText,
03696                                         KDateTime::Spec spec )
03697 {
03698   ToolTipVisitor v;
03699   if ( v.act( sourceName, incidence, date, richText, spec ) ) {
03700     return v.result();
03701   } else {
03702     return QString();
03703   }
03704 }
03705 
03706 /*******************************************************************
03707  *  Helper functions for the Incidence tooltips
03708  *******************************************************************/
03709 
03710 //@cond PRIVATE
03711 static QString mailBodyIncidence( const Incidence::Ptr &incidence )
03712 {
03713   QString body;
03714   if ( !incidence->summary().isEmpty() ) {
03715     body += i18n( "Summary: %1\n", incidence->richSummary() );
03716   }
03717   if ( !incidence->organizer()->isEmpty() ) {
03718     body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() );
03719   }
03720   if ( !incidence->location().isEmpty() ) {
03721     body += i18n( "Location: %1\n", incidence->richLocation() );
03722   }
03723   return body;
03724 }
03725 //@endcond
03726 
03727 //@cond PRIVATE
03728 class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor
03729 {
03730   public:
03731     MailBodyVisitor()
03732       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
03733 
03734     bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() )
03735     {
03736       mSpec = spec;
03737       mResult = "";
03738       return incidence ? incidence->accept( *this, incidence ) : false;
03739     }
03740     QString result() const
03741     {
03742       return mResult;
03743     }
03744 
03745   protected:
03746     bool visit( Event::Ptr event );
03747     bool visit( Todo::Ptr todo );
03748     bool visit( Journal::Ptr journal );
03749     bool visit( FreeBusy::Ptr )
03750     {
03751       mResult = i18n( "This is a Free Busy Object" );
03752       return !mResult.isEmpty();
03753     }
03754   protected:
03755     KDateTime::Spec mSpec;
03756     QString mResult;
03757 };
03758 
03759 bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event )
03760 {
03761   QString recurrence[]= {
03762     i18nc( "no recurrence", "None" ),
03763     i18nc( "event recurs by minutes", "Minutely" ),
03764     i18nc( "event recurs by hours", "Hourly" ),
03765     i18nc( "event recurs by days", "Daily" ),
03766     i18nc( "event recurs by weeks", "Weekly" ),
03767     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
03768     i18nc( "event recurs same day each month", "Monthly Same Day" ),
03769     i18nc( "event recurs same month each year", "Yearly Same Month" ),
03770     i18nc( "event recurs same day each year", "Yearly Same Day" ),
03771     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
03772   };
03773 
03774   mResult = mailBodyIncidence( event );
03775   mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
03776   if ( !event->allDay() ) {
03777     mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
03778   }
03779   if ( event->dtStart() != event->dtEnd() ) {
03780     mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
03781   }
03782   if ( !event->allDay() ) {
03783     mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
03784   }
03785   if ( event->recurs() ) {
03786     Recurrence *recur = event->recurrence();
03787     // TODO: Merge these two to one of the form "Recurs every 3 days"
03788     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
03789     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
03790 
03791     if ( recur->duration() > 0 ) {
03792       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
03793       mResult += '\n';
03794     } else {
03795       if ( recur->duration() != -1 ) {
03796 // TODO_Recurrence: What to do with all-day
03797         QString endstr;
03798         if ( event->allDay() ) {
03799           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03800         } else {
03801           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
03802         }
03803         mResult += i18n( "Repeat until: %1\n", endstr );
03804       } else {
03805         mResult += i18n( "Repeats forever\n" );
03806       }
03807     }
03808   }
03809 
03810   QString details = event->richDescription();
03811   if ( !details.isEmpty() ) {
03812     mResult += i18n( "Details:\n%1\n", details );
03813   }
03814   return !mResult.isEmpty();
03815 }
03816 
03817 bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo )
03818 {
03819   mResult = mailBodyIncidence( todo );
03820 
03821   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03822     mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
03823     if ( !todo->allDay() ) {
03824       mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
03825     }
03826   }
03827   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03828     mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
03829     if ( !todo->allDay() ) {
03830       mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
03831     }
03832   }
03833   QString details = todo->richDescription();
03834   if ( !details.isEmpty() ) {
03835     mResult += i18n( "Details:\n%1\n", details );
03836   }
03837   return !mResult.isEmpty();
03838 }
03839 
03840 bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal )
03841 {
03842   mResult = mailBodyIncidence( journal );
03843   mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
03844   if ( !journal->allDay() ) {
03845     mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
03846   }
03847   if ( !journal->description().isEmpty() ) {
03848     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
03849   }
03850   return !mResult.isEmpty();
03851 }
03852 //@endcond
03853 
03854 QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence,
03855                                          KDateTime::Spec spec )
03856 {
03857   if ( !incidence ) {
03858     return QString();
03859   }
03860 
03861   MailBodyVisitor v;
03862   if ( v.act( incidence, spec ) ) {
03863     return v.result();
03864   }
03865   return QString();
03866 }
03867 
03868 //@cond PRIVATE
03869 static QString recurEnd( const Incidence::Ptr &incidence )
03870 {
03871   QString endstr;
03872   if ( incidence->allDay() ) {
03873     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03874   } else {
03875     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03876   }
03877   return endstr;
03878 }
03879 //@endcond
03880 
03881 /************************************
03882  *  More static formatting functions
03883  ************************************/
03884 
03885 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence )
03886 {
03887   if ( !incidence->recurs() ) {
03888     return i18n( "No recurrence" );
03889   }
03890   static QStringList dayList;
03891   if ( dayList.isEmpty() ) {
03892     dayList.append( i18n( "31st Last" ) );
03893     dayList.append( i18n( "30th Last" ) );
03894     dayList.append( i18n( "29th Last" ) );
03895     dayList.append( i18n( "28th Last" ) );
03896     dayList.append( i18n( "27th Last" ) );
03897     dayList.append( i18n( "26th Last" ) );
03898     dayList.append( i18n( "25th Last" ) );
03899     dayList.append( i18n( "24th Last" ) );
03900     dayList.append( i18n( "23rd Last" ) );
03901     dayList.append( i18n( "22nd Last" ) );
03902     dayList.append( i18n( "21st Last" ) );
03903     dayList.append( i18n( "20th Last" ) );
03904     dayList.append( i18n( "19th Last" ) );
03905     dayList.append( i18n( "18th Last" ) );
03906     dayList.append( i18n( "17th Last" ) );
03907     dayList.append( i18n( "16th Last" ) );
03908     dayList.append( i18n( "15th Last" ) );
03909     dayList.append( i18n( "14th Last" ) );
03910     dayList.append( i18n( "13th Last" ) );
03911     dayList.append( i18n( "12th Last" ) );
03912     dayList.append( i18n( "11th Last" ) );
03913     dayList.append( i18n( "10th Last" ) );
03914     dayList.append( i18n( "9th Last" ) );
03915     dayList.append( i18n( "8th Last" ) );
03916     dayList.append( i18n( "7th Last" ) );
03917     dayList.append( i18n( "6th Last" ) );
03918     dayList.append( i18n( "5th Last" ) );
03919     dayList.append( i18n( "4th Last" ) );
03920     dayList.append( i18n( "3rd Last" ) );
03921     dayList.append( i18n( "2nd Last" ) );
03922     dayList.append( i18nc( "last day of the month", "Last" ) );
03923     dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03924     dayList.append( i18n( "1st" ) );
03925     dayList.append( i18n( "2nd" ) );
03926     dayList.append( i18n( "3rd" ) );
03927     dayList.append( i18n( "4th" ) );
03928     dayList.append( i18n( "5th" ) );
03929     dayList.append( i18n( "6th" ) );
03930     dayList.append( i18n( "7th" ) );
03931     dayList.append( i18n( "8th" ) );
03932     dayList.append( i18n( "9th" ) );
03933     dayList.append( i18n( "10th" ) );
03934     dayList.append( i18n( "11th" ) );
03935     dayList.append( i18n( "12th" ) );
03936     dayList.append( i18n( "13th" ) );
03937     dayList.append( i18n( "14th" ) );
03938     dayList.append( i18n( "15th" ) );
03939     dayList.append( i18n( "16th" ) );
03940     dayList.append( i18n( "17th" ) );
03941     dayList.append( i18n( "18th" ) );
03942     dayList.append( i18n( "19th" ) );
03943     dayList.append( i18n( "20th" ) );
03944     dayList.append( i18n( "21st" ) );
03945     dayList.append( i18n( "22nd" ) );
03946     dayList.append( i18n( "23rd" ) );
03947     dayList.append( i18n( "24th" ) );
03948     dayList.append( i18n( "25th" ) );
03949     dayList.append( i18n( "26th" ) );
03950     dayList.append( i18n( "27th" ) );
03951     dayList.append( i18n( "28th" ) );
03952     dayList.append( i18n( "29th" ) );
03953     dayList.append( i18n( "30th" ) );
03954     dayList.append( i18n( "31st" ) );
03955   }
03956 
03957   const int weekStart = KGlobal::locale()->weekStartDay();
03958   QString dayNames;
03959   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03960 
03961   Recurrence *recur = incidence->recurrence();
03962 
03963   QString txt, recurStr;
03964   static QString noRecurrence = i18n( "No recurrence" );
03965   switch ( recur->recurrenceType() ) {
03966   case Recurrence::rNone:
03967     return noRecurrence;
03968 
03969   case Recurrence::rMinutely:
03970     if ( recur->duration() != -1 ) {
03971       recurStr = i18np( "Recurs every minute until %2",
03972                         "Recurs every %1 minutes until %2",
03973                         recur->frequency(), recurEnd( incidence ) );
03974       if ( recur->duration() >  0 ) {
03975         recurStr += i18nc( "number of occurrences",
03976                            " (<numid>%1</numid> occurrences)",
03977                            recur->duration() );
03978       }
03979     } else {
03980       recurStr = i18np( "Recurs every minute",
03981                         "Recurs every %1 minutes", recur->frequency() );
03982     }
03983     break;
03984 
03985   case Recurrence::rHourly:
03986     if ( recur->duration() != -1 ) {
03987       recurStr = i18np( "Recurs hourly until %2",
03988                         "Recurs every %1 hours until %2",
03989                         recur->frequency(), recurEnd( incidence ) );
03990       if ( recur->duration() >  0 ) {
03991         recurStr += i18nc( "number of occurrences",
03992                            " (<numid>%1</numid> occurrences)",
03993                            recur->duration() );
03994       }
03995     } else {
03996       recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
03997     }
03998     break;
03999 
04000   case Recurrence::rDaily:
04001     if ( recur->duration() != -1 ) {
04002       recurStr = i18np( "Recurs daily until %2",
04003                        "Recurs every %1 days until %2",
04004                        recur->frequency(), recurEnd( incidence ) );
04005       if ( recur->duration() >  0 ) {
04006         recurStr += i18nc( "number of occurrences",
04007                            " (<numid>%1</numid> occurrences)",
04008                            recur->duration() );
04009       }
04010     } else {
04011       recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
04012     }
04013     break;
04014 
04015   case Recurrence::rWeekly:
04016   {
04017     bool addSpace = false;
04018     for ( int i = 0; i < 7; ++i ) {
04019       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04020         if ( addSpace ) {
04021           dayNames.append( i18nc( "separator for list of days", ", " ) );
04022         }
04023         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
04024                                               KCalendarSystem::ShortDayName ) );
04025         addSpace = true;
04026       }
04027     }
04028     if ( dayNames.isEmpty() ) {
04029       dayNames = i18nc( "Recurs weekly on no days", "no days" );
04030     }
04031     if ( recur->duration() != -1 ) {
04032       recurStr = i18ncp( "Recurs weekly on [list of days] until end-date",
04033                          "Recurs weekly on %2 until %3",
04034                          "Recurs every <numid>%1</numid> weeks on %2 until %3",
04035                          recur->frequency(), dayNames, recurEnd( incidence ) );
04036       if ( recur->duration() >  0 ) {
04037         recurStr += i18nc( "number of occurrences",
04038                            " (<numid>%1</numid> occurrences)",
04039                            recur->duration() );
04040       }
04041     } else {
04042       recurStr = i18ncp( "Recurs weekly on [list of days]",
04043                          "Recurs weekly on %2",
04044                          "Recurs every <numid>%1</numid> weeks on %2",
04045                          recur->frequency(), dayNames );
04046     }
04047     break;
04048   }
04049   case Recurrence::rMonthlyPos:
04050   {
04051     if ( !recur->monthPositions().isEmpty() ) {
04052       RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04053       if ( recur->duration() != -1 ) {
04054         recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
04055                            " weekdayname until end-date",
04056                            "Recurs every month on the %2 %3 until %4",
04057                            "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
04058                            recur->frequency(),
04059                            dayList[rule.pos() + 31],
04060                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04061                            recurEnd( incidence ) );
04062         if ( recur->duration() >  0 ) {
04063           recurStr += i18nc( "number of occurrences",
04064                              " (<numid>%1</numid> occurrences)",
04065                              recur->duration() );
04066         }
04067       } else {
04068         recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
04069                            "Recurs every month on the %2 %3",
04070                            "Recurs every %1 months on the %2 %3",
04071                            recur->frequency(),
04072                            dayList[rule.pos() + 31],
04073                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
04074       }
04075     }
04076     break;
04077   }
04078   case Recurrence::rMonthlyDay:
04079   {
04080     if ( !recur->monthDays().isEmpty() ) {
04081       int days = recur->monthDays()[0];
04082       if ( recur->duration() != -1 ) {
04083         recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
04084                            "Recurs monthly on the %2 day until %3",
04085                            "Recurs every %1 months on the %2 day until %3",
04086                            recur->frequency(),
04087                            dayList[days + 31],
04088                            recurEnd( incidence ) );
04089         if ( recur->duration() >  0 ) {
04090           recurStr += i18nc( "number of occurrences",
04091                              " (<numid>%1</numid> occurrences)",
04092                              recur->duration() );
04093         }
04094       } else {
04095         recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day",
04096                            "Recurs monthly on the %2 day",
04097                            "Recurs every <numid>%1</numid> month on the %2 day",
04098                            recur->frequency(),
04099                            dayList[days + 31] );
04100       }
04101     }
04102     break;
04103   }
04104   case Recurrence::rYearlyMonth:
04105   {
04106     if ( recur->duration() != -1 ) {
04107       if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04108         recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
04109                            " until end-date",
04110                            "Recurs yearly on %2 %3 until %4",
04111                            "Recurs every %1 years on %2 %3 until %4",
04112                            recur->frequency(),
04113                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04114                            dayList[ recur->yearDates()[0] + 31 ],
04115                            recurEnd( incidence ) );
04116         if ( recur->duration() >  0 ) {
04117           recurStr += i18nc( "number of occurrences",
04118                              " (<numid>%1</numid> occurrences)",
04119                              recur->duration() );
04120         }
04121       }
04122     } else {
04123       if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04124         recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
04125                            "Recurs yearly on %2 %3",
04126                            "Recurs every %1 years on %2 %3",
04127                            recur->frequency(),
04128                            calSys->monthName( recur->yearMonths()[0],
04129                                               recur->startDate().year() ),
04130                            dayList[ recur->yearDates()[0] + 31 ] );
04131       } else {
04132         if (!recur->yearMonths().isEmpty() ) {
04133           recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04134                             "Recurs yearly on %1 %2",
04135                             calSys->monthName( recur->yearMonths()[0],
04136                                                recur->startDate().year() ),
04137                             dayList[ recur->startDate().day() + 31 ] );
04138         } else {
04139           recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04140                             "Recurs yearly on %1 %2",
04141                             calSys->monthName( recur->startDate().month(),
04142                                                recur->startDate().year() ),
04143                             dayList[ recur->startDate().day() + 31 ] );
04144         }
04145       }
04146     }
04147     break;
04148   }
04149   case Recurrence::rYearlyDay:
04150     if ( !recur->yearDays().isEmpty() ) {
04151       if ( recur->duration() != -1 ) {
04152         recurStr = i18ncp( "Recurs every N years on day N until end-date",
04153                            "Recurs every year on day <numid>%2</numid> until %3",
04154                            "Recurs every <numid>%1</numid> years"
04155                            " on day <numid>%2</numid> until %3",
04156                            recur->frequency(),
04157                            recur->yearDays()[0],
04158                            recurEnd( incidence ) );
04159         if ( recur->duration() >  0 ) {
04160           recurStr += i18nc( "number of occurrences",
04161                              " (<numid>%1</numid> occurrences)",
04162                              recur->duration() );
04163         }
04164       } else {
04165         recurStr = i18ncp( "Recurs every N YEAR[S] on day N",
04166                            "Recurs every year on day <numid>%2</numid>",
04167                            "Recurs every <numid>%1</numid> years"
04168                            " on day <numid>%2</numid>",
04169                            recur->frequency(), recur->yearDays()[0] );
04170       }
04171     }
04172     break;
04173   case Recurrence::rYearlyPos:
04174   {
04175     if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
04176       RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04177       if ( recur->duration() != -1 ) {
04178         recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04179                            "of monthname until end-date",
04180                            "Every year on the %2 %3 of %4 until %5",
04181                            "Every <numid>%1</numid> years on the %2 %3 of %4"
04182                            " until %5",
04183                            recur->frequency(),
04184                            dayList[rule.pos() + 31],
04185                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04186                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04187                            recurEnd( incidence ) );
04188         if ( recur->duration() >  0 ) {
04189           recurStr += i18nc( "number of occurrences",
04190                              " (<numid>%1</numid> occurrences)",
04191                              recur->duration() );
04192         }
04193       } else {
04194         recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04195                            "of monthname",
04196                            "Every year on the %2 %3 of %4",
04197                            "Every <numid>%1</numid> years on the %2 %3 of %4",
04198                            recur->frequency(),
04199                            dayList[rule.pos() + 31],
04200                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04201                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04202       }
04203     }
04204   }
04205   break;
04206   }
04207 
04208   if ( recurStr.isEmpty() ) {
04209     recurStr = i18n( "Incidence recurs" );
04210   }
04211 
04212   // Now, append the EXDATEs
04213   DateTimeList l = recur->exDateTimes();
04214   DateTimeList::ConstIterator il;
04215   QStringList exStr;
04216   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04217     switch ( recur->recurrenceType() ) {
04218     case Recurrence::rMinutely:
04219       exStr << i18n( "minute %1", (*il).time().minute() );
04220       break;
04221     case Recurrence::rHourly:
04222       exStr << KGlobal::locale()->formatTime( (*il).time() );
04223       break;
04224     case Recurrence::rDaily:
04225       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04226       break;
04227     case Recurrence::rWeekly:
04228       exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName );
04229       break;
04230     case Recurrence::rMonthlyPos:
04231       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04232       break;
04233     case Recurrence::rMonthlyDay:
04234       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04235       break;
04236     case Recurrence::rYearlyMonth:
04237       exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName );
04238       break;
04239     case Recurrence::rYearlyDay:
04240       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04241       break;
04242     case Recurrence::rYearlyPos:
04243       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04244       break;
04245     }
04246   }
04247 
04248   DateList d = recur->exDates();
04249   DateList::ConstIterator dl;
04250   for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04251     switch ( recur->recurrenceType() ) {
04252     case Recurrence::rDaily:
04253       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04254       break;
04255     case Recurrence::rWeekly:
04256       // exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName );
04257       // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
04258       if ( exStr.isEmpty() ) {
04259         exStr << i18np( "1 day", "%1 days", recur->exDates().count() );
04260       }
04261       break;
04262     case Recurrence::rMonthlyPos:
04263       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04264       break;
04265     case Recurrence::rMonthlyDay:
04266       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04267       break;
04268     case Recurrence::rYearlyMonth:
04269       exStr << calSys->monthName( (*dl), KCalendarSystem::LongName );
04270       break;
04271     case Recurrence::rYearlyDay:
04272       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04273       break;
04274     case Recurrence::rYearlyPos:
04275       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04276       break;
04277     }
04278   }
04279 
04280   if ( !exStr.isEmpty() ) {
04281     recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) );
04282   }
04283 
04284   return recurStr;
04285 }
04286 
04287 QString IncidenceFormatter::timeToString( const KDateTime &date,
04288                                           bool shortfmt,
04289                                           const KDateTime::Spec &spec )
04290 {
04291   if ( spec.isValid() ) {
04292 
04293     QString timeZone;
04294     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04295       timeZone = ' ' + spec.timeZone().name();
04296     }
04297 
04298     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
04299   } else {
04300     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04301   }
04302 }
04303 
04304 QString IncidenceFormatter::dateToString( const KDateTime &date,
04305                                           bool shortfmt,
04306                                           const KDateTime::Spec &spec )
04307 {
04308   if ( spec.isValid() ) {
04309 
04310     QString timeZone;
04311     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04312       timeZone = ' ' + spec.timeZone().name();
04313     }
04314 
04315     return
04316       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
04317                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
04318       timeZone;
04319   } else {
04320     return
04321       KGlobal::locale()->formatDate( date.date(),
04322                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04323   }
04324 }
04325 
04326 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
04327                                               bool allDay,
04328                                               bool shortfmt,
04329                                               const KDateTime::Spec &spec )
04330 {
04331   if ( allDay ) {
04332     return dateToString( date, shortfmt, spec );
04333   }
04334 
04335   if ( spec.isValid() ) {
04336     QString timeZone;
04337     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04338       timeZone = ' ' + spec.timeZone().name();
04339     }
04340 
04341     return KGlobal::locale()->formatDateTime(
04342       date.toTimeSpec( spec ).dateTime(),
04343       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
04344   } else {
04345     return  KGlobal::locale()->formatDateTime(
04346       date.dateTime(),
04347       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04348   }
04349 }
04350 
04351 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar,
04352                                             const Incidence::Ptr &incidence )
04353 {
04354   Q_UNUSED( calendar );
04355   Q_UNUSED( incidence );
04356   return QString();
04357 }
04358 
04359 static QString secs2Duration( int secs )
04360 {
04361   QString tmp;
04362   int days = secs / 86400;
04363   if ( days > 0 ) {
04364     tmp += i18np( "1 day", "%1 days", days );
04365     tmp += ' ';
04366     secs -= ( days * 86400 );
04367   }
04368   int hours = secs / 3600;
04369   if ( hours > 0 ) {
04370     tmp += i18np( "1 hour", "%1 hours", hours );
04371     tmp += ' ';
04372     secs -= ( hours * 3600 );
04373   }
04374   int mins = secs / 60;
04375   if ( mins > 0 ) {
04376     tmp += i18np( "1 minute", "%1 minutes", mins );
04377   }
04378   return tmp;
04379 }
04380 
04381 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence )
04382 {
04383   QString tmp;
04384   if ( incidence->type() == Incidence::TypeEvent ) {
04385     Event::Ptr event = incidence.staticCast<Event>();
04386     if ( event->hasEndDate() ) {
04387       if ( !event->allDay() ) {
04388         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04389       } else {
04390         tmp = i18np( "1 day", "%1 days",
04391                      event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04392       }
04393     } else {
04394       tmp = i18n( "forever" );
04395     }
04396   } else if ( incidence->type() == Incidence::TypeTodo ) {
04397     Todo::Ptr todo = incidence.staticCast<Todo>();
04398     if ( todo->hasDueDate() ) {
04399       if ( todo->hasStartDate() ) {
04400         if ( !todo->allDay() ) {
04401           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04402         } else {
04403           tmp = i18np( "1 day", "%1 days",
04404                        todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04405         }
04406       }
04407     }
04408   }
04409   return tmp;
04410 }
04411 
04412 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence,
04413                                                     bool shortfmt )
04414 {
04415   //TODO: implement shortfmt=false
04416   Q_UNUSED( shortfmt );
04417 
04418   QStringList reminderStringList;
04419 
04420   if ( incidence ) {
04421     Alarm::List alarms = incidence->alarms();
04422     Alarm::List::ConstIterator it;
04423     for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
04424       Alarm::Ptr alarm = *it;
04425       int offset = 0;
04426       QString remStr, atStr, offsetStr;
04427       if ( alarm->hasTime() ) {
04428         offset = 0;
04429         if ( alarm->time().isValid() ) {
04430           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04431         }
04432       } else if ( alarm->hasStartOffset() ) {
04433         offset = alarm->startOffset().asSeconds();
04434         if ( offset < 0 ) {
04435           offset = -offset;
04436           offsetStr = i18nc( "N days/hours/minutes before the start datetime",
04437                              "%1 before the start", secs2Duration( offset ) );
04438         } else if ( offset > 0 ) {
04439           offsetStr = i18nc( "N days/hours/minutes after the start datetime",
04440                              "%1 after the start", secs2Duration( offset ) );
04441         } else { //offset is 0
04442           if ( incidence->dtStart().isValid() ) {
04443             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04444           }
04445         }
04446       } else if ( alarm->hasEndOffset() ) {
04447         offset = alarm->endOffset().asSeconds();
04448         if ( offset < 0 ) {
04449           offset = -offset;
04450           if ( incidence->type() == Incidence::TypeTodo ) {
04451             offsetStr = i18nc( "N days/hours/minutes before the due datetime",
04452                                "%1 before the to-do is due", secs2Duration( offset ) );
04453           } else {
04454             offsetStr = i18nc( "N days/hours/minutes before the end datetime",
04455                                "%1 before the end", secs2Duration( offset ) );
04456           }
04457         } else if ( offset > 0 ) {
04458           if ( incidence->type() == Incidence::TypeTodo ) {
04459             offsetStr = i18nc( "N days/hours/minutes after the due datetime",
04460                                "%1 after the to-do is due", secs2Duration( offset ) );
04461           } else {
04462             offsetStr = i18nc( "N days/hours/minutes after the end datetime",
04463                                "%1 after the end", secs2Duration( offset ) );
04464           }
04465         } else { //offset is 0
04466           if ( incidence->type() == Incidence::TypeTodo ) {
04467             Todo::Ptr t = incidence.staticCast<Todo>();
04468             if ( t->dtDue().isValid() ) {
04469               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04470             }
04471           } else {
04472             Event::Ptr e = incidence.staticCast<Event>();
04473             if ( e->dtEnd().isValid() ) {
04474               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04475             }
04476           }
04477         }
04478       }
04479       if ( offset == 0 ) {
04480         if ( !atStr.isEmpty() ) {
04481           remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
04482         }
04483       } else {
04484         remStr = offsetStr;
04485       }
04486 
04487       if ( alarm->repeatCount() > 0 ) {
04488         QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
04489         QString intervalStr = i18nc( "interval is N days/hours/minutes",
04490                                      "interval is %1",
04491                                      secs2Duration( alarm->snoozeTime().asSeconds() ) );
04492         QString repeatStr = i18nc( "(repeat string, interval string)",
04493                                    "(%1, %2)", countStr, intervalStr );
04494         remStr = remStr + ' ' + repeatStr;
04495 
04496       }
04497       reminderStringList << remStr;
04498     }
04499   }
04500 
04501   return reminderStringList;
04502 }

KCalUtils Library

Skip menu "KCalUtils Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Members
  • File List
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.6.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