• Skip to content
  • Skip to link menu
KDE 4.5 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KCal Library

incidenceformatter.cpp

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

KCal Library

Skip menu "KCal Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • 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
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • 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.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal