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

KCal Library

incidenceformatter.cpp

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

KCal Library

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

KDE-PIM Libraries

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