• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • 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            !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
01467         comments << string2HTML( incidence->description() );
01468       } else {
01469         if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
01470           comments << incidence->richDescription();
01471         } else {
01472           comments << incidence->description();
01473         }
01474         if ( noHtmlMode ) {
01475           comments[0] = cleanHtml( comments[0] );
01476         }
01477         comments[0] = htmlAddTag( "p", comments[0] );
01478       }
01479     }
01480     //else desc and comments are empty
01481   } else {
01482     // non-empty comments
01483     foreach ( const QString &c, incidence->comments() ) {
01484       if ( !c.isEmpty() ) {
01485         // kcalutils doesn't know about richtext comments, so we need to guess
01486         if ( !Qt::mightBeRichText( c ) ) {
01487           comments << string2HTML( c );
01488         } else {
01489           if ( noHtmlMode ) {
01490             comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) );
01491           } else {
01492             comments << c;
01493           }
01494         }
01495       }
01496     }
01497     if ( !incidence->description().isEmpty() ) {
01498       // use description too
01499       if ( !incidence->descriptionIsRich() &&
01500            !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
01501         descr = string2HTML( incidence->description() );
01502       } else {
01503         if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
01504           descr = incidence->richDescription();
01505         } else {
01506           descr = incidence->description();
01507         }
01508         if ( noHtmlMode ) {
01509           descr = cleanHtml( descr );
01510         }
01511         descr = htmlAddTag( "p", descr );
01512       }
01513     }
01514   }
01515 
01516   if( !descr.isEmpty() ) {
01517     html += "<p>";
01518     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01519     html += "<tr><td><center>" +
01520             htmlAddTag( "u", i18n( "Description:" ) ) +
01521             "</center></td></tr>";
01522     html += "<tr><td>" + descr + "</td></tr>";
01523     html += "</table>";
01524   }
01525 
01526   if ( !comments.isEmpty() ) {
01527     html += "<p>";
01528     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01529     html += "<tr><td><center>" +
01530             htmlAddTag( "u", i18n( "Comments:" ) ) +
01531             "</center></td></tr>";
01532     html += "<tr><td>";
01533     if ( comments.count() > 1 ) {
01534       html += "<ul>";
01535       for ( int i=0; i < comments.count(); ++i ) {
01536         html += "<li>" + comments[i] + "</li>";
01537       }
01538       html += "</ul>";
01539     } else {
01540       html += comments[0];
01541     }
01542     html += "</td></tr>";
01543     html += "</table>";
01544   }
01545   return html;
01546 }
01547 
01548 static QString invitationDetailsEvent( const Event::Ptr &event, bool noHtmlMode,
01549                                        KDateTime::Spec spec )
01550 {
01551   // Invitation details are formatted into an HTML table
01552   if ( !event ) {
01553     return QString();
01554   }
01555 
01556   QString html = htmlInvitationDetailsBegin();
01557   html += htmlInvitationDetailsTableBegin();
01558 
01559   // Invitation summary & location rows
01560   html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
01561   html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
01562 
01563   // If a 1 day event
01564   if ( event->dtStart().date() == event->dtEnd().date() ) {
01565     html += htmlRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
01566     if ( !event->allDay() ) {
01567       html += htmlRow( i18n( "Time:" ),
01568                        timeToString( event->dtStart(), true, spec ) +
01569                        " - " +
01570                        timeToString( event->dtEnd(), true, spec ) );
01571     }
01572   } else {
01573     html += htmlRow( i18nc( "starting date", "From:" ),
01574                      dateToString( event->dtStart(), false, spec ) );
01575     if ( !event->allDay() ) {
01576       html += htmlRow( i18nc( "starting time", "At:" ),
01577                        timeToString( event->dtStart(), true, spec ) );
01578     }
01579     if ( event->hasEndDate() ) {
01580       html += htmlRow( i18nc( "ending date", "To:" ),
01581                        dateToString( event->dtEnd(), false, spec ) );
01582       if ( !event->allDay() ) {
01583         html += htmlRow( i18nc( "ending time", "At:" ),
01584                          timeToString( event->dtEnd(), true, spec ) );
01585       }
01586     } else {
01587       html += htmlRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) );
01588     }
01589   }
01590 
01591   // Invitation Duration Row
01592   html += htmlRow( i18n( "Duration:" ), durationString( event ) );
01593 
01594   // Invitation Recurrence Row
01595   if ( event->recurs() ) {
01596     html += htmlRow( i18n( "Recurrence:" ), recurrenceString( event ) );
01597   }
01598 
01599   html += htmlInvitationDetailsTableEnd();
01600   html += invitationDetailsIncidence( event, noHtmlMode );
01601   html += htmlInvitationDetailsEnd();
01602 
01603   return html;
01604 }
01605 
01606 static QString invitationDetailsEvent( const Event::Ptr &event, const Event::Ptr &oldevent,
01607                                        const ScheduleMessage::Ptr message, bool noHtmlMode,
01608                                        KDateTime::Spec spec )
01609 {
01610   if ( !oldevent ) {
01611     return invitationDetailsEvent( event, noHtmlMode, spec );
01612   }
01613 
01614   QString html;
01615 
01616   // Print extra info typically dependent on the iTIP
01617   if ( message->method() == iTIPDeclineCounter ) {
01618     html += "<br>";
01619     html += invitationNote( QString(),
01620                             i18n( "Please respond again to the original proposal." ),
01621                             QString(), noteColor() );
01622   }
01623 
01624   html += htmlInvitationDetailsBegin();
01625   html += htmlInvitationDetailsTableBegin();
01626 
01627   html += htmlRow( i18n( "What:" ),
01628                    invitationSummary( event, noHtmlMode ),
01629                    invitationSummary( oldevent, noHtmlMode ) );
01630 
01631   html += htmlRow( i18n( "Where:" ),
01632                    invitationLocation( event, noHtmlMode ),
01633                    invitationLocation( oldevent, noHtmlMode ) );
01634 
01635   // If a 1 day event
01636   if ( event->dtStart().date() == event->dtEnd().date() ) {
01637     html += htmlRow( i18n( "Date:" ),
01638                      dateToString( event->dtStart(), false ),
01639                      dateToString( oldevent->dtStart(), false ) );
01640     QString spanStr, oldspanStr;
01641     if ( !event->allDay() ) {
01642       spanStr = timeToString( event->dtStart(), true ) +
01643                 " - " +
01644                 timeToString( event->dtEnd(), true );
01645     }
01646     if ( !oldevent->allDay() ) {
01647       oldspanStr = timeToString( oldevent->dtStart(), true ) +
01648                    " - " +
01649                    timeToString( oldevent->dtEnd(), true );
01650     }
01651     html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
01652   } else {
01653     html += htmlRow( i18nc( "Starting date of an event", "From:" ),
01654                      dateToString( event->dtStart(), false ),
01655                      dateToString( oldevent->dtStart(), false ) );
01656     QString startStr, oldstartStr;
01657     if ( !event->allDay() ) {
01658       startStr = timeToString( event->dtStart(), true );
01659     }
01660     if ( !oldevent->allDay() ) {
01661       oldstartStr = timeToString( oldevent->dtStart(), true );
01662     }
01663     html += htmlRow( i18nc( "Starting time of an event", "At:" ), startStr, oldstartStr );
01664     if ( event->hasEndDate() ) {
01665       html += htmlRow( i18nc( "Ending date of an event", "To:" ),
01666                        dateToString( event->dtEnd(), false ),
01667                        dateToString( oldevent->dtEnd(), false ) );
01668       QString endStr, oldendStr;
01669       if ( !event->allDay() ) {
01670         endStr = timeToString( event->dtEnd(), true );
01671       }
01672       if ( !oldevent->allDay() ) {
01673         oldendStr = timeToString( oldevent->dtEnd(), true );
01674       }
01675       html += htmlRow( i18nc( "Starting time of an event", "At:" ), endStr, oldendStr );
01676     } else {
01677       QString endStr = i18n( "no end date specified" );
01678       QString oldendStr;
01679       if ( !oldevent->hasEndDate() ) {
01680         oldendStr = i18n( "no end date specified" );
01681       } else {
01682         oldendStr = dateTimeToString( oldevent->dtEnd(), oldevent->allDay(), false );
01683       }
01684       html += htmlRow( i18nc( "Ending date of an event", "To:" ), endStr, oldendStr );
01685     }
01686   }
01687 
01688   html += htmlRow( i18n( "Duration:" ), durationString( event ), durationString( oldevent ) );
01689 
01690   QString recurStr, oldrecurStr;
01691   if ( event->recurs() ||  oldevent->recurs() ) {
01692     recurStr = recurrenceString( event );
01693     oldrecurStr = recurrenceString( oldevent );
01694   }
01695   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01696 
01697   html += htmlInvitationDetailsTableEnd();
01698   html += invitationDetailsIncidence( event, noHtmlMode );
01699   html += htmlInvitationDetailsEnd();
01700 
01701   return html;
01702 }
01703 
01704 static QString invitationDetailsTodo( const Todo::Ptr &todo, bool noHtmlMode,
01705                                       KDateTime::Spec spec )
01706 {
01707   // To-do details are formatted into an HTML table
01708   if ( !todo ) {
01709     return QString();
01710   }
01711 
01712   QString html = htmlInvitationDetailsBegin();
01713   html += htmlInvitationDetailsTableBegin();
01714 
01715   // Invitation summary & location rows
01716   html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
01717   html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
01718 
01719   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01720     html += htmlRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
01721     if ( !todo->allDay() ) {
01722       html += htmlRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) );
01723     }
01724   }
01725   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01726     html += htmlRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
01727     if ( !todo->allDay() ) {
01728       html += htmlRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) );
01729     }
01730   } else {
01731     html += htmlRow( i18n( "Due Date:" ), i18nc( "Due Date: None", "None" ) );
01732   }
01733 
01734   // Invitation Duration Row
01735   html += htmlRow( i18n( "Duration:" ), durationString( todo ) );
01736 
01737   // Completeness
01738   if ( todo->percentComplete() > 0 ) {
01739     html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) );
01740   }
01741 
01742   // Invitation Recurrence Row
01743   if ( todo->recurs() ) {
01744     html += htmlRow( i18n( "Recurrence:" ), recurrenceString( todo ) );
01745   }
01746 
01747   html += htmlInvitationDetailsTableEnd();
01748   html += invitationDetailsIncidence( todo, noHtmlMode );
01749   html += htmlInvitationDetailsEnd();
01750 
01751   return html;
01752 }
01753 
01754 static QString invitationDetailsTodo( const Todo::Ptr &todo, const Todo::Ptr &oldtodo,
01755                                       const ScheduleMessage::Ptr message, bool noHtmlMode,
01756                                       KDateTime::Spec spec )
01757 {
01758   if ( !oldtodo ) {
01759     return invitationDetailsTodo( todo, noHtmlMode, spec );
01760   }
01761 
01762   QString html;
01763 
01764   // Print extra info typically dependent on the iTIP
01765   if ( message->method() == iTIPDeclineCounter ) {
01766     html += "<br>";
01767     html += invitationNote( QString(),
01768                             i18n( "Please respond again to the original proposal." ),
01769                             QString(), noteColor() );
01770   }
01771 
01772   html += htmlInvitationDetailsBegin();
01773   html += htmlInvitationDetailsTableBegin();
01774 
01775   html += htmlRow( i18n( "What:" ),
01776                    invitationSummary( todo, noHtmlMode ),
01777                    invitationSummary( todo, noHtmlMode ) );
01778 
01779   html += htmlRow( i18n( "Where:" ),
01780                    invitationLocation( todo, noHtmlMode ),
01781                    invitationLocation( oldtodo, noHtmlMode ) );
01782 
01783   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01784     html += htmlRow( i18n( "Start Date:" ),
01785                      dateToString( todo->dtStart(), false ),
01786                      dateToString( oldtodo->dtStart(), false ) );
01787     QString startTimeStr, oldstartTimeStr;
01788     if ( !todo->allDay() || !oldtodo->allDay() ) {
01789       startTimeStr = todo->allDay() ?
01790                      i18n( "All day" ) : timeToString( todo->dtStart(), false );
01791       oldstartTimeStr = oldtodo->allDay() ?
01792                         i18n( "All day" ) : timeToString( oldtodo->dtStart(), false );
01793     }
01794     html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
01795   }
01796   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01797     html += htmlRow( i18n( "Due Date:" ),
01798                      dateToString( todo->dtDue(), false ),
01799                      dateToString( oldtodo->dtDue(), false ) );
01800     QString endTimeStr, oldendTimeStr;
01801     if ( !todo->allDay() || !oldtodo->allDay() ) {
01802       endTimeStr = todo->allDay() ?
01803                    i18n( "All day" ) : timeToString( todo->dtDue(), false );
01804       oldendTimeStr = oldtodo->allDay() ?
01805                       i18n( "All day" ) : timeToString( oldtodo->dtDue(), false );
01806     }
01807     html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
01808   } else {
01809     QString dueStr = i18nc( "Due Date: None", "None" );
01810     QString olddueStr;
01811     if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
01812       olddueStr = i18nc( "Due Date: None", "None" );
01813    } else {
01814       olddueStr = dateTimeToString( oldtodo->dtDue(), oldtodo->allDay(), false );
01815     }
01816     html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
01817   }
01818 
01819   html += htmlRow( i18n( "Duration:" ), durationString( todo ), durationString( oldtodo ) );
01820 
01821   QString completionStr, oldcompletionStr;
01822   if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) {
01823     completionStr = i18n( "%1%", todo->percentComplete() );
01824     oldcompletionStr = i18n( "%1%", oldtodo->percentComplete() );
01825   }
01826   html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr );
01827 
01828   QString recurStr, oldrecurStr;
01829   if ( todo->recurs() || oldtodo->recurs() ) {
01830     recurStr = recurrenceString( todo );
01831     oldrecurStr = recurrenceString( oldtodo );
01832   }
01833   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01834 
01835   html += htmlInvitationDetailsTableEnd();
01836   html += invitationDetailsIncidence( todo, noHtmlMode );
01837 
01838   html += htmlInvitationDetailsEnd();
01839 
01840   return html;
01841 }
01842 
01843 static QString invitationDetailsJournal( const Journal::Ptr &journal, bool noHtmlMode,
01844                                          KDateTime::Spec spec )
01845 {
01846   if ( !journal ) {
01847     return QString();
01848   }
01849 
01850   QString html = htmlInvitationDetailsBegin();
01851   html += htmlInvitationDetailsTableBegin();
01852 
01853   html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
01854   html += htmlRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
01855 
01856   html += htmlInvitationDetailsTableEnd();
01857   html += invitationDetailsIncidence( journal, noHtmlMode );
01858   html += htmlInvitationDetailsEnd();
01859 
01860   return html;
01861 }
01862 
01863 static QString invitationDetailsJournal( const Journal::Ptr &journal,
01864                                          const Journal::Ptr &oldjournal,
01865                                          bool noHtmlMode, KDateTime::Spec spec )
01866 {
01867   if ( !oldjournal ) {
01868     return invitationDetailsJournal( journal, noHtmlMode, spec );
01869   }
01870 
01871   QString html = htmlInvitationDetailsBegin();
01872   html += htmlInvitationDetailsTableBegin();
01873 
01874   html += htmlRow( i18n( "What:" ),
01875                    invitationSummary( journal, noHtmlMode ),
01876                    invitationSummary( oldjournal, noHtmlMode ) );
01877 
01878   html += htmlRow( i18n( "Date:" ),
01879                    dateToString( journal->dtStart(), false, spec ),
01880                    dateToString( oldjournal->dtStart(), false, spec ) );
01881 
01882   html += htmlInvitationDetailsTableEnd();
01883   html += invitationDetailsIncidence( journal, noHtmlMode );
01884   html += htmlInvitationDetailsEnd();
01885 
01886   return html;
01887 }
01888 
01889 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, bool noHtmlMode,
01890                                           KDateTime::Spec spec )
01891 {
01892   Q_UNUSED( noHtmlMode );
01893 
01894   if ( !fb ) {
01895     return QString();
01896   }
01897 
01898   QString html = htmlInvitationDetailsTableBegin();
01899 
01900   html += htmlRow( i18n( "Person:" ), fb->organizer()->fullName() );
01901   html += htmlRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
01902   html += htmlRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
01903 
01904   html += "<tr><td colspan=2><hr></td></tr>\n";
01905   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01906 
01907   Period::List periods = fb->busyPeriods();
01908   Period::List::iterator it;
01909   for ( it = periods.begin(); it != periods.end(); ++it ) {
01910     Period per = *it;
01911     if ( per.hasDuration() ) {
01912       int dur = per.duration().asSeconds();
01913       QString cont;
01914       if ( dur >= 3600 ) {
01915         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
01916         dur %= 3600;
01917       }
01918       if ( dur >= 60 ) {
01919         cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
01920         dur %= 60;
01921       }
01922       if ( dur > 0 ) {
01923         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
01924       }
01925       html += htmlRow( QString(),
01926                        i18nc( "startDate for duration", "%1 for %2",
01927                               KGlobal::locale()->formatDateTime(
01928                                 per.start().dateTime(), KLocale::LongDate ),
01929                               cont ) );
01930     } else {
01931       QString cont;
01932       if ( per.start().date() == per.end().date() ) {
01933         cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
01934                       KGlobal::locale()->formatDate( per.start().date() ),
01935                       KGlobal::locale()->formatTime( per.start().time() ),
01936                       KGlobal::locale()->formatTime( per.end().time() ) );
01937       } else {
01938         cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
01939                       KGlobal::locale()->formatDateTime(
01940                         per.start().dateTime(), KLocale::LongDate ),
01941                       KGlobal::locale()->formatDateTime(
01942                         per.end().dateTime(), KLocale::LongDate ) );
01943       }
01944 
01945       html += htmlRow( QString(), cont );
01946     }
01947   }
01948 
01949   html += htmlInvitationDetailsTableEnd();
01950   return html;
01951 }
01952 
01953 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, const FreeBusy::Ptr &oldfb,
01954                                           bool noHtmlMode, KDateTime::Spec spec )
01955 {
01956   Q_UNUSED( oldfb );
01957   return invitationDetailsFreeBusy( fb, noHtmlMode, spec );
01958 }
01959 
01960 static bool replyMeansCounter( const Incidence::Ptr &incidence )
01961 {
01962   Q_UNUSED( incidence );
01963   return false;
01978 }
01979 
01980 static QString invitationHeaderEvent( const Event::Ptr &event,
01981                                       const Incidence::Ptr &existingIncidence,
01982                                       ScheduleMessage::Ptr msg, const QString &sender )
01983 {
01984   if ( !msg || !event ) {
01985     return QString();
01986   }
01987 
01988   switch ( msg->method() ) {
01989   case iTIPPublish:
01990     return i18n( "This invitation has been published" );
01991   case iTIPRequest:
01992     if ( existingIncidence && event->revision() > 0 ) {
01993       QString orgStr = organizerName( event, sender );
01994       if ( senderIsOrganizer( event, sender ) ) {
01995         return i18n( "This invitation has been updated by the organizer %1", orgStr );
01996       } else {
01997         return i18n( "This invitation has been updated by %1 as a representative of %2",
01998                      sender, orgStr );
01999       }
02000     }
02001     if ( iamOrganizer( event ) ) {
02002       return i18n( "I created this invitation" );
02003     } else {
02004       QString orgStr = organizerName( event, sender );
02005       if ( senderIsOrganizer( event, sender ) ) {
02006         return i18n( "You received an invitation from %1", orgStr );
02007       } else {
02008         return i18n( "You received an invitation from %1 as a representative of %2",
02009                      sender, orgStr );
02010       }
02011     }
02012   case iTIPRefresh:
02013     return i18n( "This invitation was refreshed" );
02014   case iTIPCancel:
02015     if ( iamOrganizer( event ) ) {
02016       return i18n( "This invitation has been canceled" );
02017     } else {
02018       return i18n( "The organizer has revoked the invitation" );
02019     }
02020   case iTIPAdd:
02021     return i18n( "Addition to the invitation" );
02022   case iTIPReply:
02023   {
02024     if ( replyMeansCounter( event ) ) {
02025       return i18n( "%1 makes this counter proposal", firstAttendeeName( event, sender ) );
02026     }
02027 
02028     Attendee::List attendees = event->attendees();
02029     if( attendees.count() == 0 ) {
02030       kDebug() << "No attendees in the iCal reply!";
02031       return QString();
02032     }
02033     if ( attendees.count() != 1 ) {
02034       kDebug() << "Warning: attendeecount in the reply should be 1"
02035                << "but is" << attendees.count();
02036     }
02037     QString attendeeName = firstAttendeeName( event, sender );
02038 
02039     QString delegatorName, dummy;
02040     Attendee::Ptr attendee = *attendees.begin();
02041     KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
02042     if ( delegatorName.isEmpty() ) {
02043       delegatorName = attendee->delegator();
02044     }
02045 
02046     switch( attendee->status() ) {
02047     case Attendee::NeedsAction:
02048       return i18n( "%1 indicates this invitation still needs some action", attendeeName );
02049     case Attendee::Accepted:
02050       if ( event->revision() > 0 ) {
02051         if ( !sender.isEmpty() ) {
02052           return i18n( "This invitation has been updated by attendee %1", sender );
02053         } else {
02054           return i18n( "This invitation has been updated by an attendee" );
02055         }
02056       } else {
02057         if ( delegatorName.isEmpty() ) {
02058           return i18n( "%1 accepts this invitation", attendeeName );
02059         } else {
02060           return i18n( "%1 accepts this invitation on behalf of %2",
02061                        attendeeName, delegatorName );
02062         }
02063       }
02064     case Attendee::Tentative:
02065       if ( delegatorName.isEmpty() ) {
02066         return i18n( "%1 tentatively accepts this invitation", attendeeName );
02067       } else {
02068         return i18n( "%1 tentatively accepts this invitation on behalf of %2",
02069                      attendeeName, delegatorName );
02070       }
02071     case Attendee::Declined:
02072       if ( delegatorName.isEmpty() ) {
02073         return i18n( "%1 declines this invitation", attendeeName );
02074       } else {
02075         return i18n( "%1 declines this invitation on behalf of %2",
02076                      attendeeName, delegatorName );
02077       }
02078     case Attendee::Delegated:
02079     {
02080       QString delegate, dummy;
02081       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02082       if ( delegate.isEmpty() ) {
02083         delegate = attendee->delegate();
02084       }
02085       if ( !delegate.isEmpty() ) {
02086         return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
02087       } else {
02088         return i18n( "%1 has delegated this invitation", attendeeName );
02089       }
02090     }
02091     case Attendee::Completed:
02092       return i18n( "This invitation is now completed" );
02093     case Attendee::InProcess:
02094       return i18n( "%1 is still processing the invitation", attendeeName );
02095     case Attendee::None:
02096       return i18n( "Unknown response to this invitation" );
02097     }
02098     break;
02099   }
02100   case iTIPCounter:
02101     return i18n( "%1 makes this counter proposal",
02102                  firstAttendeeName( event, i18n( "Sender" ) ) );
02103 
02104   case iTIPDeclineCounter:
02105   {
02106     QString orgStr = organizerName( event, sender );
02107     if ( senderIsOrganizer( event, sender ) ) {
02108       return i18n( "%1 declines your counter proposal", orgStr );
02109     } else {
02110       return i18n( "%1 declines your counter proposal on behalf of %2", sender, orgStr );
02111     }
02112   }
02113 
02114   case iTIPNoMethod:
02115     return i18n( "Error: Event iTIP message with unknown method" );
02116   }
02117   kError() << "encountered an iTIP method that we do not support";
02118   return QString();
02119 }
02120 
02121 static QString invitationHeaderTodo( const Todo::Ptr &todo,
02122                                      const Incidence::Ptr &existingIncidence,
02123                                      ScheduleMessage::Ptr msg, const QString &sender )
02124 {
02125   if ( !msg || !todo ) {
02126     return QString();
02127   }
02128 
02129   switch ( msg->method() ) {
02130   case iTIPPublish:
02131     return i18n( "This to-do has been published" );
02132   case iTIPRequest:
02133     if ( existingIncidence && todo->revision() > 0 ) {
02134       QString orgStr = organizerName( todo, sender );
02135       if ( senderIsOrganizer( todo, sender ) ) {
02136         return i18n( "This to-do has been updated by the organizer %1", orgStr );
02137       } else {
02138         return i18n( "This to-do has been updated by %1 as a representative of %2",
02139                      sender, orgStr );
02140       }
02141     } else {
02142       if ( iamOrganizer( todo ) ) {
02143         return i18n( "I created this to-do" );
02144       } else {
02145         QString orgStr = organizerName( todo, sender );
02146         if ( senderIsOrganizer( todo, sender ) ) {
02147           return i18n( "You have been assigned this to-do by %1", orgStr );
02148         } else {
02149           return i18n( "You have been assigned this to-do by %1 as a representative of %2",
02150                        sender, orgStr );
02151         }
02152       }
02153     }
02154   case iTIPRefresh:
02155     return i18n( "This to-do was refreshed" );
02156   case iTIPCancel:
02157     if ( iamOrganizer( todo ) ) {
02158       return i18n( "This to-do was canceled" );
02159     } else {
02160       return i18n( "The organizer has revoked this to-do" );
02161     }
02162   case iTIPAdd:
02163     return i18n( "Addition to the to-do" );
02164   case iTIPReply:
02165   {
02166     if ( replyMeansCounter( todo ) ) {
02167       return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02168     }
02169 
02170     Attendee::List attendees = todo->attendees();
02171     if ( attendees.count() == 0 ) {
02172       kDebug() << "No attendees in the iCal reply!";
02173       return QString();
02174     }
02175     if ( attendees.count() != 1 ) {
02176       kDebug() << "Warning: attendeecount in the reply should be 1"
02177                << "but is" << attendees.count();
02178     }
02179     QString attendeeName = firstAttendeeName( todo, sender );
02180 
02181     QString delegatorName, dummy;
02182     Attendee::Ptr attendee = *attendees.begin();
02183     KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName );
02184     if ( delegatorName.isEmpty() ) {
02185       delegatorName = attendee->delegator();
02186     }
02187 
02188     switch( attendee->status() ) {
02189     case Attendee::NeedsAction:
02190       return i18n( "%1 indicates this to-do assignment still needs some action",
02191                    attendeeName );
02192     case Attendee::Accepted:
02193       if ( todo->revision() > 0 ) {
02194         if ( !sender.isEmpty() ) {
02195           if ( todo->isCompleted() ) {
02196             return i18n( "This to-do has been completed by assignee %1", sender );
02197           } else {
02198             return i18n( "This to-do has been updated by assignee %1", sender );
02199           }
02200         } else {
02201           if ( todo->isCompleted() ) {
02202             return i18n( "This to-do has been completed by an assignee" );
02203           } else {
02204             return i18n( "This to-do has been updated by an assignee" );
02205           }
02206         }
02207       } else {
02208         if ( delegatorName.isEmpty() ) {
02209           return i18n( "%1 accepts this to-do", attendeeName );
02210         } else {
02211           return i18n( "%1 accepts this to-do on behalf of %2",
02212                        attendeeName, delegatorName );
02213         }
02214       }
02215     case Attendee::Tentative:
02216       if ( delegatorName.isEmpty() ) {
02217         return i18n( "%1 tentatively accepts this to-do", attendeeName );
02218       } else {
02219         return i18n( "%1 tentatively accepts this to-do on behalf of %2",
02220                      attendeeName, delegatorName );
02221       }
02222     case Attendee::Declined:
02223       if ( delegatorName.isEmpty() ) {
02224         return i18n( "%1 declines this to-do", attendeeName );
02225       } else {
02226         return i18n( "%1 declines this to-do on behalf of %2",
02227                      attendeeName, delegatorName );
02228       }
02229     case Attendee::Delegated:
02230     {
02231       QString delegate, dummy;
02232       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02233       if ( delegate.isEmpty() ) {
02234         delegate = attendee->delegate();
02235       }
02236       if ( !delegate.isEmpty() ) {
02237         return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate );
02238       } else {
02239         return i18n( "%1 has delegated this to-do", attendeeName );
02240       }
02241     }
02242     case Attendee::Completed:
02243       return i18n( "The request for this to-do is now completed" );
02244     case Attendee::InProcess:
02245       return i18n( "%1 is still processing the to-do", attendeeName );
02246     case Attendee::None:
02247       return i18n( "Unknown response to this to-do" );
02248     }
02249     break;
02250   }
02251   case iTIPCounter:
02252     return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02253 
02254   case iTIPDeclineCounter:
02255   {
02256     QString orgStr = organizerName( todo, sender );
02257     if ( senderIsOrganizer( todo, sender ) ) {
02258       return i18n( "%1 declines the counter proposal", orgStr );
02259     } else {
02260       return i18n( "%1 declines the counter proposal on behalf of %2", sender, orgStr );
02261     }
02262   }
02263 
02264   case iTIPNoMethod:
02265     return i18n( "Error: To-do iTIP message with unknown method" );
02266   }
02267   kError() << "encountered an iTIP method that we do not support";
02268   return QString();
02269 }
02270 
02271 static QString invitationHeaderJournal( const Journal::Ptr &journal,
02272                                         ScheduleMessage::Ptr msg )
02273 {
02274   if ( !msg || !journal ) {
02275     return QString();
02276   }
02277 
02278   switch ( msg->method() ) {
02279   case iTIPPublish:
02280     return i18n( "This journal has been published" );
02281   case iTIPRequest:
02282     return i18n( "You have been assigned this journal" );
02283   case iTIPRefresh:
02284     return i18n( "This journal was refreshed" );
02285   case iTIPCancel:
02286     return i18n( "This journal was canceled" );
02287   case iTIPAdd:
02288     return i18n( "Addition to the journal" );
02289   case iTIPReply:
02290   {
02291     if ( replyMeansCounter( journal ) ) {
02292       return i18n( "Sender makes this counter proposal" );
02293     }
02294 
02295     Attendee::List attendees = journal->attendees();
02296     if ( attendees.count() == 0 ) {
02297       kDebug() << "No attendees in the iCal reply!";
02298       return QString();
02299     }
02300     if( attendees.count() != 1 ) {
02301       kDebug() << "Warning: attendeecount in the reply should be 1 "
02302                << "but is " << attendees.count();
02303     }
02304     Attendee::Ptr attendee = *attendees.begin();
02305 
02306     switch( attendee->status() ) {
02307     case Attendee::NeedsAction:
02308       return i18n( "Sender indicates this journal assignment still needs some action" );
02309     case Attendee::Accepted:
02310       return i18n( "Sender accepts this journal" );
02311     case Attendee::Tentative:
02312       return i18n( "Sender tentatively accepts this journal" );
02313     case Attendee::Declined:
02314       return i18n( "Sender declines this journal" );
02315     case Attendee::Delegated:
02316       return i18n( "Sender has delegated this request for the journal" );
02317     case Attendee::Completed:
02318       return i18n( "The request for this journal is now completed" );
02319     case Attendee::InProcess:
02320       return i18n( "Sender is still processing the invitation" );
02321     case Attendee::None:
02322       return i18n( "Unknown response to this journal" );
02323     }
02324     break;
02325   }
02326   case iTIPCounter:
02327     return i18n( "Sender makes this counter proposal" );
02328   case iTIPDeclineCounter:
02329     return i18n( "Sender declines the counter proposal" );
02330   case iTIPNoMethod:
02331     return i18n( "Error: Journal iTIP message with unknown method" );
02332   }
02333   kError() << "encountered an iTIP method that we do not support";
02334   return QString();
02335 }
02336 
02337 static QString invitationHeaderFreeBusy( const FreeBusy::Ptr &fb,
02338                                          ScheduleMessage::Ptr msg )
02339 {
02340   if ( !msg || !fb ) {
02341     return QString();
02342   }
02343 
02344   switch ( msg->method() ) {
02345   case iTIPPublish:
02346     return i18n( "This free/busy list has been published" );
02347   case iTIPRequest:
02348     return i18n( "The free/busy list has been requested" );
02349   case iTIPRefresh:
02350     return i18n( "This free/busy list was refreshed" );
02351   case iTIPCancel:
02352     return i18n( "This free/busy list was canceled" );
02353   case iTIPAdd:
02354     return i18n( "Addition to the free/busy list" );
02355   case iTIPReply:
02356     return i18n( "Reply to the free/busy list" );
02357   case iTIPCounter:
02358     return i18n( "Sender makes this counter proposal" );
02359   case iTIPDeclineCounter:
02360     return i18n( "Sender declines the counter proposal" );
02361   case iTIPNoMethod:
02362     return i18n( "Error: Free/Busy iTIP message with unknown method" );
02363   }
02364   kError() << "encountered an iTIP method that we do not support";
02365   return QString();
02366 }
02367 //@endcond
02368 
02369 static QString invitationAttendeeList( const Incidence::Ptr &incidence )
02370 {
02371   QString tmpStr;
02372   if ( !incidence ) {
02373     return tmpStr;
02374   }
02375   if ( incidence->type() == Incidence::TypeTodo ) {
02376     tmpStr += i18n( "Assignees" );
02377   } else {
02378     tmpStr += i18n( "Invitation List" );
02379   }
02380 
02381   int count=0;
02382   Attendee::List attendees = incidence->attendees();
02383   if ( !attendees.isEmpty() ) {
02384     QStringList comments;
02385     Attendee::List::ConstIterator it;
02386     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02387       Attendee::Ptr a = *it;
02388       if ( !iamAttendee( a ) ) {
02389         count++;
02390         if ( count == 1 ) {
02391           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02392         }
02393         tmpStr += "<tr>";
02394         tmpStr += "<td>";
02395         comments.clear();
02396         if ( attendeeIsOrganizer( incidence, a ) ) {
02397           comments << i18n( "organizer" );
02398         }
02399         if ( !a->delegator().isEmpty() ) {
02400           comments << i18n( " (delegated by %1)", a->delegator() );
02401         }
02402         if ( !a->delegate().isEmpty() ) {
02403           comments << i18n( " (delegated to %1)", a->delegate() );
02404         }
02405         tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02406         tmpStr += "</td>";
02407         tmpStr += "</tr>";
02408       }
02409     }
02410   }
02411   if ( count ) {
02412     tmpStr += "</table>";
02413   } else {
02414     tmpStr.clear();
02415   }
02416 
02417   return tmpStr;
02418 }
02419 
02420 static QString invitationRsvpList( const Incidence::Ptr &incidence, const Attendee::Ptr &sender )
02421 {
02422   QString tmpStr;
02423   if ( !incidence ) {
02424     return tmpStr;
02425   }
02426   if ( incidence->type() == Incidence::TypeTodo ) {
02427     tmpStr += i18n( "Assignees" );
02428   } else {
02429     tmpStr += i18n( "Invitation List" );
02430   }
02431 
02432   int count=0;
02433   Attendee::List attendees = incidence->attendees();
02434   if ( !attendees.isEmpty() ) {
02435     QStringList comments;
02436     Attendee::List::ConstIterator it;
02437     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02438       Attendee::Ptr a = *it;
02439       if ( !attendeeIsOrganizer( incidence, a ) ) {
02440         QString statusStr = Stringify::attendeeStatus( a->status () );
02441         if ( sender && ( a->email() == sender->email() ) ) {
02442           // use the attendee taken from the response incidence,
02443           // rather than the attendee from the calendar incidence.
02444           if ( a->status() != sender->status() ) {
02445             statusStr = i18n( "%1 (<i>unrecorded</i>)",
02446                               Stringify::attendeeStatus( sender->status() ) );
02447           }
02448           a = sender;
02449         }
02450         count++;
02451         if ( count == 1 ) {
02452           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02453         }
02454         tmpStr += "<tr>";
02455         tmpStr += "<td>";
02456         comments.clear();
02457         if ( iamAttendee( a ) ) {
02458           comments << i18n( "myself" );
02459         }
02460         if ( !a->delegator().isEmpty() ) {
02461           comments << i18n( " (delegated by %1)", a->delegator() );
02462         }
02463         if ( !a->delegate().isEmpty() ) {
02464           comments << i18n( " (delegated to %1)", a->delegate() );
02465         }
02466         tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02467         tmpStr += "</td>";
02468         tmpStr += "<td>" + statusStr + "</td>";
02469         tmpStr += "</tr>";
02470       }
02471     }
02472   }
02473   if ( count ) {
02474     tmpStr += "</table>";
02475   } else {
02476     tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>";
02477   }
02478 
02479   return tmpStr;
02480 }
02481 
02482 static QString invitationAttachments( InvitationFormatterHelper *helper,
02483                                       const Incidence::Ptr &incidence )
02484 {
02485   QString tmpStr;
02486   if ( !incidence ) {
02487     return tmpStr;
02488   }
02489 
02490   Attachment::List attachments = incidence->attachments();
02491   if ( !attachments.isEmpty() ) {
02492     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02493 
02494     Attachment::List::ConstIterator it;
02495     for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
02496       Attachment::Ptr a = *it;
02497       tmpStr += "<li>";
02498       // Attachment icon
02499       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02500       const QString iconStr = ( mimeType ?
02501                                 mimeType->iconName( a->uri() ) :
02502                                 QString( "application-octet-stream" ) );
02503       const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
02504       if ( !iconPath.isEmpty() ) {
02505         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02506       }
02507       tmpStr += helper->makeLink( "ATTACH:" + a->label().toUtf8().toBase64(), a->label() );
02508       tmpStr += "</li>";
02509     }
02510     tmpStr += "</ol>";
02511   }
02512 
02513   return tmpStr;
02514 }
02515 
02516 //@cond PRIVATE
02517 class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor
02518 {
02519   public:
02520     ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; }
02521     bool act( const IncidenceBase::Ptr &incidence,
02522               const Incidence::Ptr &existingIncidence,
02523               ScheduleMessage::Ptr msg, const QString &sender )
02524     {
02525       mExistingIncidence = existingIncidence;
02526       mMessage = msg;
02527       mSender = sender;
02528       return incidence->accept( *this, incidence );
02529     }
02530     QString result() const { return mResult; }
02531 
02532   protected:
02533     QString mResult;
02534     Incidence::Ptr mExistingIncidence;
02535     ScheduleMessage::Ptr mMessage;
02536     QString mSender;
02537 };
02538 
02539 class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor :
02540       public IncidenceFormatter::ScheduleMessageVisitor
02541 {
02542   protected:
02543     bool visit( Event::Ptr event )
02544     {
02545       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02546       return !mResult.isEmpty();
02547     }
02548     bool visit( Todo::Ptr todo )
02549     {
02550       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02551       return !mResult.isEmpty();
02552     }
02553     bool visit( Journal::Ptr journal )
02554     {
02555       mResult = invitationHeaderJournal( journal, mMessage );
02556       return !mResult.isEmpty();
02557     }
02558     bool visit( FreeBusy::Ptr fb )
02559     {
02560       mResult = invitationHeaderFreeBusy( fb, mMessage );
02561       return !mResult.isEmpty();
02562     }
02563 };
02564 
02565 class KCalUtils::IncidenceFormatter::InvitationBodyVisitor
02566   : public IncidenceFormatter::ScheduleMessageVisitor
02567 {
02568   public:
02569     InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
02570       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
02571 
02572   protected:
02573     bool visit( Event::Ptr event )
02574     {
02575       Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>();
02576       mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec );
02577       return !mResult.isEmpty();
02578     }
02579     bool visit( Todo::Ptr todo )
02580     {
02581       Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>();
02582       mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec );
02583       return !mResult.isEmpty();
02584     }
02585     bool visit( Journal::Ptr journal )
02586     {
02587       Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>();
02588       mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec );
02589       return !mResult.isEmpty();
02590     }
02591     bool visit( FreeBusy::Ptr fb )
02592     {
02593       mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec );
02594       return !mResult.isEmpty();
02595     }
02596 
02597   private:
02598     bool mNoHtmlMode;
02599     KDateTime::Spec mSpec;
02600 };
02601 //@endcond
02602 
02603 InvitationFormatterHelper::InvitationFormatterHelper()
02604   : d( 0 )
02605 {
02606 }
02607 
02608 InvitationFormatterHelper::~InvitationFormatterHelper()
02609 {
02610 }
02611 
02612 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
02613 {
02614   return id;
02615 }
02616 
02617 //@cond PRIVATE
02618 class IncidenceFormatter::IncidenceCompareVisitor : public Visitor
02619 {
02620   public:
02621     IncidenceCompareVisitor() {}
02622     bool act( const IncidenceBase::Ptr &incidence,
02623               const Incidence::Ptr &existingIncidence )
02624     {
02625       if ( !existingIncidence ) {
02626         return false;
02627       }
02628       Incidence::Ptr inc = incidence.staticCast<Incidence>();
02629       if ( !inc || !existingIncidence ||
02630            inc->revision() <= existingIncidence->revision() ) {
02631         return false;
02632       }
02633       mExistingIncidence = existingIncidence;
02634       return incidence->accept( *this, incidence );
02635     }
02636 
02637     QString result() const
02638     {
02639       if ( mChanges.isEmpty() ) {
02640         return QString();
02641       }
02642       QString html = "<div align=\"left\"><ul><li>";
02643       html += mChanges.join( "</li><li>" );
02644       html += "</li><ul></div>";
02645       return html;
02646     }
02647 
02648   protected:
02649     bool visit( Event::Ptr event )
02650     {
02651       compareEvents( event, mExistingIncidence.dynamicCast<Event>() );
02652       compareIncidences( event, mExistingIncidence );
02653       return !mChanges.isEmpty();
02654     }
02655     bool visit( Todo::Ptr todo )
02656     {
02657       compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() );
02658       compareIncidences( todo, mExistingIncidence );
02659       return !mChanges.isEmpty();
02660     }
02661     bool visit( Journal::Ptr journal )
02662     {
02663       compareIncidences( journal, mExistingIncidence );
02664       return !mChanges.isEmpty();
02665     }
02666     bool visit( FreeBusy::Ptr fb )
02667     {
02668       Q_UNUSED( fb );
02669       return !mChanges.isEmpty();
02670     }
02671 
02672   private:
02673     void compareEvents( const Event::Ptr &newEvent,
02674                         const Event::Ptr &oldEvent )
02675     {
02676       if ( !oldEvent || !newEvent ) {
02677         return;
02678       }
02679       if ( oldEvent->dtStart() != newEvent->dtStart() ||
02680            oldEvent->allDay() != newEvent->allDay() ) {
02681         mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
02682                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
02683       }
02684       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
02685            oldEvent->allDay() != newEvent->allDay() ) {
02686         mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
02687                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
02688       }
02689     }
02690 
02691     void compareTodos( const Todo::Ptr &newTodo,
02692                        const Todo::Ptr &oldTodo )
02693     {
02694       if ( !oldTodo || !newTodo ) {
02695         return;
02696       }
02697 
02698       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02699         mChanges += i18n( "The to-do has been completed" );
02700       }
02701       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02702         mChanges += i18n( "The to-do is no longer completed" );
02703       }
02704       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02705         const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
02706         const QString newPer = i18n( "%1%", newTodo->percentComplete() );
02707         mChanges += i18n( "The task completed percentage has changed from %1 to %2",
02708                           oldPer, newPer );
02709       }
02710 
02711       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02712         mChanges += i18n( "A to-do starting time has been added" );
02713       }
02714       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02715         mChanges += i18n( "The to-do starting time has been removed" );
02716       }
02717       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02718            oldTodo->dtStart() != newTodo->dtStart() ) {
02719         mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
02720                           dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
02721                           dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
02722       }
02723 
02724       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02725         mChanges += i18n( "A to-do due time has been added" );
02726       }
02727       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02728         mChanges += i18n( "The to-do due time has been removed" );
02729       }
02730       if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
02731            oldTodo->dtDue() != newTodo->dtDue() ) {
02732         mChanges += i18n( "The to-do due time has been changed from %1 to %2",
02733                           dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
02734                           dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
02735       }
02736     }
02737 
02738     void compareIncidences( const Incidence::Ptr &newInc,
02739                             const Incidence::Ptr &oldInc )
02740     {
02741       if ( !oldInc || !newInc ) {
02742         return;
02743       }
02744 
02745       if ( oldInc->summary() != newInc->summary() ) {
02746         mChanges += i18n( "The summary has been changed to: \"%1\"",
02747                           newInc->richSummary() );
02748       }
02749 
02750       if ( oldInc->location() != newInc->location() ) {
02751         mChanges += i18n( "The location has been changed to: \"%1\"",
02752                           newInc->richLocation() );
02753       }
02754 
02755       if ( oldInc->description() != newInc->description() ) {
02756         mChanges += i18n( "The description has been changed to: \"%1\"",
02757                           newInc->richDescription() );
02758       }
02759 
02760       Attendee::List oldAttendees = oldInc->attendees();
02761       Attendee::List newAttendees = newInc->attendees();
02762       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02763             it != newAttendees.constEnd(); ++it ) {
02764         Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() );
02765         if ( !oldAtt ) {
02766           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
02767         } else {
02768           if ( oldAtt->status() != (*it)->status() ) {
02769             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
02770                               (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) );
02771           }
02772         }
02773       }
02774 
02775       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02776             it != oldAttendees.constEnd(); ++it ) {
02777         if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
02778           Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() );
02779           if ( !newAtt ) {
02780             mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
02781           }
02782         }
02783       }
02784     }
02785 
02786   private:
02787     Incidence::Ptr mExistingIncidence;
02788     QStringList mChanges;
02789 };
02790 //@endcond
02791 
02792 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02793 {
02794   if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
02795     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02796                   arg( generateLinkURL( id ), text );
02797     return res;
02798   } else {
02799     // draw the attachment links in non-bold face
02800     QString res = QString( "<a href=\"%1\">%2</a>" ).
02801                   arg( generateLinkURL( id ), text );
02802     return res;
02803   }
02804 }
02805 
02806 // Check if the given incidence is likely one that we own instead one from
02807 // a shared calendar (Kolab-specific)
02808 static bool incidenceOwnedByMe( const Calendar::Ptr &calendar,
02809                                 const Incidence::Ptr &incidence )
02810 {
02811   Q_UNUSED( calendar );
02812   Q_UNUSED( incidence );
02813   return true;
02814 }
02815 
02816 // The open & close table cell tags for the invitation buttons
02817 static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
02818 static QString tdClose = "</td>";
02819 
02820 static QString responseButtons( const Incidence::Ptr &inc,
02821                                 bool rsvpReq, bool rsvpRec,
02822                                 InvitationFormatterHelper *helper )
02823 {
02824   QString html;
02825   if ( !helper ) {
02826     return html;
02827   }
02828 
02829   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02830     // Record only
02831     html += tdOpen;
02832     html += helper->makeLink( "record", i18n( "[Record]" ) );
02833     html += tdClose;
02834 
02835     // Move to trash
02836     html += tdOpen;
02837     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02838     html += tdClose;
02839 
02840   } else {
02841 
02842     // Accept
02843     html += tdOpen;
02844     html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
02845     html += tdClose;
02846 
02847     // Tentative
02848     html += tdOpen;
02849     html += helper->makeLink( "accept_conditionally",
02850                               i18nc( "Accept invitation conditionally", "Accept cond." ) );
02851     html += tdClose;
02852 
02853     // Counter proposal
02854     html += tdOpen;
02855     html += helper->makeLink( "counter",
02856                               i18nc( "invitation counter proposal", "Counter proposal" ) );
02857     html += tdClose;
02858 
02859     // Decline
02860     html += tdOpen;
02861     html += helper->makeLink( "decline",
02862                               i18nc( "decline invitation", "Decline" ) );
02863     html += tdClose;
02864   }
02865 
02866   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02867     // Delegate
02868     html += tdOpen;
02869     html += helper->makeLink( "delegate",
02870                               i18nc( "delegate inviation to another", "Delegate" ) );
02871     html += tdClose;
02872 
02873     // Forward
02874     html += tdOpen;
02875     html += helper->makeLink( "forward",
02876                               i18nc( "forward request to another", "Forward" ) );
02877     html += tdClose;
02878 
02879     // Check calendar
02880     if ( inc && inc->type() == Incidence::TypeEvent ) {
02881       html += tdOpen;
02882       html += helper->makeLink( "check_calendar",
02883                                 i18nc( "look for scheduling conflicts", "Check my calendar" ) );
02884       html += tdClose;
02885     }
02886   }
02887   return html;
02888 }
02889 
02890 static QString counterButtons( const Incidence::Ptr &incidence,
02891                                InvitationFormatterHelper *helper )
02892 {
02893   QString html;
02894   if ( !helper ) {
02895     return html;
02896   }
02897 
02898   // Accept proposal
02899   html += tdOpen;
02900   html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
02901   html += tdClose;
02902 
02903   // Decline proposal
02904   html += tdOpen;
02905   html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
02906   html += tdClose;
02907 
02908   // Check calendar
02909   if ( incidence && incidence->type() == Incidence::TypeEvent ) {
02910     html += tdOpen;
02911     html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
02912     html += tdClose;
02913   }
02914   return html;
02915 }
02916 
02917 Calendar::Ptr InvitationFormatterHelper::calendar() const
02918 {
02919   return Calendar::Ptr();
02920 }
02921 
02922 static QString formatICalInvitationHelper( QString invitation,
02923                                            const MemoryCalendar::Ptr &mCalendar,
02924                                            InvitationFormatterHelper *helper,
02925                                            bool noHtmlMode,
02926                                            KDateTime::Spec spec,
02927                                            const QString &sender,
02928                                            bool outlookCompareStyle )
02929 {
02930   if ( invitation.isEmpty() ) {
02931     return QString();
02932   }
02933 
02934   ICalFormat format;
02935   // parseScheduleMessage takes the tz from the calendar,
02936   // no need to set it manually here for the format!
02937   ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation );
02938 
02939   if( !msg ) {
02940     kDebug() << "Failed to parse the scheduling message";
02941     Q_ASSERT( format.exception() );
02942     kDebug() << Stringify::errorMessage( *format.exception() ); //krazy:exclude=kdebug
02943     return QString();
02944   }
02945 
02946   IncidenceBase::Ptr incBase = msg->event();
02947 
02948   incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
02949 
02950   // Determine if this incidence is in my calendar (and owned by me)
02951   Incidence::Ptr existingIncidence;
02952   if ( incBase && helper->calendar() ) {
02953     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02954 
02955     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02956       existingIncidence.clear();
02957     }
02958     if ( !existingIncidence ) {
02959       const Incidence::List list = helper->calendar()->incidences();
02960       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02961         if ( (*it)->schedulingID() == incBase->uid() &&
02962              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02963           existingIncidence = *it;
02964           break;
02965         }
02966       }
02967     }
02968   }
02969 
02970   Incidence::Ptr inc = incBase.staticCast<Incidence>();  // the incidence in the invitation email
02971 
02972   // First make the text of the message
02973   QString html;
02974   html += "<div align=\"center\" style=\"border:solid 1px;\">";
02975 
02976   IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
02977   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02978   if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02979     return QString();
02980   }
02981   html += htmlAddTag( "h3", headerVisitor.result() );
02982 
02983   if ( outlookCompareStyle ||
02984        msg->method() == iTIPDeclineCounter ) { //use Outlook style for decline
02985     // use the Outlook 2007 Comparison Style
02986     IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02987     bool bodyOk;
02988     if ( msg->method() == iTIPRequest || msg->method() == iTIPReply ||
02989          msg->method() == iTIPDeclineCounter ) {
02990       if ( inc && existingIncidence &&
02991            inc->revision() < existingIncidence->revision() ) {
02992         bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02993       } else {
02994         bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02995       }
02996     } else {
02997       bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender );
02998     }
02999     if ( bodyOk ) {
03000       html += bodyVisitor.result();
03001     } else {
03002       return QString();
03003     }
03004   } else {
03005     // use our "Classic" Comparison Style
03006     InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
03007     if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) {
03008       return QString();
03009     }
03010     html += bodyVisitor.result();
03011 
03012     if ( msg->method() == iTIPRequest ) {
03013       IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
03014       if ( compareVisitor.act( inc, existingIncidence ) ) {
03015         html += "<p align=\"left\">";
03016         if ( senderIsOrganizer( inc, sender ) ) {
03017           html += i18n( "The following changes have been made by the organizer:" );
03018         } else if ( !sender.isEmpty() ) {
03019           html += i18n( "The following changes have been made by %1:", sender );
03020         } else {
03021           html += i18n( "The following changes have been made:" );
03022         }
03023         html += "</p>";
03024         html += compareVisitor.result();
03025       }
03026     }
03027     if ( msg->method() == iTIPReply ) {
03028       IncidenceCompareVisitor compareVisitor;
03029       if ( compareVisitor.act( inc, existingIncidence ) ) {
03030         html += "<p align=\"left\">";
03031         if ( !sender.isEmpty() ) {
03032           html += i18n( "The following changes have been made by %1:", sender );
03033         } else {
03034           html += i18n( "The following changes have been made by an attendee:" );
03035         }
03036         html += "</p>";
03037         html += compareVisitor.result();
03038       }
03039     }
03040   }
03041 
03042   // determine if I am the organizer for this invitation
03043   bool myInc = iamOrganizer( inc );
03044 
03045   // determine if the invitation response has already been recorded
03046   bool rsvpRec = false;
03047   Attendee::Ptr ea;
03048   if ( !myInc ) {
03049     Incidence::Ptr rsvpIncidence = existingIncidence;
03050     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
03051       rsvpIncidence = inc;
03052     }
03053     if ( rsvpIncidence ) {
03054       ea = findMyAttendee( rsvpIncidence );
03055     }
03056     if ( ea &&
03057          ( ea->status() == Attendee::Accepted ||
03058            ea->status() == Attendee::Declined ||
03059            ea->status() == Attendee::Tentative ) ) {
03060       rsvpRec = true;
03061     }
03062   }
03063 
03064   // determine invitation role
03065   QString role;
03066   bool isDelegated = false;
03067   Attendee::Ptr a = findMyAttendee( inc );
03068   if ( !a && inc ) {
03069     if ( !inc->attendees().isEmpty() ) {
03070       a = inc->attendees().first();
03071     }
03072   }
03073   if ( a ) {
03074     isDelegated = ( a->status() == Attendee::Delegated );
03075     role = Stringify::attendeeRole( a->role() );
03076   }
03077 
03078   // determine if RSVP needed, not-needed, or response already recorded
03079   bool rsvpReq = rsvpRequested( inc );
03080   if ( !myInc && a ) {
03081     html += "<br/>";
03082     html += "<i><u>";
03083     if ( rsvpRec && inc ) {
03084       if ( inc->revision() == 0 ) {
03085         html += i18n( "Your <b>%1</b> response has been recorded",
03086                       Stringify::attendeeStatus( ea->status() ) );
03087       } else {
03088         html += i18n( "Your status for this invitation is <b>%1</b>",
03089                       Stringify::attendeeStatus( ea->status() ) );
03090       }
03091       rsvpReq = false;
03092     } else if ( msg->method() == iTIPCancel ) {
03093       html += i18n( "This invitation was canceled" );
03094     } else if ( msg->method() == iTIPAdd ) {
03095       html += i18n( "This invitation was accepted" );
03096     } else if ( msg->method() == iTIPDeclineCounter ) {
03097       rsvpReq = true;
03098       html += rsvpRequestedStr( rsvpReq, role );
03099     } else {
03100       if ( !isDelegated ) {
03101         html += rsvpRequestedStr( rsvpReq, role );
03102       } else {
03103         html += i18n( "Awaiting delegation response" );
03104       }
03105     }
03106     html += "</u></i>";
03107   }
03108 
03109   // Print if the organizer gave you a preset status
03110   if ( !myInc ) {
03111     if ( inc && inc->revision() == 0 ) {
03112       QString statStr = myStatusStr( inc );
03113       if ( !statStr.isEmpty() ) {
03114         html += "<br/>";
03115         html += "<i>";
03116         html += statStr;
03117         html += "</i>";
03118       }
03119     }
03120   }
03121 
03122   // Add groupware links
03123 
03124   html += "<p>";
03125   html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
03126 
03127   switch ( msg->method() ) {
03128     case iTIPPublish:
03129     case iTIPRequest:
03130     case iTIPRefresh:
03131     case iTIPAdd:
03132     {
03133       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03134         if ( inc->type() == Incidence::TypeTodo ) {
03135           html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
03136         } else {
03137           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
03138         }
03139       }
03140 
03141       if ( !myInc && a ) {
03142         html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03143       }
03144       break;
03145     }
03146 
03147     case iTIPCancel:
03148       // Remove invitation
03149       if ( inc ) {
03150         html += tdOpen;
03151         if ( inc->type() == Incidence::TypeTodo ) {
03152           html += helper->makeLink( "cancel",
03153                                     i18n( "Remove invitation from my to-do list" ) );
03154         } else {
03155           html += helper->makeLink( "cancel",
03156                                     i18n( "Remove invitation from my calendar" ) );
03157         }
03158         html += tdClose;
03159       }
03160       break;
03161 
03162     case iTIPReply:
03163     {
03164       // Record invitation response
03165       Attendee::Ptr a;
03166       Attendee::Ptr ea;
03167       if ( inc ) {
03168         // First, determine if this reply is really a counter in disguise.
03169         if ( replyMeansCounter( inc ) ) {
03170           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03171           break;
03172         }
03173 
03174         // Next, maybe this is a declined reply that was delegated from me?
03175         // find first attendee who is delegated-from me
03176         // look a their PARTSTAT response, if the response is declined,
03177         // then we need to start over which means putting all the action
03178         // buttons and NOT putting on the [Record response..] button
03179         a = findDelegatedFromMyAttendee( inc );
03180         if ( a ) {
03181           if ( a->status() != Attendee::Accepted ||
03182                a->status() != Attendee::Tentative ) {
03183             html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03184             break;
03185           }
03186         }
03187 
03188         // Finally, simply allow a Record of the reply
03189         if ( !inc->attendees().isEmpty() ) {
03190           a = inc->attendees().first();
03191         }
03192         if ( a && helper->calendar() ) {
03193           ea = findAttendee( existingIncidence, a->email() );
03194         }
03195       }
03196       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03197         html += tdOpen;
03198         html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded",
03199                                        Stringify::attendeeStatus( ea->status() ) ) );
03200         html += tdClose;
03201       } else {
03202         if ( inc ) {
03203           if ( inc->type() == Incidence::TypeTodo ) {
03204             html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
03205           } else {
03206             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03207           }
03208         }
03209       }
03210       break;
03211     }
03212 
03213     case iTIPCounter:
03214       // Counter proposal
03215       html += counterButtons( inc, helper );
03216       break;
03217 
03218     case iTIPDeclineCounter:
03219       html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03220       break;
03221 
03222     case iTIPNoMethod:
03223       break;
03224   }
03225 
03226   // close the groupware table
03227   html += "</tr></table>";
03228 
03229   // Add the attendee list
03230   if ( myInc ) {
03231     html += invitationRsvpList( existingIncidence, a );
03232   } else {
03233     html += invitationAttendeeList( inc );
03234   }
03235 
03236   // close the top-level table
03237   html += "</div>";
03238 
03239   // Add the attachment list
03240   html += invitationAttachments( helper, inc );
03241 
03242   return html;
03243 }
03244 //@endcond
03245 
03246 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03247                                                   const MemoryCalendar::Ptr &calendar,
03248                                                   InvitationFormatterHelper *helper,
03249                                                   bool outlookCompareStyle )
03250 {
03251   return formatICalInvitationHelper( invitation, calendar, helper, false,
03252                                      KSystemTimeZones::local(), QString(),
03253                                      outlookCompareStyle );
03254 }
03255 
03256 QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
03257                                                         const MemoryCalendar::Ptr &calendar,
03258                                                         InvitationFormatterHelper *helper,
03259                                                         const QString &sender,
03260                                                         bool outlookCompareStyle )
03261 {
03262   return formatICalInvitationHelper( invitation, calendar, helper, true,
03263                                      KSystemTimeZones::local(), sender,
03264                                      outlookCompareStyle );
03265 }
03266 
03267 /*******************************************************************
03268  *  Helper functions for the Incidence tooltips
03269  *******************************************************************/
03270 
03271 //@cond PRIVATE
03272 class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor
03273 {
03274   public:
03275     ToolTipVisitor()
03276       : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
03277 
03278     bool act( const MemoryCalendar::Ptr &calendar,
03279               const IncidenceBase::Ptr &incidence,
03280               const QDate &date=QDate(), bool richText=true,
03281               KDateTime::Spec spec=KDateTime::Spec() )
03282     {
03283       mCalendar = calendar;
03284       mLocation.clear();
03285       mDate = date;
03286       mRichText = richText;
03287       mSpec = spec;
03288       mResult = "";
03289       return incidence ? incidence->accept( *this, incidence ) : false;
03290     }
03291 
03292     bool act( const QString &location, const IncidenceBase::Ptr &incidence,
03293               const QDate &date=QDate(), bool richText=true,
03294               KDateTime::Spec spec=KDateTime::Spec() )
03295     {
03296       mLocation = location;
03297       mDate = date;
03298       mRichText = richText;
03299       mSpec = spec;
03300       mResult = "";
03301       return incidence ? incidence->accept( *this, incidence ) : false;
03302     }
03303 
03304     QString result() const { return mResult; }
03305 
03306   protected:
03307     bool visit( Event::Ptr event );
03308     bool visit( Todo::Ptr todo );
03309     bool visit( Journal::Ptr journal );
03310     bool visit( FreeBusy::Ptr fb );
03311 
03312     QString dateRangeText( const Event::Ptr &event, const QDate &date );
03313     QString dateRangeText( const Todo::Ptr &todo, const QDate &date );
03314     QString dateRangeText( const Journal::Ptr &journal );
03315     QString dateRangeText( const FreeBusy::Ptr &fb );
03316 
03317     QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText );
03318 
03319   protected:
03320     MemoryCalendar::Ptr mCalendar;
03321     QString mLocation;
03322     QDate mDate;
03323     bool mRichText;
03324     KDateTime::Spec mSpec;
03325     QString mResult;
03326 };
03327 
03328 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event,
03329                                                            const QDate &date )
03330 {
03331   //FIXME: support mRichText==false
03332   QString ret;
03333   QString tmp;
03334 
03335   KDateTime startDt = event->dtStart();
03336   KDateTime endDt = event->dtEnd();
03337   if ( event->recurs() ) {
03338     if ( date.isValid() ) {
03339       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03340       int diffDays = startDt.daysTo( kdt );
03341       kdt = kdt.addSecs( -1 );
03342       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
03343       if ( event->hasEndDate() ) {
03344         endDt = endDt.addDays( diffDays );
03345         if ( startDt > endDt ) {
03346           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
03347           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03348         }
03349       }
03350     }
03351   }
03352 
03353   if ( event->isMultiDay() ) {
03354     tmp = dateToString( startDt, true, mSpec );
03355     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
03356 
03357     tmp = dateToString( endDt, true, mSpec );
03358     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
03359 
03360   } else {
03361 
03362     ret += "<br>" +
03363            i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
03364     if ( !event->allDay() ) {
03365       const QString dtStartTime = timeToString( startDt, true, mSpec );
03366       const QString dtEndTime = timeToString( endDt, true, mSpec );
03367       if ( dtStartTime == dtEndTime ) {
03368         // to prevent 'Time: 17:00 - 17:00'
03369         tmp = "<br>" +
03370               i18nc( "time for event", "<i>Time:</i> %1",
03371                      dtStartTime );
03372       } else {
03373         tmp = "<br>" +
03374               i18nc( "time range for event",
03375                      "<i>Time:</i> %1 - %2",
03376                      dtStartTime, dtEndTime );
03377       }
03378       ret += tmp;
03379     }
03380   }
03381   return ret.replace( ' ', "&nbsp;" );
03382 }
03383 
03384 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo,
03385                                                            const QDate &date )
03386 {
03387   //FIXME: support mRichText==false
03388   QString ret;
03389   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03390     KDateTime startDt = todo->dtStart();
03391     if ( todo->recurs() ) {
03392       if ( date.isValid() ) {
03393         startDt.setDate( date );
03394       }
03395     }
03396     ret += "<br>" +
03397            i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
03398   }
03399 
03400   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03401     KDateTime dueDt = todo->dtDue();
03402     if ( todo->recurs() ) {
03403       if ( date.isValid() ) {
03404         KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03405         kdt = kdt.addSecs( -1 );
03406         dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
03407       }
03408     }
03409     ret += "<br>" +
03410            i18n( "<i>Due:</i> %1",
03411                  dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
03412   }
03413 
03414   // Print priority and completed info here, for lack of a better place
03415 
03416   if ( todo->priority() > 0 ) {
03417     ret += "<br>";
03418     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03419     ret += QString::number( todo->priority() );
03420   }
03421 
03422   ret += "<br>";
03423   if ( todo->isCompleted() ) {
03424     ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + "&nbsp;";
03425     ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', "&nbsp;" );
03426   } else {
03427     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03428     ret += i18n( "%1%", todo->percentComplete() );
03429   }
03430 
03431   return ret.replace( ' ', "&nbsp;" );
03432 }
03433 
03434 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal )
03435 {
03436   //FIXME: support mRichText==false
03437   QString ret;
03438   if ( journal->dtStart().isValid() ) {
03439     ret += "<br>" +
03440            i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
03441   }
03442   return ret.replace( ' ', "&nbsp;" );
03443 }
03444 
03445 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb )
03446 {
03447   //FIXME: support mRichText==false
03448   QString ret;
03449   ret = "<br>" +
03450         i18n( "<i>Period start:</i> %1",
03451               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
03452   ret += "<br>" +
03453          i18n( "<i>Period start:</i> %1",
03454                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
03455   return ret.replace( ' ', "&nbsp;" );
03456 }
03457 
03458 bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event )
03459 {
03460   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03461   return !mResult.isEmpty();
03462 }
03463 
03464 bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo )
03465 {
03466   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03467   return !mResult.isEmpty();
03468 }
03469 
03470 bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal )
03471 {
03472   mResult = generateToolTip( journal, dateRangeText( journal ) );
03473   return !mResult.isEmpty();
03474 }
03475 
03476 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb )
03477 {
03478   //FIXME: support mRichText==false
03479   mResult = "<qt><b>" +
03480             i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) +
03481             "</b>";
03482   mResult += dateRangeText( fb );
03483   mResult += "</qt>";
03484   return !mResult.isEmpty();
03485 }
03486 
03487 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03488 {
03489   // Search for a new print name, if needed.
03490   const QString printName = searchName( email, name );
03491 
03492   // Get the icon corresponding to the attendee participation status.
03493   const QString iconPath = rsvpStatusIconPath( status );
03494 
03495   // Make the return string.
03496   QString personString;
03497   if ( !iconPath.isEmpty() ) {
03498     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03499   }
03500   if ( status != Attendee::None ) {
03501     personString += i18nc( "attendee name (attendee status)", "%1 (%2)",
03502                            printName.isEmpty() ? email : printName,
03503                            Stringify::attendeeStatus( status ) );
03504   } else {
03505     personString += i18n( "%1", printName.isEmpty() ? email : printName );
03506   }
03507   return personString;
03508 }
03509 
03510 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03511 {
03512   // Search for a new print name, if needed
03513   const QString printName = searchName( email, name );
03514 
03515   // Get the icon for organizer
03516   const QString iconPath =
03517     KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
03518 
03519   // Make the return string.
03520   QString personString;
03521   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03522   personString += ( printName.isEmpty() ? email : printName );
03523   return personString;
03524 }
03525 
03526 static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence,
03527                                               Attendee::Role role, bool showStatus )
03528 {
03529   int maxNumAtts = 8; // maximum number of people to print per attendee role
03530   const QString etc = i18nc( "elipsis", "..." );
03531 
03532   int i = 0;
03533   QString tmpStr;
03534   Attendee::List::ConstIterator it;
03535   Attendee::List attendees = incidence->attendees();
03536 
03537   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
03538     Attendee::Ptr a = *it;
03539     if ( a->role() != role ) {
03540       // skip not this role
03541       continue;
03542     }
03543     if ( attendeeIsOrganizer( incidence, a ) ) {
03544       // skip attendee that is also the organizer
03545       continue;
03546     }
03547     if ( i == maxNumAtts ) {
03548       tmpStr += "&nbsp;&nbsp;" + etc;
03549       break;
03550     }
03551     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
03552                                               showStatus ? a->status() : Attendee::None );
03553     if ( !a->delegator().isEmpty() ) {
03554       tmpStr += i18n( " (delegated by %1)", a->delegator() );
03555     }
03556     if ( !a->delegate().isEmpty() ) {
03557       tmpStr += i18n( " (delegated to %1)", a->delegate() );
03558     }
03559     tmpStr += "<br>";
03560     i++;
03561   }
03562   if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
03563     tmpStr.chop( 4 );
03564   }
03565   return tmpStr;
03566 }
03567 
03568 static QString tooltipFormatAttendees( const Calendar::Ptr &calendar,
03569                                        const Incidence::Ptr &incidence )
03570 {
03571   QString tmpStr, str;
03572 
03573   // Add organizer link
03574   int attendeeCount = incidence->attendees().count();
03575   if ( attendeeCount > 1 ||
03576        ( attendeeCount == 1 &&
03577          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03578     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03579     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer()->email(),
03580                                                        incidence->organizer()->name() );
03581   }
03582 
03583   // Show the attendee status if the incidence's organizer owns the resource calendar,
03584   // which means they are running the show and have all the up-to-date response info.
03585   const bool showStatus = attendeeCount > 0 && incOrganizerOwnsCalendar( calendar, incidence );
03586 
03587   // Add "chair"
03588   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
03589   if ( !str.isEmpty() ) {
03590     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
03591     tmpStr += str;
03592   }
03593 
03594   // Add required participants
03595   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
03596   if ( !str.isEmpty() ) {
03597     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
03598     tmpStr += str;
03599   }
03600 
03601   // Add optional participants
03602   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
03603   if ( !str.isEmpty() ) {
03604     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
03605     tmpStr += str;
03606   }
03607 
03608   // Add observers
03609   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
03610   if ( !str.isEmpty() ) {
03611     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
03612     tmpStr += str;
03613   }
03614 
03615   return tmpStr;
03616 }
03617 
03618 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence,
03619                                                              QString dtRangeText )
03620 {
03621   int maxDescLen = 120; // maximum description chars to print (before elipsis)
03622 
03623   //FIXME: support mRichText==false
03624   if ( !incidence ) {
03625     return QString();
03626   }
03627 
03628   QString tmp = "<qt>";
03629 
03630   // header
03631   tmp += "<b>" + incidence->richSummary() + "</b>";
03632   tmp += "<hr>";
03633 
03634   QString calStr = mLocation;
03635   if ( mCalendar ) {
03636     calStr = resourceString( mCalendar, incidence );
03637   }
03638   if ( !calStr.isEmpty() ) {
03639     tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03640     tmp += calStr;
03641   }
03642 
03643   tmp += dtRangeText;
03644 
03645   if ( !incidence->location().isEmpty() ) {
03646     tmp += "<br>";
03647     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03648     tmp += incidence->richLocation();
03649   }
03650 
03651   QString durStr = durationString( incidence );
03652   if ( !durStr.isEmpty() ) {
03653     tmp += "<br>";
03654     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03655     tmp += durStr;
03656   }
03657 
03658   if ( incidence->recurs() ) {
03659     tmp += "<br>";
03660     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03661     tmp += recurrenceString( incidence );
03662   }
03663 
03664   if ( !incidence->description().isEmpty() ) {
03665     QString desc( incidence->description() );
03666     if ( !incidence->descriptionIsRich() ) {
03667       if ( desc.length() > maxDescLen ) {
03668         desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." );
03669       }
03670       desc = Qt::escape( desc ).replace( '\n', "<br>" );
03671     } else {
03672       // TODO: truncate the description when it's rich text
03673     }
03674     tmp += "<hr>";
03675     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03676     tmp += desc;
03677     tmp += "<hr>";
03678   }
03679 
03680   int reminderCount = incidence->alarms().count();
03681   if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) {
03682     tmp += "<br>";
03683     tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + "&nbsp;";
03684     tmp += reminderStringList( incidence ).join( ", " );
03685   }
03686 
03687   tmp += "<br>";
03688   tmp += tooltipFormatAttendees( mCalendar, incidence );
03689 
03690   int categoryCount = incidence->categories().count();
03691   if ( categoryCount > 0 ) {
03692     tmp += "<br>";
03693     tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + "&nbsp;";
03694     tmp += incidence->categories().join( ", " );
03695   }
03696 
03697   tmp += "</qt>";
03698   return tmp;
03699 }
03700 //@endcond
03701 
03702 QString IncidenceFormatter::toolTipStr( const QString &sourceName,
03703                                         const IncidenceBase::Ptr &incidence,
03704                                         const QDate &date,
03705                                         bool richText,
03706                                         KDateTime::Spec spec )
03707 {
03708   ToolTipVisitor v;
03709   if ( v.act( sourceName, incidence, date, richText, spec ) ) {
03710     return v.result();
03711   } else {
03712     return QString();
03713   }
03714 }
03715 
03716 /*******************************************************************
03717  *  Helper functions for the Incidence tooltips
03718  *******************************************************************/
03719 
03720 //@cond PRIVATE
03721 static QString mailBodyIncidence( const Incidence::Ptr &incidence )
03722 {
03723   QString body;
03724   if ( !incidence->summary().isEmpty() ) {
03725     body += i18n( "Summary: %1\n", incidence->richSummary() );
03726   }
03727   if ( !incidence->organizer()->isEmpty() ) {
03728     body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() );
03729   }
03730   if ( !incidence->location().isEmpty() ) {
03731     body += i18n( "Location: %1\n", incidence->richLocation() );
03732   }
03733   return body;
03734 }
03735 //@endcond
03736 
03737 //@cond PRIVATE
03738 class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor
03739 {
03740   public:
03741     MailBodyVisitor()
03742       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
03743 
03744     bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() )
03745     {
03746       mSpec = spec;
03747       mResult = "";
03748       return incidence ? incidence->accept( *this, incidence ) : false;
03749     }
03750     QString result() const
03751     {
03752       return mResult;
03753     }
03754 
03755   protected:
03756     bool visit( Event::Ptr event );
03757     bool visit( Todo::Ptr todo );
03758     bool visit( Journal::Ptr journal );
03759     bool visit( FreeBusy::Ptr )
03760     {
03761       mResult = i18n( "This is a Free Busy Object" );
03762       return !mResult.isEmpty();
03763     }
03764   protected:
03765     KDateTime::Spec mSpec;
03766     QString mResult;
03767 };
03768 
03769 bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event )
03770 {
03771   QString recurrence[]= {
03772     i18nc( "no recurrence", "None" ),
03773     i18nc( "event recurs by minutes", "Minutely" ),
03774     i18nc( "event recurs by hours", "Hourly" ),
03775     i18nc( "event recurs by days", "Daily" ),
03776     i18nc( "event recurs by weeks", "Weekly" ),
03777     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
03778     i18nc( "event recurs same day each month", "Monthly Same Day" ),
03779     i18nc( "event recurs same month each year", "Yearly Same Month" ),
03780     i18nc( "event recurs same day each year", "Yearly Same Day" ),
03781     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
03782   };
03783 
03784   mResult = mailBodyIncidence( event );
03785   mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
03786   if ( !event->allDay() ) {
03787     mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
03788   }
03789   if ( event->dtStart() != event->dtEnd() ) {
03790     mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
03791   }
03792   if ( !event->allDay() ) {
03793     mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
03794   }
03795   if ( event->recurs() ) {
03796     Recurrence *recur = event->recurrence();
03797     // TODO: Merge these two to one of the form "Recurs every 3 days"
03798     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
03799     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
03800 
03801     if ( recur->duration() > 0 ) {
03802       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
03803       mResult += '\n';
03804     } else {
03805       if ( recur->duration() != -1 ) {
03806 // TODO_Recurrence: What to do with all-day
03807         QString endstr;
03808         if ( event->allDay() ) {
03809           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03810         } else {
03811           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
03812         }
03813         mResult += i18n( "Repeat until: %1\n", endstr );
03814       } else {
03815         mResult += i18n( "Repeats forever\n" );
03816       }
03817     }
03818   }
03819 
03820   QString details = event->richDescription();
03821   if ( !details.isEmpty() ) {
03822     mResult += i18n( "Details:\n%1\n", details );
03823   }
03824   return !mResult.isEmpty();
03825 }
03826 
03827 bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo )
03828 {
03829   mResult = mailBodyIncidence( todo );
03830 
03831   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03832     mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
03833     if ( !todo->allDay() ) {
03834       mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
03835     }
03836   }
03837   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03838     mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
03839     if ( !todo->allDay() ) {
03840       mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
03841     }
03842   }
03843   QString details = todo->richDescription();
03844   if ( !details.isEmpty() ) {
03845     mResult += i18n( "Details:\n%1\n", details );
03846   }
03847   return !mResult.isEmpty();
03848 }
03849 
03850 bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal )
03851 {
03852   mResult = mailBodyIncidence( journal );
03853   mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
03854   if ( !journal->allDay() ) {
03855     mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
03856   }
03857   if ( !journal->description().isEmpty() ) {
03858     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
03859   }
03860   return !mResult.isEmpty();
03861 }
03862 //@endcond
03863 
03864 QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence,
03865                                          KDateTime::Spec spec )
03866 {
03867   if ( !incidence ) {
03868     return QString();
03869   }
03870 
03871   MailBodyVisitor v;
03872   if ( v.act( incidence, spec ) ) {
03873     return v.result();
03874   }
03875   return QString();
03876 }
03877 
03878 //@cond PRIVATE
03879 static QString recurEnd( const Incidence::Ptr &incidence )
03880 {
03881   QString endstr;
03882   if ( incidence->allDay() ) {
03883     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03884   } else {
03885     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03886   }
03887   return endstr;
03888 }
03889 //@endcond
03890 
03891 /************************************
03892  *  More static formatting functions
03893  ************************************/
03894 
03895 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence )
03896 {
03897   if ( !incidence->recurs() ) {
03898     return i18n( "No recurrence" );
03899   }
03900   static QStringList dayList;
03901   if ( dayList.isEmpty() ) {
03902     dayList.append( i18n( "31st Last" ) );
03903     dayList.append( i18n( "30th Last" ) );
03904     dayList.append( i18n( "29th Last" ) );
03905     dayList.append( i18n( "28th Last" ) );
03906     dayList.append( i18n( "27th Last" ) );
03907     dayList.append( i18n( "26th Last" ) );
03908     dayList.append( i18n( "25th Last" ) );
03909     dayList.append( i18n( "24th Last" ) );
03910     dayList.append( i18n( "23rd Last" ) );
03911     dayList.append( i18n( "22nd Last" ) );
03912     dayList.append( i18n( "21st Last" ) );
03913     dayList.append( i18n( "20th Last" ) );
03914     dayList.append( i18n( "19th Last" ) );
03915     dayList.append( i18n( "18th Last" ) );
03916     dayList.append( i18n( "17th Last" ) );
03917     dayList.append( i18n( "16th Last" ) );
03918     dayList.append( i18n( "15th Last" ) );
03919     dayList.append( i18n( "14th Last" ) );
03920     dayList.append( i18n( "13th Last" ) );
03921     dayList.append( i18n( "12th Last" ) );
03922     dayList.append( i18n( "11th Last" ) );
03923     dayList.append( i18n( "10th Last" ) );
03924     dayList.append( i18n( "9th Last" ) );
03925     dayList.append( i18n( "8th Last" ) );
03926     dayList.append( i18n( "7th Last" ) );
03927     dayList.append( i18n( "6th Last" ) );
03928     dayList.append( i18n( "5th Last" ) );
03929     dayList.append( i18n( "4th Last" ) );
03930     dayList.append( i18n( "3rd Last" ) );
03931     dayList.append( i18n( "2nd Last" ) );
03932     dayList.append( i18nc( "last day of the month", "Last" ) );
03933     dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03934     dayList.append( i18n( "1st" ) );
03935     dayList.append( i18n( "2nd" ) );
03936     dayList.append( i18n( "3rd" ) );
03937     dayList.append( i18n( "4th" ) );
03938     dayList.append( i18n( "5th" ) );
03939     dayList.append( i18n( "6th" ) );
03940     dayList.append( i18n( "7th" ) );
03941     dayList.append( i18n( "8th" ) );
03942     dayList.append( i18n( "9th" ) );
03943     dayList.append( i18n( "10th" ) );
03944     dayList.append( i18n( "11th" ) );
03945     dayList.append( i18n( "12th" ) );
03946     dayList.append( i18n( "13th" ) );
03947     dayList.append( i18n( "14th" ) );
03948     dayList.append( i18n( "15th" ) );
03949     dayList.append( i18n( "16th" ) );
03950     dayList.append( i18n( "17th" ) );
03951     dayList.append( i18n( "18th" ) );
03952     dayList.append( i18n( "19th" ) );
03953     dayList.append( i18n( "20th" ) );
03954     dayList.append( i18n( "21st" ) );
03955     dayList.append( i18n( "22nd" ) );
03956     dayList.append( i18n( "23rd" ) );
03957     dayList.append( i18n( "24th" ) );
03958     dayList.append( i18n( "25th" ) );
03959     dayList.append( i18n( "26th" ) );
03960     dayList.append( i18n( "27th" ) );
03961     dayList.append( i18n( "28th" ) );
03962     dayList.append( i18n( "29th" ) );
03963     dayList.append( i18n( "30th" ) );
03964     dayList.append( i18n( "31st" ) );
03965   }
03966 
03967   const int weekStart = KGlobal::locale()->weekStartDay();
03968   QString dayNames;
03969   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03970 
03971   Recurrence *recur = incidence->recurrence();
03972 
03973   QString txt, recurStr;
03974   static QString noRecurrence = i18n( "No recurrence" );
03975   switch ( recur->recurrenceType() ) {
03976   case Recurrence::rNone:
03977     return noRecurrence;
03978 
03979   case Recurrence::rMinutely:
03980     if ( recur->duration() != -1 ) {
03981       recurStr = i18np( "Recurs every minute until %2",
03982                         "Recurs every %1 minutes until %2",
03983                         recur->frequency(), recurEnd( incidence ) );
03984       if ( recur->duration() >  0 ) {
03985         recurStr += i18nc( "number of occurrences",
03986                            " (<numid>%1</numid> occurrences)",
03987                            recur->duration() );
03988       }
03989     } else {
03990       recurStr = i18np( "Recurs every minute",
03991                         "Recurs every %1 minutes", recur->frequency() );
03992     }
03993     break;
03994 
03995   case Recurrence::rHourly:
03996     if ( recur->duration() != -1 ) {
03997       recurStr = i18np( "Recurs hourly until %2",
03998                         "Recurs every %1 hours until %2",
03999                         recur->frequency(), recurEnd( incidence ) );
04000       if ( recur->duration() >  0 ) {
04001         recurStr += i18nc( "number of occurrences",
04002                            " (<numid>%1</numid> occurrences)",
04003                            recur->duration() );
04004       }
04005     } else {
04006       recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
04007     }
04008     break;
04009 
04010   case Recurrence::rDaily:
04011     if ( recur->duration() != -1 ) {
04012       recurStr = i18np( "Recurs daily until %2",
04013                        "Recurs every %1 days until %2",
04014                        recur->frequency(), recurEnd( incidence ) );
04015       if ( recur->duration() >  0 ) {
04016         recurStr += i18nc( "number of occurrences",
04017                            " (<numid>%1</numid> occurrences)",
04018                            recur->duration() );
04019       }
04020     } else {
04021       recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
04022     }
04023     break;
04024 
04025   case Recurrence::rWeekly:
04026   {
04027     bool addSpace = false;
04028     for ( int i = 0; i < 7; ++i ) {
04029       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04030         if ( addSpace ) {
04031           dayNames.append( i18nc( "separator for list of days", ", " ) );
04032         }
04033         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
04034                                               KCalendarSystem::ShortDayName ) );
04035         addSpace = true;
04036       }
04037     }
04038     if ( dayNames.isEmpty() ) {
04039       dayNames = i18nc( "Recurs weekly on no days", "no days" );
04040     }
04041     if ( recur->duration() != -1 ) {
04042       recurStr = i18ncp( "Recurs weekly on [list of days] until end-date",
04043                          "Recurs weekly on %2 until %3",
04044                          "Recurs every <numid>%1</numid> weeks on %2 until %3",
04045                          recur->frequency(), dayNames, recurEnd( incidence ) );
04046       if ( recur->duration() >  0 ) {
04047         recurStr += i18nc( "number of occurrences",
04048                            " (<numid>%1</numid> occurrences)",
04049                            recur->duration() );
04050       }
04051     } else {
04052       recurStr = i18ncp( "Recurs weekly on [list of days]",
04053                          "Recurs weekly on %2",
04054                          "Recurs every <numid>%1</numid> weeks on %2",
04055                          recur->frequency(), dayNames );
04056     }
04057     break;
04058   }
04059   case Recurrence::rMonthlyPos:
04060   {
04061     if ( !recur->monthPositions().isEmpty() ) {
04062       RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04063       if ( recur->duration() != -1 ) {
04064         recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
04065                            " weekdayname until end-date",
04066                            "Recurs every month on the %2 %3 until %4",
04067                            "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
04068                            recur->frequency(),
04069                            dayList[rule.pos() + 31],
04070                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04071                            recurEnd( incidence ) );
04072         if ( recur->duration() >  0 ) {
04073           recurStr += i18nc( "number of occurrences",
04074                              " (<numid>%1</numid> occurrences)",
04075                              recur->duration() );
04076         }
04077       } else {
04078         recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
04079                            "Recurs every month on the %2 %3",
04080                            "Recurs every %1 months on the %2 %3",
04081                            recur->frequency(),
04082                            dayList[rule.pos() + 31],
04083                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
04084       }
04085     }
04086     break;
04087   }
04088   case Recurrence::rMonthlyDay:
04089   {
04090     if ( !recur->monthDays().isEmpty() ) {
04091       int days = recur->monthDays()[0];
04092       if ( recur->duration() != -1 ) {
04093         recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
04094                            "Recurs monthly on the %2 day until %3",
04095                            "Recurs every %1 months on the %2 day until %3",
04096                            recur->frequency(),
04097                            dayList[days + 31],
04098                            recurEnd( incidence ) );
04099         if ( recur->duration() >  0 ) {
04100           recurStr += i18nc( "number of occurrences",
04101                              " (<numid>%1</numid> occurrences)",
04102                              recur->duration() );
04103         }
04104       } else {
04105         recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day",
04106                            "Recurs monthly on the %2 day",
04107                            "Recurs every <numid>%1</numid> month on the %2 day",
04108                            recur->frequency(),
04109                            dayList[days + 31] );
04110       }
04111     }
04112     break;
04113   }
04114   case Recurrence::rYearlyMonth:
04115   {
04116     if ( recur->duration() != -1 ) {
04117       if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04118         recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
04119                            " until end-date",
04120                            "Recurs yearly on %2 %3 until %4",
04121                            "Recurs every %1 years on %2 %3 until %4",
04122                            recur->frequency(),
04123                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04124                            dayList[ recur->yearDates()[0] + 31 ],
04125                            recurEnd( incidence ) );
04126         if ( recur->duration() >  0 ) {
04127           recurStr += i18nc( "number of occurrences",
04128                              " (<numid>%1</numid> occurrences)",
04129                              recur->duration() );
04130         }
04131       }
04132     } else {
04133       if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04134         recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
04135                            "Recurs yearly on %2 %3",
04136                            "Recurs every %1 years on %2 %3",
04137                            recur->frequency(),
04138                            calSys->monthName( recur->yearMonths()[0],
04139                                               recur->startDate().year() ),
04140                            dayList[ recur->yearDates()[0] + 31 ] );
04141       } else {
04142         if (!recur->yearMonths().isEmpty() ) {
04143           recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04144                             "Recurs yearly on %1 %2",
04145                             calSys->monthName( recur->yearMonths()[0],
04146                                                recur->startDate().year() ),
04147                             dayList[ recur->startDate().day() + 31 ] );
04148         } else {
04149           recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04150                             "Recurs yearly on %1 %2",
04151                             calSys->monthName( recur->startDate().month(),
04152                                                recur->startDate().year() ),
04153                             dayList[ recur->startDate().day() + 31 ] );
04154         }
04155       }
04156     }
04157     break;
04158   }
04159   case Recurrence::rYearlyDay:
04160     if ( !recur->yearDays().isEmpty() ) {
04161       if ( recur->duration() != -1 ) {
04162         recurStr = i18ncp( "Recurs every N years on day N until end-date",
04163                            "Recurs every year on day <numid>%2</numid> until %3",
04164                            "Recurs every <numid>%1</numid> years"
04165                            " on day <numid>%2</numid> until %3",
04166                            recur->frequency(),
04167                            recur->yearDays()[0],
04168                            recurEnd( incidence ) );
04169         if ( recur->duration() >  0 ) {
04170           recurStr += i18nc( "number of occurrences",
04171                              " (<numid>%1</numid> occurrences)",
04172                              recur->duration() );
04173         }
04174       } else {
04175         recurStr = i18ncp( "Recurs every N YEAR[S] on day N",
04176                            "Recurs every year on day <numid>%2</numid>",
04177                            "Recurs every <numid>%1</numid> years"
04178                            " on day <numid>%2</numid>",
04179                            recur->frequency(), recur->yearDays()[0] );
04180       }
04181     }
04182     break;
04183   case Recurrence::rYearlyPos:
04184   {
04185     if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
04186       RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04187       if ( recur->duration() != -1 ) {
04188         recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04189                            "of monthname until end-date",
04190                            "Every year on the %2 %3 of %4 until %5",
04191                            "Every <numid>%1</numid> years on the %2 %3 of %4"
04192                            " until %5",
04193                            recur->frequency(),
04194                            dayList[rule.pos() + 31],
04195                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04196                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04197                            recurEnd( incidence ) );
04198         if ( recur->duration() >  0 ) {
04199           recurStr += i18nc( "number of occurrences",
04200                              " (<numid>%1</numid> occurrences)",
04201                              recur->duration() );
04202         }
04203       } else {
04204         recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04205                            "of monthname",
04206                            "Every year on the %2 %3 of %4",
04207                            "Every <numid>%1</numid> years on the %2 %3 of %4",
04208                            recur->frequency(),
04209                            dayList[rule.pos() + 31],
04210                            calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04211                            calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04212       }
04213     }
04214   }
04215   break;
04216   }
04217 
04218   if ( recurStr.isEmpty() ) {
04219     recurStr = i18n( "Incidence recurs" );
04220   }
04221 
04222   // Now, append the EXDATEs
04223   DateTimeList l = recur->exDateTimes();
04224   DateTimeList::ConstIterator il;
04225   QStringList exStr;
04226   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04227     switch ( recur->recurrenceType() ) {
04228     case Recurrence::rMinutely:
04229       exStr << i18n( "minute %1", (*il).time().minute() );
04230       break;
04231     case Recurrence::rHourly:
04232       exStr << KGlobal::locale()->formatTime( (*il).time() );
04233       break;
04234     case Recurrence::rDaily:
04235       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04236       break;
04237     case Recurrence::rWeekly:
04238       exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName );
04239       break;
04240     case Recurrence::rMonthlyPos:
04241       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04242       break;
04243     case Recurrence::rMonthlyDay:
04244       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04245       break;
04246     case Recurrence::rYearlyMonth:
04247       exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName );
04248       break;
04249     case Recurrence::rYearlyDay:
04250       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04251       break;
04252     case Recurrence::rYearlyPos:
04253       exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04254       break;
04255     }
04256   }
04257 
04258   DateList d = recur->exDates();
04259   DateList::ConstIterator dl;
04260   for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04261     switch ( recur->recurrenceType() ) {
04262     case Recurrence::rDaily:
04263       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04264       break;
04265     case Recurrence::rWeekly:
04266       // exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName );
04267       // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
04268       if ( exStr.isEmpty() ) {
04269         exStr << i18np( "1 day", "%1 days", recur->exDates().count() );
04270       }
04271       break;
04272     case Recurrence::rMonthlyPos:
04273       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04274       break;
04275     case Recurrence::rMonthlyDay:
04276       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04277       break;
04278     case Recurrence::rYearlyMonth:
04279       exStr << calSys->monthName( (*dl), KCalendarSystem::LongName );
04280       break;
04281     case Recurrence::rYearlyDay:
04282       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04283       break;
04284     case Recurrence::rYearlyPos:
04285       exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04286       break;
04287     }
04288   }
04289 
04290   if ( !exStr.isEmpty() ) {
04291     recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) );
04292   }
04293 
04294   return recurStr;
04295 }
04296 
04297 QString IncidenceFormatter::timeToString( const KDateTime &date,
04298                                           bool shortfmt,
04299                                           const KDateTime::Spec &spec )
04300 {
04301   if ( spec.isValid() ) {
04302 
04303     QString timeZone;
04304     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04305       timeZone = ' ' + spec.timeZone().name();
04306     }
04307 
04308     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
04309   } else {
04310     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04311   }
04312 }
04313 
04314 QString IncidenceFormatter::dateToString( const KDateTime &date,
04315                                           bool shortfmt,
04316                                           const KDateTime::Spec &spec )
04317 {
04318   if ( spec.isValid() ) {
04319 
04320     QString timeZone;
04321     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04322       timeZone = ' ' + spec.timeZone().name();
04323     }
04324 
04325     return
04326       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
04327                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
04328       timeZone;
04329   } else {
04330     return
04331       KGlobal::locale()->formatDate( date.date(),
04332                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04333   }
04334 }
04335 
04336 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
04337                                               bool allDay,
04338                                               bool shortfmt,
04339                                               const KDateTime::Spec &spec )
04340 {
04341   if ( allDay ) {
04342     return dateToString( date, shortfmt, spec );
04343   }
04344 
04345   if ( spec.isValid() ) {
04346     QString timeZone;
04347     if ( spec.timeZone() != KSystemTimeZones::local() ) {
04348       timeZone = ' ' + spec.timeZone().name();
04349     }
04350 
04351     return KGlobal::locale()->formatDateTime(
04352       date.toTimeSpec( spec ).dateTime(),
04353       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
04354   } else {
04355     return  KGlobal::locale()->formatDateTime(
04356       date.dateTime(),
04357       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04358   }
04359 }
04360 
04361 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar,
04362                                             const Incidence::Ptr &incidence )
04363 {
04364   Q_UNUSED( calendar );
04365   Q_UNUSED( incidence );
04366   return QString();
04367 }
04368 
04369 static QString secs2Duration( int secs )
04370 {
04371   QString tmp;
04372   int days = secs / 86400;
04373   if ( days > 0 ) {
04374     tmp += i18np( "1 day", "%1 days", days );
04375     tmp += ' ';
04376     secs -= ( days * 86400 );
04377   }
04378   int hours = secs / 3600;
04379   if ( hours > 0 ) {
04380     tmp += i18np( "1 hour", "%1 hours", hours );
04381     tmp += ' ';
04382     secs -= ( hours * 3600 );
04383   }
04384   int mins = secs / 60;
04385   if ( mins > 0 ) {
04386     tmp += i18np( "1 minute", "%1 minutes", mins );
04387   }
04388   return tmp;
04389 }
04390 
04391 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence )
04392 {
04393   QString tmp;
04394   if ( incidence->type() == Incidence::TypeEvent ) {
04395     Event::Ptr event = incidence.staticCast<Event>();
04396     if ( event->hasEndDate() ) {
04397       if ( !event->allDay() ) {
04398         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04399       } else {
04400         tmp = i18np( "1 day", "%1 days",
04401                      event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04402       }
04403     } else {
04404       tmp = i18n( "forever" );
04405     }
04406   } else if ( incidence->type() == Incidence::TypeTodo ) {
04407     Todo::Ptr todo = incidence.staticCast<Todo>();
04408     if ( todo->hasDueDate() ) {
04409       if ( todo->hasStartDate() ) {
04410         if ( !todo->allDay() ) {
04411           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04412         } else {
04413           tmp = i18np( "1 day", "%1 days",
04414                        todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04415         }
04416       }
04417     }
04418   }
04419   return tmp;
04420 }
04421 
04422 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence,
04423                                                     bool shortfmt )
04424 {
04425   //TODO: implement shortfmt=false
04426   Q_UNUSED( shortfmt );
04427 
04428   QStringList reminderStringList;
04429 
04430   if ( incidence ) {
04431     Alarm::List alarms = incidence->alarms();
04432     Alarm::List::ConstIterator it;
04433     for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
04434       Alarm::Ptr alarm = *it;
04435       int offset = 0;
04436       QString remStr, atStr, offsetStr;
04437       if ( alarm->hasTime() ) {
04438         offset = 0;
04439         if ( alarm->time().isValid() ) {
04440           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04441         }
04442       } else if ( alarm->hasStartOffset() ) {
04443         offset = alarm->startOffset().asSeconds();
04444         if ( offset < 0 ) {
04445           offset = -offset;
04446           offsetStr = i18nc( "N days/hours/minutes before the start datetime",
04447                              "%1 before the start", secs2Duration( offset ) );
04448         } else if ( offset > 0 ) {
04449           offsetStr = i18nc( "N days/hours/minutes after the start datetime",
04450                              "%1 after the start", secs2Duration( offset ) );
04451         } else { //offset is 0
04452           if ( incidence->dtStart().isValid() ) {
04453             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04454           }
04455         }
04456       } else if ( alarm->hasEndOffset() ) {
04457         offset = alarm->endOffset().asSeconds();
04458         if ( offset < 0 ) {
04459           offset = -offset;
04460           if ( incidence->type() == Incidence::TypeTodo ) {
04461             offsetStr = i18nc( "N days/hours/minutes before the due datetime",
04462                                "%1 before the to-do is due", secs2Duration( offset ) );
04463           } else {
04464             offsetStr = i18nc( "N days/hours/minutes before the end datetime",
04465                                "%1 before the end", secs2Duration( offset ) );
04466           }
04467         } else if ( offset > 0 ) {
04468           if ( incidence->type() == Incidence::TypeTodo ) {
04469             offsetStr = i18nc( "N days/hours/minutes after the due datetime",
04470                                "%1 after the to-do is due", secs2Duration( offset ) );
04471           } else {
04472             offsetStr = i18nc( "N days/hours/minutes after the end datetime",
04473                                "%1 after the end", secs2Duration( offset ) );
04474           }
04475         } else { //offset is 0
04476           if ( incidence->type() == Incidence::TypeTodo ) {
04477             Todo::Ptr t = incidence.staticCast<Todo>();
04478             if ( t->dtDue().isValid() ) {
04479               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04480             }
04481           } else {
04482             Event::Ptr e = incidence.staticCast<Event>();
04483             if ( e->dtEnd().isValid() ) {
04484               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04485             }
04486           }
04487         }
04488       }
04489       if ( offset == 0 ) {
04490         if ( !atStr.isEmpty() ) {
04491           remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
04492         }
04493       } else {
04494         remStr = offsetStr;
04495       }
04496 
04497       if ( alarm->repeatCount() > 0 ) {
04498         QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
04499         QString intervalStr = i18nc( "interval is N days/hours/minutes",
04500                                      "interval is %1",
04501                                      secs2Duration( alarm->snoozeTime().asSeconds() ) );
04502         QString repeatStr = i18nc( "(repeat string, interval string)",
04503                                    "(%1, %2)", countStr, intervalStr );
04504         remStr = remStr + ' ' + repeatStr;
04505 
04506       }
04507       reminderStringList << remStr;
04508     }
04509   }
04510 
04511   return reminderStringList;
04512 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:49:01 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalUtils Library

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal