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

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
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.3
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