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

KCalUtils Library

Skip menu "KCalUtils Library"
  • Main Page
  • Namespace List
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • 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.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal