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

KCalUtils Library

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

KCalUtils Library

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal