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

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.7.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal