• Skip to content
  • Skip to link menu
KDE 4.4 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 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 //@endcond
00148 
00149 /*******************************************************************
00150  *  Helper functions for the extensive display (display viewer)
00151  *******************************************************************/
00152 
00153 //@cond PRIVATE
00154 static QString displayViewLinkPerson( const QString &email, QString name,
00155                                       QString uid, const QString &iconPath )
00156 {
00157   // Make the search, if there is an email address to search on,
00158   // and either name or uid is missing
00159   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00160     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00161     KABC::Addressee::List addressList = add_book->findByEmail( email );
00162     KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() );
00163     if ( !o.isEmpty() && addressList.size() < 2 ) {
00164       if ( name.isEmpty() ) {
00165         // No name set, so use the one from the addressbook
00166         name = o.formattedName();
00167       }
00168       uid = o.uid();
00169     } else {
00170       // Email not found in the addressbook. Don't make a link
00171       uid.clear();
00172     }
00173   }
00174 
00175   // Show the attendee
00176   QString tmpString;
00177   if ( !uid.isEmpty() ) {
00178     // There is a UID, so make a link to the addressbook
00179     if ( name.isEmpty() ) {
00180       // Use the email address for text
00181       tmpString += htmlAddLink( "uid:" + uid, email );
00182     } else {
00183       tmpString += htmlAddLink( "uid:" + uid, name );
00184     }
00185   } else {
00186     // No UID, just show some text
00187     tmpString += ( name.isEmpty() ? email : name );
00188   }
00189 
00190   // Make the mailto link
00191   if ( !email.isEmpty() && !iconPath.isNull() ) {
00192     KUrl mailto;
00193     mailto.setProtocol( "mailto" );
00194     mailto.setPath( email );
00195     tmpString += htmlAddLink( mailto.url(),
00196                               "<img valign=\"top\" src=\"" + iconPath + "\">" );
00197   }
00198 
00199   return tmpString;
00200 }
00201 
00202 static QString displayViewFormatAttendees( Incidence *incidence )
00203 {
00204   QString tmpStr;
00205   Attendee::List attendees = incidence->attendees();
00206   KIconLoader *iconLoader = KIconLoader::global();
00207   const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00208 
00209   // Add organizer link
00210   tmpStr += "<tr>";
00211   tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00212   tmpStr += "<td>" +
00213             displayViewLinkPerson( incidence->organizer().email(),
00214                                    incidence->organizer().name(),
00215                                    QString(), iconPath ) +
00216             "</td>";
00217   tmpStr += "</tr>";
00218 
00219   // Add attendees links
00220   tmpStr += "<tr>";
00221   tmpStr += "<td><b>" + i18n( "Attendees:" ) + "</b></td>";
00222   tmpStr += "<td>";
00223   Attendee::List::ConstIterator it;
00224   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00225     Attendee *a = *it;
00226     if ( a->email() == incidence->organizer().email() ) {
00227       // skip attendee that is also the organizer
00228       continue;
00229     }
00230     tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid(), iconPath );
00231     if ( !a->delegator().isEmpty() ) {
00232       tmpStr += i18n( " (delegated by %1)", a->delegator() );
00233     }
00234     if ( !a->delegate().isEmpty() ) {
00235       tmpStr += i18n( " (delegated to %1)", a->delegate() );
00236     }
00237     tmpStr += "<br>";
00238   }
00239   if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
00240     tmpStr.chop( 4 );
00241   }
00242 
00243   tmpStr += "</td>";
00244   tmpStr += "</tr>";
00245   return tmpStr;
00246 }
00247 
00248 static QString displayViewFormatAttachments( Incidence *incidence )
00249 {
00250   QString tmpStr;
00251   Attachment::List as = incidence->attachments();
00252   Attachment::List::ConstIterator it;
00253   int count = 0;
00254   for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
00255     count++;
00256     if ( (*it)->isUri() ) {
00257       QString name;
00258       if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) {
00259         name = i18n( "Show mail" );
00260       } else {
00261         name = (*it)->label();
00262       }
00263       tmpStr += htmlAddLink( (*it)->uri(), name );
00264     } else {
00265       tmpStr += (*it)->label();
00266     }
00267     if ( count < as.count() ) {
00268       tmpStr += "<br>";
00269     }
00270   }
00271   return tmpStr;
00272 }
00273 
00274 static QString displayViewFormatCategories( Incidence *incidence )
00275 {
00276   return incidence->categoriesStr();
00277 }
00278 
00279 static QString displayViewFormatCreationDate( Incidence *incidence, KDateTime::Spec spec )
00280 {
00281   KDateTime kdt = incidence->created().toTimeSpec( spec );
00282   return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) );
00283 }
00284 
00285 static QString displayViewFormatBirthday( Event *event )
00286 {
00287   if ( !event ) {
00288     return QString();
00289   }
00290   if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" ) {
00291     return QString();
00292   }
00293 
00294   QString uid_1 = event->customProperty( "KABC", "UID-1" );
00295   QString name_1 = event->customProperty( "KABC", "NAME-1" );
00296   QString email_1= event->customProperty( "KABC", "EMAIL-1" );
00297 
00298   KIconLoader *iconLoader = KIconLoader::global();
00299   const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00300   //TODO: add a birthday cake icon
00301   QString tmpStr = displayViewLinkPerson( email_1, name_1, uid_1, iconPath );
00302 
00303   if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00304     QString uid_2 = event->customProperty( "KABC", "UID-2" );
00305     QString name_2 = event->customProperty( "KABC", "NAME-2" );
00306     QString email_2= event->customProperty( "KABC", "EMAIL-2" );
00307     tmpStr += "<br>";
00308     tmpStr += displayViewLinkPerson( email_2, name_2, uid_2, iconPath );
00309   }
00310 
00311   return tmpStr;
00312 }
00313 
00314 static QString displayViewFormatHeader( Incidence *incidence )
00315 {
00316   QString tmpStr = "<table><tr>";
00317 
00318   // show icons
00319   KIconLoader *iconLoader = KIconLoader::global();
00320   tmpStr += "<td>";
00321 
00322   // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't
00323   // need downcasting.
00324 
00325   if ( incidence->type() == "Todo" ) {
00326     tmpStr += "<img valign=\"top\" src=\"";
00327     Todo *todo = static_cast<Todo *>( incidence );
00328     if ( !todo->isCompleted() ) {
00329       tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small );
00330     } else {
00331       tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small );
00332     }
00333     tmpStr += "\">";
00334   }
00335 
00336   if ( incidence->type() == "Event" ) {
00337     QString iconPath;
00338     if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00339       if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00340         iconPath = iconLoader->iconPath( "view-calendar-anniversary", KIconLoader::Small );
00341       } else {
00342         iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small );
00343       }
00344     } else {
00345       iconPath = iconLoader->iconPath( "view-calendar-day", KIconLoader::Small );
00346     }
00347     tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00348   }
00349 
00350   if ( incidence->type() == "Journal" ) {
00351     tmpStr += "<img valign=\"top\" src=\"" +
00352               iconLoader->iconPath( "view-pim-journal", KIconLoader::Small ) +
00353               "\">";
00354   }
00355 
00356   if ( incidence->isAlarmEnabled() ) {
00357     tmpStr += "<img valign=\"top\" src=\"" +
00358               iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
00359               "\">";
00360   }
00361   if ( incidence->recurs() ) {
00362     tmpStr += "<img valign=\"top\" src=\"" +
00363               iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
00364               "\">";
00365   }
00366   if ( incidence->isReadOnly() ) {
00367     tmpStr += "<img valign=\"top\" src=\"" +
00368               iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
00369               "\">";
00370   }
00371   tmpStr += "</td>";
00372 
00373   tmpStr += "<td>";
00374   tmpStr += "<b><u>" + incidence->richSummary() + "</u></b>";
00375   tmpStr += "</td>";
00376 
00377   tmpStr += "</tr></table>";
00378 
00379   return tmpStr;
00380 }
00381 
00382 static QString displayViewFormatEvent( Calendar *calendar, Event *event,
00383                                        const QDate &date, KDateTime::Spec spec )
00384 {
00385   if ( !event ) {
00386     return QString();
00387   }
00388 
00389   QString tmpStr = displayViewFormatHeader( event );
00390 
00391   tmpStr += "<table>";
00392   tmpStr += "<col width=\"25%\"/>";
00393   tmpStr += "<col width=\"75%\"/>";
00394 
00395   if ( calendar ) {
00396     QString calStr = resourceString( calendar, event );
00397     if ( !calStr.isEmpty() ) {
00398       tmpStr += "<tr>";
00399       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00400       tmpStr += "<td>" + calStr + "</td>";
00401       tmpStr += "</tr>";
00402     }
00403   }
00404 
00405   if ( !event->location().isEmpty() ) {
00406     tmpStr += "<tr>";
00407     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00408     tmpStr += "<td>" + event->richLocation() + "</td>";
00409     tmpStr += "</tr>";
00410   }
00411 
00412   tmpStr += "<tr>";
00413   KDateTime startDt = event->dtStart();
00414   KDateTime endDt = event->dtEnd();
00415   if ( event->recurs() ) {
00416     if ( date.isValid() ) {
00417       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
00418       int diffDays = startDt.daysTo( kdt );
00419       kdt = kdt.addSecs( -1 );
00420       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
00421       if ( event->hasEndDate() ) {
00422         endDt = endDt.addDays( diffDays );
00423         if ( startDt > endDt ) {
00424           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
00425           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00426         }
00427       }
00428     }
00429   }
00430   if ( event->allDay() ) {
00431     if ( event->isMultiDay() ) {
00432       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00433       tmpStr += "<td>" +
00434                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00435                        dateToString( startDt, false, spec ),
00436                        dateToString( endDt, false, spec ) ) +
00437                 "</td>";
00438     } else {
00439       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00440       tmpStr += "<td>" +
00441                 i18nc( "date as string","%1",
00442                        dateToString( startDt, false, spec ) ) +
00443                 "</td>";
00444     }
00445   } else {
00446     if ( event->isMultiDay() ) {
00447       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00448       tmpStr += "<td>" +
00449                 i18nc( "<beginTime> - <endTime>","%1 - %2",
00450                        dateToString( startDt, false, spec ),
00451                        dateToString( endDt, false, spec ) ) +
00452                 "</td>";
00453     } else {
00454       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00455       if ( event->hasEndDate() && startDt != endDt ) {
00456         tmpStr += "<td>" +
00457                   i18nc( "<beginTime> - <endTime>","%1 - %2",
00458                          timeToString( startDt, true, spec ),
00459                          timeToString( endDt, true, spec ) ) +
00460                   "</td>";
00461       } else {
00462         tmpStr += "<td>" +
00463                   timeToString( startDt, true, spec ) +
00464                   "</td>";
00465       }
00466       tmpStr += "</tr><tr>";
00467       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00468       tmpStr += "<td>" +
00469                 i18nc( "date as string","%1",
00470                        dateToString( startDt, false, spec ) ) +
00471                 "</td>";
00472     }
00473   }
00474   tmpStr += "</tr>";
00475 
00476   if ( event->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00477     tmpStr += "<tr>";
00478     if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00479       tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00480     } else {
00481       tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00482     }
00483     tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00484     tmpStr += "</tr>";
00485     tmpStr += "</table>";
00486     return tmpStr;
00487   }
00488 
00489   if ( !event->description().isEmpty() ) {
00490     tmpStr += "<tr>";
00491     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00492     tmpStr += "<td>" + event->richDescription() + "</td>";
00493     tmpStr += "</tr>";
00494   }
00495 
00496   int categoryCount = event->categories().count();
00497   if ( categoryCount > 0 ) {
00498     tmpStr += "<tr>";
00499     tmpStr += "<td><b>";
00500     tmpStr += i18np( "Category:", "Categories:", categoryCount ) +
00501               "</b></td>";
00502     tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00503     tmpStr += "</tr>";
00504   }
00505 
00506   if ( event->recurs() ) {
00507     KDateTime dt = event->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00508     tmpStr += "<tr>";
00509     tmpStr += "<td><b>" + i18nc( "next occurrence", "Next:" )+ "</b></td>";
00510     tmpStr += "<td>" +
00511               ( dt.isValid() ?
00512                 dateTimeToString( dt, event->allDay(), false, spec ) :
00513                 i18nc( "no date", "none" ) ) +
00514               "</td>";
00515     tmpStr += "</tr>";
00516   }
00517 
00518   if ( event->attendees().count() > 1 ) {
00519     tmpStr += displayViewFormatAttendees( event );
00520   }
00521 
00522   int attachmentCount = event->attachments().count();
00523   if ( attachmentCount > 0 ) {
00524     tmpStr += "<tr>";
00525     tmpStr += "<td><b>" +
00526               i18np( "Attachment:", "Attachments:", attachmentCount ) +
00527               "</b></td>";
00528     tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00529     tmpStr += "</tr>";
00530   }
00531   tmpStr += "</table>";
00532 
00533   tmpStr += "<p><em>" + displayViewFormatCreationDate( event, spec ) + "</em>";
00534 
00535   return tmpStr;
00536 }
00537 
00538 static QString displayViewFormatTodo( Calendar *calendar, Todo *todo,
00539                                       const QDate &date, KDateTime::Spec spec )
00540 {
00541   if ( !todo ) {
00542     return QString();
00543   }
00544 
00545   QString tmpStr = displayViewFormatHeader( todo );
00546 
00547   tmpStr += "<table>";
00548   tmpStr += "<col width=\"25%\"/>";
00549   tmpStr += "<col width=\"75%\"/>";
00550 
00551   if ( calendar ) {
00552     QString calStr = IncidenceFormatter::resourceString( calendar, todo );
00553     if ( !calStr.isEmpty() ) {
00554       tmpStr += "<tr>";
00555       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00556       tmpStr += "<td>" + calStr + "</td>";
00557       tmpStr += "</tr>";
00558     }
00559   }
00560 
00561   if ( !todo->location().isEmpty() ) {
00562     tmpStr += "<tr>";
00563     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00564     tmpStr += "<td>" + todo->richLocation() + "</td>";
00565     tmpStr += "</tr>";
00566   }
00567 
00568   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
00569     KDateTime startDt = todo->dtStart();
00570     if ( todo->recurs() ) {
00571       if ( date.isValid() ) {
00572         startDt.setDate( date );
00573       }
00574     }
00575     tmpStr += "<tr>";
00576     tmpStr += "<td><b>" +
00577               i18nc( "to-do start date/time", "Start:" ) +
00578               "</b></td>";
00579     tmpStr += "<td>" +
00580               dateTimeToString( startDt, todo->allDay(), false, spec ) +
00581               "</td>";
00582     tmpStr += "</tr>";
00583   }
00584 
00585   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00586     KDateTime dueDt = todo->dtDue();
00587     if ( todo->recurs() ) {
00588       if ( date.isValid() ) {
00589         dueDt.addDays( todo->dtDue().date().daysTo( date ) );
00590       }
00591     }
00592     tmpStr += "<tr>";
00593     tmpStr += "<td><b>" +
00594               i18nc( "to-do due date/time", "Due:" ) +
00595               "</b></td>";
00596     tmpStr += "<td>" +
00597               dateTimeToString( dueDt, todo->allDay(), false, spec ) +
00598               "</td>";
00599     tmpStr += "</tr>";
00600   }
00601 
00602   if ( !todo->description().isEmpty() ) {
00603     tmpStr += "<tr>";
00604     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00605     tmpStr += "<td>" + todo->richDescription() + "</td>";
00606     tmpStr += "</tr>";
00607   }
00608 
00609   int categoryCount = todo->categories().count();
00610   if ( categoryCount > 0 ) {
00611     tmpStr += "<tr>";
00612     tmpStr += "<td><b>" +
00613               i18np( "Category:", "Categories:", categoryCount ) +
00614               "</b></td>";
00615     tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00616     tmpStr += "</tr>";
00617   }
00618 
00619   tmpStr += "<tr>";
00620   tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00621   tmpStr += "<td>";
00622   if ( todo->priority() > 0 ) {
00623     tmpStr += QString::number( todo->priority() );
00624   } else {
00625     tmpStr += i18n( "Unspecified" );
00626   }
00627   tmpStr += "</td>";
00628   tmpStr += "</tr>";
00629 
00630   tmpStr += "<tr>";
00631   tmpStr += "<td><b>" +
00632             i18nc( "percent completed", "Completed:" ) + "</b></td>";
00633   tmpStr += "<td>" + i18n( "%1%", todo->percentComplete() ) + "</td>";
00634   tmpStr += "</tr>";
00635 
00636   if ( todo->recurs() ) {
00637     KDateTime dt = todo->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00638     tmpStr += "<tr>";
00639     tmpStr += "<td><b>" + i18nc( "next occurrence", "Next:" ) + "</b></td>";
00640     tmpStr += ( dt.isValid() ?
00641                 dateTimeToString( dt, todo->allDay(), false, spec ) :
00642                 i18nc( "no date", "none" ) ) +
00643               "</td>";
00644     tmpStr += "</tr>";
00645   }
00646 
00647   if ( todo->attendees().count() > 1 ) {
00648     tmpStr += displayViewFormatAttendees( todo );
00649   }
00650 
00651   int attachmentCount = todo->attachments().count();
00652   if ( attachmentCount > 0 ) {
00653     tmpStr += "<tr>";
00654     tmpStr += "<td><b>" +
00655               i18np( "Attachment:", "Attachments:", attachmentCount ) +
00656               "</b></td>";
00657     tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00658     tmpStr += "</tr>";
00659   }
00660   tmpStr += "</table>";
00661 
00662   tmpStr += "<p><em>" + displayViewFormatCreationDate( todo, spec ) + "</em>";
00663 
00664   return tmpStr;
00665 }
00666 
00667 static QString displayViewFormatJournal( Calendar *calendar, Journal *journal,
00668                                          KDateTime::Spec spec )
00669 {
00670   if ( !journal ) {
00671     return QString();
00672   }
00673 
00674   QString tmpStr = displayViewFormatHeader( journal );
00675 
00676   tmpStr += "<table>";
00677   tmpStr += "<col width=\"25%\"/>";
00678   tmpStr += "<col width=\"75%\"/>";
00679 
00680   if ( calendar ) {
00681     QString calStr = IncidenceFormatter::resourceString( calendar, journal );
00682     if ( !calStr.isEmpty() ) {
00683       tmpStr += "<tr>";
00684       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00685       tmpStr += "<td>" + calStr + "</td>";
00686       tmpStr += "</tr>";
00687     }
00688   }
00689 
00690   tmpStr += "<tr>";
00691   tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00692   tmpStr += "<td>" +
00693             dateToString( journal->dtStart(), false, spec ) +
00694             "</td>";
00695   tmpStr += "</tr>";
00696 
00697   if ( !journal->description().isEmpty() ) {
00698     tmpStr += "<tr>";
00699     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00700     tmpStr += "<td>" + journal->richDescription() + "</td>";
00701     tmpStr += "</tr>";
00702   }
00703 
00704   int categoryCount = journal->categories().count();
00705   if ( categoryCount > 0 ) {
00706     tmpStr += "<tr>";
00707     tmpStr += "<td><b>" +
00708               i18np( "Category:", "Categories:", categoryCount ) +
00709               "</b></td>";
00710     tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00711     tmpStr += "</tr>";
00712   }
00713 
00714   tmpStr += "</table>";
00715 
00716   tmpStr += "<p><em>" + displayViewFormatCreationDate( journal, spec ) + "</em>";
00717 
00718   return tmpStr;
00719 }
00720 
00721 static QString displayViewFormatFreeBusy( Calendar *calendar, FreeBusy *fb,
00722                                           KDateTime::Spec spec )
00723 {
00724   Q_UNUSED( calendar );
00725   if ( !fb ) {
00726     return QString();
00727   }
00728 
00729   QString tmpStr(
00730     htmlAddTag(
00731       "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) );
00732 
00733   tmpStr += htmlAddTag( "h4",
00734                         i18n( "Busy times in date range %1 - %2:",
00735                               dateToString( fb->dtStart(), true, spec ),
00736                               dateToString( fb->dtEnd(), true, spec ) ) );
00737 
00738   QList<Period> periods = fb->busyPeriods();
00739 
00740   QString text =
00741     htmlAddTag( "em",
00742                 htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
00743 
00744   QList<Period>::iterator it;
00745   for ( it = periods.begin(); it != periods.end(); ++it ) {
00746     Period per = *it;
00747     if ( per.hasDuration() ) {
00748       int dur = per.duration().asSeconds();
00749       QString cont;
00750       if ( dur >= 3600 ) {
00751         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00752         dur %= 3600;
00753       }
00754       if ( dur >= 60 ) {
00755         cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
00756         dur %= 60;
00757       }
00758       if ( dur > 0 ) {
00759         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00760       }
00761       text += i18nc( "startDate for duration", "%1 for %2",
00762                      dateTimeToString( per.start(), false, true, spec ),
00763                      cont );
00764       text += "<br>";
00765     } else {
00766       if ( per.start().date() == per.end().date() ) {
00767         text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00768                        dateToString( per.start(), true, spec ),
00769                        timeToString( per.start(), true, spec ),
00770                        timeToString( per.end(), true, spec ) );
00771       } else {
00772         text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
00773                        dateTimeToString( per.start(), false, true, spec ),
00774                        dateTimeToString( per.end(), false, true, spec ) );
00775       }
00776       text += "<br>";
00777     }
00778   }
00779   tmpStr += htmlAddTag( "p", text );
00780   return tmpStr;
00781 }
00782 //@endcond
00783 
00784 //@cond PRIVATE
00785 class KCal::IncidenceFormatter::EventViewerVisitor
00786   : public IncidenceBase::Visitor
00787 {
00788   public:
00789     EventViewerVisitor()
00790       : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
00791 
00792     bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date,
00793               KDateTime::Spec spec=KDateTime::Spec() )
00794     {
00795       mCalendar = calendar;
00796       mDate = date;
00797       mSpec = spec;
00798       mResult = "";
00799       return incidence->accept( *this );
00800     }
00801     QString result() const { return mResult; }
00802 
00803   protected:
00804     bool visit( Event *event )
00805     {
00806       mResult = displayViewFormatEvent( mCalendar, event, mDate, mSpec );
00807       return !mResult.isEmpty();
00808     }
00809     bool visit( Todo *todo )
00810     {
00811       mResult = displayViewFormatTodo( mCalendar, todo, mDate, mSpec );
00812       return !mResult.isEmpty();
00813     }
00814     bool visit( Journal *journal )
00815     {
00816       mResult = displayViewFormatJournal( mCalendar, journal, mSpec );
00817       return !mResult.isEmpty();
00818     }
00819     bool visit( FreeBusy *fb )
00820     {
00821       mResult = displayViewFormatFreeBusy( mCalendar, fb, mSpec );
00822       return !mResult.isEmpty();
00823     }
00824 
00825   protected:
00826     Calendar *mCalendar;
00827     QDate mDate;
00828     KDateTime::Spec mSpec;
00829     QString mResult;
00830 };
00831 //@endcond
00832 
00833 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00834 {
00835   return extensiveDisplayStr( 0, incidence, QDate(), KDateTime::Spec() );
00836 }
00837 
00838 QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence,
00839                                                  KDateTime::Spec spec )
00840 {
00841   if ( !incidence ) {
00842     return QString();
00843   }
00844 
00845   EventViewerVisitor v;
00846   if ( v.act( 0, incidence, QDate(), spec ) ) {
00847     return v.result();
00848   } else {
00849     return QString();
00850   }
00851 }
00852 
00853 QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar,
00854                                                  IncidenceBase *incidence,
00855                                                  const QDate &date,
00856                                                  KDateTime::Spec spec )
00857 {
00858   if ( !incidence ) {
00859     return QString();
00860   }
00861 
00862   EventViewerVisitor v;
00863   if ( v.act( calendar, incidence, date, spec ) ) {
00864     return v.result();
00865   } else {
00866     return QString();
00867   }
00868 }
00869 
00870 /***********************************************************************
00871  *  Helper functions for the body part formatter of kmail (Invitations)
00872  ***********************************************************************/
00873 
00874 //@cond PRIVATE
00875 static QString string2HTML( const QString &str )
00876 {
00877   return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
00878 }
00879 
00880 static QString cleanHtml( const QString &html )
00881 {
00882   QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
00883   rx.indexIn( html );
00884   QString body = rx.cap( 1 );
00885 
00886   return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
00887 }
00888 
00889 static QString eventStartTimeStr( Event *event )
00890 {
00891   QString tmp;
00892   if ( !event->allDay() ) {
00893     tmp =  i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
00894                   dateToString( event->dtStart(), true, KSystemTimeZones::local() ),
00895                   timeToString( event->dtStart(), true, KSystemTimeZones::local() ) );
00896   } else {
00897     tmp = i18nc( "%1: Start Date", "%1 (all day)",
00898                  dateToString( event->dtStart(), true, KSystemTimeZones::local() ) );
00899   }
00900   return tmp;
00901 }
00902 
00903 static QString eventEndTimeStr( Event *event )
00904 {
00905   QString tmp;
00906   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00907     if ( !event->allDay() ) {
00908       tmp =  i18nc( "%1: End Date, %2: End Time", "%1 %2",
00909                     dateToString( event->dtEnd(), true, KSystemTimeZones::local() ),
00910                     timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
00911     } else {
00912       tmp = i18nc( "%1: End Date", "%1 (all day)",
00913                    dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
00914     }
00915   }
00916   return tmp;
00917 }
00918 
00919 static QString invitationRow( const QString &cell1, const QString &cell2 )
00920 {
00921   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00922 }
00923 
00924 static Attendee *findMyAttendee( Incidence *incidence )
00925 {
00926   // Return the attendee for the incidence that is probably me
00927 
00928   Attendee *attendee = 0;
00929   if ( !incidence ) {
00930     return attendee;
00931   }
00932 
00933   KEMailSettings settings;
00934   QStringList profiles = settings.profiles();
00935   for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
00936     settings.setProfile( *it );
00937 
00938     Attendee::List attendees = incidence->attendees();
00939     Attendee::List::ConstIterator it2;
00940     for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
00941       Attendee *a = *it2;
00942       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
00943         attendee = a;
00944         break;
00945       }
00946     }
00947   }
00948   return attendee;
00949 }
00950 
00951 static Attendee *findAttendee( Incidence *incidence, const QString &email )
00952 {
00953   // Search for an attendee by email address
00954 
00955   Attendee *attendee = 0;
00956   if ( !incidence ) {
00957     return attendee;
00958   }
00959 
00960   Attendee::List attendees = incidence->attendees();
00961   Attendee::List::ConstIterator it;
00962   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00963     Attendee *a = *it;
00964     if ( email == a->email() ) {
00965       attendee = a;
00966       break;
00967     }
00968   }
00969   return attendee;
00970 }
00971 
00972 static bool rsvpRequested( Incidence *incidence )
00973 {
00974   if ( !incidence ) {
00975     return false;
00976   }
00977 
00978   //use a heuristic to determine if a response is requested.
00979 
00980   bool rsvp = true; // better send superfluously than not at all
00981   Attendee::List attendees = incidence->attendees();
00982   Attendee::List::ConstIterator it;
00983   for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00984     if ( it == attendees.constBegin() ) {
00985       rsvp = (*it)->RSVP(); // use what the first one has
00986     } else {
00987       if ( (*it)->RSVP() != rsvp ) {
00988         rsvp = true; // they differ, default
00989         break;
00990       }
00991     }
00992   }
00993   return rsvp;
00994 }
00995 
00996 static QString rsvpRequestedStr( bool rsvpRequested )
00997 {
00998   if ( rsvpRequested ) {
00999     return i18n( "Your response is requested" );
01000   } else {
01001     return i18n( "A response is not necessary" );
01002   }
01003 }
01004 
01005 static QString invitationPerson( const QString &email, QString name, QString uid )
01006 {
01007   // Make the search, if there is an email address to search on,
01008   // and either name or uid is missing
01009   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
01010     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
01011     KABC::Addressee::List addressList = add_book->findByEmail( email );
01012     if ( !addressList.isEmpty() ) {
01013       KABC::Addressee o = addressList.first();
01014       if ( !o.isEmpty() && addressList.size() < 2 ) {
01015         if ( name.isEmpty() ) {
01016           // No name set, so use the one from the addressbook
01017           name = o.formattedName();
01018         }
01019         uid = o.uid();
01020       } else {
01021         // Email not found in the addressbook. Don't make a link
01022         uid.clear();
01023       }
01024     }
01025   }
01026 
01027   // Show the attendee
01028   QString tmpString;
01029   if ( !uid.isEmpty() ) {
01030     // There is a UID, so make a link to the addressbook
01031     if ( name.isEmpty() ) {
01032       // Use the email address for text
01033       tmpString += htmlAddLink( "uid:" + uid, email );
01034     } else {
01035       tmpString += htmlAddLink( "uid:" + uid, name );
01036     }
01037   } else {
01038     // No UID, just show some text
01039     tmpString += ( name.isEmpty() ? email : name );
01040   }
01041   tmpString += '\n';
01042 
01043   // Make the mailto link
01044   if ( !email.isEmpty() ) {
01045     KCal::Person person( name, email );
01046     KUrl mailto;
01047     mailto.setProtocol( "mailto" );
01048     mailto.setPath( person.fullName() );
01049     const QString iconPath =
01050       KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small );
01051     tmpString += htmlAddLink( mailto.url(),
01052                               "<img valign=\"top\" src=\"" + iconPath + "\">" );
01053   }
01054   tmpString += '\n';
01055 
01056   return tmpString;
01057 }
01058 
01059 static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode )
01060 {
01061   // if description and comment -> use both
01062   // if description, but no comment -> use the desc as the comment (and no desc)
01063   // if comment, but no description -> use the comment and no description
01064 
01065   QString html;
01066   QString descr;
01067   QStringList comments;
01068 
01069   if ( incidence->comments().isEmpty() ) {
01070     if ( !incidence->description().isEmpty() ) {
01071       // use description as comments
01072       if ( !incidence->descriptionIsRich() ) {
01073         comments << string2HTML( incidence->description() );
01074       } else {
01075         comments << incidence->richDescription();
01076         if ( noHtmlMode ) {
01077           comments[0] = cleanHtml( comments[0] );
01078         }
01079         comments[0] = htmlAddTag( "p", comments[0] );
01080       }
01081     }
01082     //else desc and comments are empty
01083   } else {
01084     // non-empty comments
01085     foreach ( const QString &c, incidence->comments() ) {
01086       if ( !c.isEmpty() ) {
01087         comments += string2HTML( c );
01088       }
01089     }
01090     if ( !incidence->description().isEmpty() ) {
01091       // use description too
01092       if ( !incidence->descriptionIsRich() ) {
01093         descr = string2HTML( incidence->description() );
01094       } else {
01095         descr = incidence->richDescription();
01096         if ( noHtmlMode ) {
01097           descr = cleanHtml( descr );
01098         }
01099         descr = htmlAddTag( "p", descr );
01100       }
01101     }
01102   }
01103 
01104   if( !descr.isEmpty() ) {
01105     html += "<p>";
01106     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01107     html += "<tr><td><center>" +
01108             htmlAddTag( "u", i18n( "Description:" ) ) +
01109             "</center></td></tr>";
01110     html += "<tr><td>" + descr + "</td></tr>";
01111     html += "</table>";
01112   }
01113 
01114   if ( !comments.isEmpty() ) {
01115     html += "<p>";
01116     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01117     html += "<tr><td><center>" +
01118             htmlAddTag( "u", i18n( "Comments:" ) ) +
01119             "</center></td></tr>";
01120     html += "<tr><td>";
01121     if ( comments.count() > 1 ) {
01122       html += "<ul>";
01123       for ( int i=0; i < comments.count(); ++i ) {
01124         html += "<li>" + comments[i] + "</li>";
01125       }
01126       html += "</ul>";
01127     } else {
01128       html += comments[0];
01129     }
01130     html += "</td></tr>";
01131     html += "</table>";
01132   }
01133   return html;
01134 }
01135 
01136 static QString invitationDetailsEvent( Event *event, bool noHtmlMode, KDateTime::Spec spec )
01137 {
01138   // Invitation details are formatted into an HTML table
01139   if ( !event ) {
01140     return QString();
01141   }
01142 
01143   QString sSummary = i18n( "Summary unspecified" );
01144   if ( !event->summary().isEmpty() ) {
01145     if ( !event->summaryIsRich() ) {
01146       sSummary = Qt::escape( event->summary() );
01147     } else {
01148       sSummary = event->richSummary();
01149       if ( noHtmlMode ) {
01150         sSummary = cleanHtml( sSummary );
01151       }
01152     }
01153   }
01154 
01155   QString sLocation = i18n( "Location unspecified" );
01156   if ( !event->location().isEmpty() ) {
01157     if ( !event->locationIsRich() ) {
01158       sLocation = Qt::escape( event->location() );
01159     } else {
01160       sLocation = event->richLocation();
01161       if ( noHtmlMode ) {
01162         sLocation = cleanHtml( sLocation );
01163       }
01164     }
01165   }
01166 
01167   QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
01168   QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
01169   html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01170 
01171   // Invitation summary & location rows
01172   html += invitationRow( i18n( "What:" ), sSummary );
01173   html += invitationRow( i18n( "Where:" ), sLocation );
01174 
01175   // If a 1 day event
01176   if ( event->dtStart().date() == event->dtEnd().date() ) {
01177     html += invitationRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
01178     if ( !event->allDay() ) {
01179       html += invitationRow( i18n( "Time:" ),
01180                              timeToString( event->dtStart(), true, spec ) +
01181                              " - " +
01182                              timeToString( event->dtEnd(), true, spec ) );
01183     }
01184   } else {
01185     html += invitationRow( i18nc( "starting date", "From:" ),
01186                            dateToString( event->dtStart(), false, spec ) );
01187     if ( !event->allDay() ) {
01188       html += invitationRow( i18nc( "starting time", "At:" ),
01189                              timeToString( event->dtStart(), true, spec ) );
01190     }
01191     if ( event->hasEndDate() ) {
01192       html += invitationRow( i18nc( "ending date", "To:" ),
01193                              dateToString( event->dtEnd(), false, spec ) );
01194       if ( !event->allDay() ) {
01195         html += invitationRow( i18nc( "ending time", "At:" ),
01196                                timeToString( event->dtEnd(), true, spec ) );
01197       }
01198     } else {
01199       html += invitationRow( i18nc( "ending date", "To:" ),
01200                              i18n( "no end date specified" ) );
01201     }
01202   }
01203 
01204   // Invitation Duration Row
01205   if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) {
01206     QString tmp;
01207     int secs = event->dtStart().secsTo( event->dtEnd() );
01208     int days = secs / 86400;
01209     if ( days > 0 ) {
01210       tmp += i18np( "1 day", "%1 days", days );
01211       tmp += ' ';
01212       secs -= ( days * 86400 );
01213     }
01214     int hours = secs / 3600;
01215     if ( hours > 0 ) {
01216       tmp += i18np( "1 hour", "%1 hours", hours );
01217       tmp += ' ';
01218       secs -= ( hours * 3600 );
01219     }
01220     int mins = secs / 60;
01221     if ( mins > 0 ) {
01222       tmp += i18np( "1 minute", "%1 minutes", mins );
01223       tmp += ' ';
01224     }
01225     html += invitationRow( i18n( "Duration:" ), tmp );
01226   }
01227 
01228   if ( event->recurs() ) {
01229     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
01230   }
01231 
01232   html += "</table></div>\n";
01233   html += invitationsDetailsIncidence( event, noHtmlMode );
01234 
01235   return html;
01236 }
01237 
01238 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode, KDateTime::Spec spec )
01239 {
01240   // To-do details are formatted into an HTML table
01241   if ( !todo ) {
01242     return QString();
01243   }
01244 
01245   QString sSummary = i18n( "Summary unspecified" );
01246   if ( !todo->summary().isEmpty() ) {
01247     if ( !todo->summaryIsRich() ) {
01248       sSummary = Qt::escape( todo->summary() );
01249     } else {
01250       sSummary = todo->richSummary();
01251       if ( noHtmlMode ) {
01252         sSummary = cleanHtml( sSummary );
01253       }
01254     }
01255   }
01256 
01257   QString sLocation = i18n( "Location unspecified" );
01258   if ( !todo->location().isEmpty() ) {
01259     if ( !todo->locationIsRich() ) {
01260       sLocation = Qt::escape( todo->location() );
01261     } else {
01262       sLocation = todo->richLocation();
01263       if ( noHtmlMode ) {
01264         sLocation = cleanHtml( sLocation );
01265       }
01266     }
01267   }
01268 
01269   QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
01270   QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
01271   html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01272 
01273   // Invitation summary & location rows
01274   html += invitationRow( i18n( "What:" ), sSummary );
01275   html += invitationRow( i18n( "Where:" ), sLocation );
01276 
01277   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01278     html += invitationRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
01279   }
01280   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01281     html += invitationRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
01282   } else {
01283     html += invitationRow( i18n( "Due Date:" ), i18nc( "no to-do due date", "None" ) );
01284   }
01285 
01286   html += "</table></div>\n";
01287   html += invitationsDetailsIncidence( todo, noHtmlMode );
01288 
01289   return html;
01290 }
01291 
01292 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode, KDateTime::Spec spec )
01293 {
01294   if ( !journal ) {
01295     return QString();
01296   }
01297 
01298   QString sSummary = i18n( "Summary unspecified" );
01299   QString sDescr = i18n( "Description unspecified" );
01300   if ( ! journal->summary().isEmpty() ) {
01301     sSummary = journal->richSummary();
01302     if ( noHtmlMode ) {
01303       sSummary = cleanHtml( sSummary );
01304     }
01305   }
01306   if ( ! journal->description().isEmpty() ) {
01307     sDescr = journal->richDescription();
01308     if ( noHtmlMode ) {
01309       sDescr = cleanHtml( sDescr );
01310     }
01311   }
01312   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01313   html += invitationRow( i18n( "Summary:" ), sSummary );
01314   html += invitationRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
01315   html += invitationRow( i18n( "Description:" ), sDescr );
01316   html += "</table>\n";
01317   html += invitationsDetailsIncidence( journal, noHtmlMode );
01318 
01319   return html;
01320 }
01321 
01322 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool noHtmlMode, KDateTime::Spec spec )
01323 {
01324   Q_UNUSED( noHtmlMode );
01325 
01326   if ( !fb ) {
01327     return QString();
01328   }
01329 
01330   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01331   html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() );
01332   html += invitationRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
01333   html += invitationRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
01334 
01335   html += "<tr><td colspan=2><hr></td></tr>\n";
01336   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01337 
01338   QList<Period> periods = fb->busyPeriods();
01339   QList<Period>::iterator it;
01340   for ( it = periods.begin(); it != periods.end(); ++it ) {
01341     Period per = *it;
01342     if ( per.hasDuration() ) {
01343       int dur = per.duration().asSeconds();
01344       QString cont;
01345       if ( dur >= 3600 ) {
01346         cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
01347         dur %= 3600;
01348       }
01349       if ( dur >= 60 ) {
01350         cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
01351         dur %= 60;
01352       }
01353       if ( dur > 0 ) {
01354         cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
01355       }
01356       html += invitationRow(
01357         QString(), i18nc( "startDate for duration", "%1 for %2",
01358                           KGlobal::locale()->formatDateTime(
01359                             per.start().dateTime(), KLocale::LongDate ), cont ) );
01360     } else {
01361       QString cont;
01362       if ( per.start().date() == per.end().date() ) {
01363         cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
01364                       KGlobal::locale()->formatDate( per.start().date() ),
01365                       KGlobal::locale()->formatTime( per.start().time() ),
01366                       KGlobal::locale()->formatTime( per.end().time() ) );
01367       } else {
01368         cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
01369                       KGlobal::locale()->formatDateTime(
01370                         per.start().dateTime(), KLocale::LongDate ),
01371                       KGlobal::locale()->formatDateTime(
01372                         per.end().dateTime(), KLocale::LongDate ) );
01373       }
01374 
01375       html += invitationRow( QString(), cont );
01376     }
01377   }
01378 
01379   html += "</table>\n";
01380   return html;
01381 }
01382 
01383 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
01384 {
01385   if ( !msg || !event ) {
01386     return QString();
01387   }
01388 
01389   switch ( msg->method() ) {
01390   case iTIPPublish:
01391     return i18n( "This event has been published" );
01392   case iTIPRequest:
01393     if ( event->revision() > 0 ) {
01394       return i18n( "This invitation has been updated" );
01395     }
01396     if ( iamOrganizer( event ) ) {
01397       return i18n( "I sent this invitation" );
01398     } else {
01399       if ( !event->organizer().fullName().isEmpty() ) {
01400         return i18n( "You received an invitation from %1",
01401                      event->organizer().fullName() );
01402       } else {
01403         return i18n( "You received an invitation" );
01404       }
01405     }
01406   case iTIPRefresh:
01407     return i18n( "This invitation was refreshed" );
01408   case iTIPCancel:
01409     return i18n( "This invitation has been canceled" );
01410   case iTIPAdd:
01411     return i18n( "Addition to the invitation" );
01412   case iTIPReply:
01413   {
01414     Attendee::List attendees = event->attendees();
01415     if( attendees.count() == 0 ) {
01416       kDebug() << "No attendees in the iCal reply!";
01417       return QString();
01418     }
01419     if ( attendees.count() != 1 ) {
01420       kDebug() << "Warning: attendeecount in the reply should be 1"
01421                << "but is" << attendees.count();
01422     }
01423     Attendee *attendee = *attendees.begin();
01424     QString attendeeName = attendee->name();
01425     if ( attendeeName.isEmpty() ) {
01426       attendeeName = attendee->email();
01427     }
01428     if ( attendeeName.isEmpty() ) {
01429       attendeeName = i18n( "Sender" );
01430     }
01431 
01432     QString delegatorName, dummy;
01433     KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
01434     if ( delegatorName.isEmpty() ) {
01435       delegatorName = attendee->delegator();
01436     }
01437 
01438     switch( attendee->status() ) {
01439     case Attendee::NeedsAction:
01440       return i18n( "%1 indicates this invitation still needs some action", attendeeName );
01441     case Attendee::Accepted:
01442       if ( delegatorName.isEmpty() ) {
01443         return i18n( "%1 accepts this invitation", attendeeName );
01444       } else {
01445         return i18n( "%1 accepts this invitation on behalf of %2",
01446                      attendeeName, delegatorName );
01447       }
01448     case Attendee::Tentative:
01449       if ( delegatorName.isEmpty() ) {
01450         return i18n( "%1 tentatively accepts this invitation", attendeeName );
01451       } else {
01452         return i18n( "%1 tentatively accepts this invitation on behalf of %2",
01453                      attendeeName, delegatorName );
01454       }
01455     case Attendee::Declined:
01456       if ( delegatorName.isEmpty() ) {
01457         return i18n( "%1 declines this invitation", attendeeName );
01458       } else {
01459         return i18n( "%1 declines this invitation on behalf of %2",
01460                      attendeeName, delegatorName );
01461       }
01462     case Attendee::Delegated:
01463     {
01464       QString delegate, dummy;
01465       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01466       if ( delegate.isEmpty() ) {
01467         delegate = attendee->delegate();
01468       }
01469       if ( !delegate.isEmpty() ) {
01470         return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
01471       } else {
01472         return i18n( "%1 has delegated this invitation", attendeeName );
01473       }
01474     }
01475     case Attendee::Completed:
01476       return i18n( "This invitation is now completed" );
01477     case Attendee::InProcess:
01478       return i18n( "%1 is still processing the invitation", attendeeName );
01479     case Attendee::None:
01480       return i18n( "Unknown response to this invitation" );
01481     }
01482     break;
01483   }
01484   case iTIPCounter:
01485     return i18n( "Sender makes this counter proposal" );
01486   case iTIPDeclineCounter:
01487     return i18n( "Sender declines the counter proposal" );
01488   case iTIPNoMethod:
01489     return i18n( "Error: Event iTIP message with unknown method" );
01490   }
01491   kError() << "encountered an iTIP method that we do not support";
01492   return QString();
01493 }
01494 
01495 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01496 {
01497   if ( !msg || !todo ) {
01498     return QString();
01499   }
01500 
01501   switch ( msg->method() ) {
01502   case iTIPPublish:
01503     return i18n( "This to-do has been published" );
01504   case iTIPRequest:
01505     if ( todo->revision() > 0 ) {
01506       return i18n( "This to-do has been updated" );
01507     } else {
01508       return i18n( "You have been assigned this to-do" );
01509     }
01510   case iTIPRefresh:
01511     return i18n( "This to-do was refreshed" );
01512   case iTIPCancel:
01513     return i18n( "This to-do was canceled" );
01514   case iTIPAdd:
01515     return i18n( "Addition to the to-do" );
01516   case iTIPReply:
01517   {
01518     Attendee::List attendees = todo->attendees();
01519     if ( attendees.count() == 0 ) {
01520       kDebug() << "No attendees in the iCal reply!";
01521       return QString();
01522     }
01523     if ( attendees.count() != 1 ) {
01524       kDebug() << "Warning: attendeecount in the reply should be 1"
01525                << "but is" << attendees.count();
01526     }
01527     Attendee *attendee = *attendees.begin();
01528 
01529     switch( attendee->status() ) {
01530     case Attendee::NeedsAction:
01531       return i18n( "Sender indicates this to-do assignment still needs some action" );
01532     case Attendee::Accepted:
01533       return i18n( "Sender accepts this to-do" );
01534     case Attendee::Tentative:
01535       return i18n( "Sender tentatively accepts this to-do" );
01536     case Attendee::Declined:
01537       return i18n( "Sender declines this to-do" );
01538     case Attendee::Delegated:
01539     {
01540       QString delegate, dummy;
01541       KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
01542       if ( delegate.isEmpty() ) {
01543         delegate = attendee->delegate();
01544       }
01545       if ( !delegate.isEmpty() ) {
01546         return i18n( "Sender has delegated this request for the to-do to %1", delegate );
01547       } else {
01548         return i18n( "Sender has delegated this request for the to-do " );
01549       }
01550     }
01551     case Attendee::Completed:
01552       return i18n( "The request for this to-do is now completed" );
01553     case Attendee::InProcess:
01554       return i18n( "Sender is still processing the invitation" );
01555     case Attendee::None:
01556       return i18n( "Unknown response to this to-do" );
01557     }
01558     break;
01559   }
01560   case iTIPCounter:
01561     return i18n( "Sender makes this counter proposal" );
01562   case iTIPDeclineCounter:
01563     return i18n( "Sender declines the counter proposal" );
01564   case iTIPNoMethod:
01565     return i18n( "Error: To-do iTIP message with unknown method" );
01566   }
01567   kError() << "encountered an iTIP method that we do not support";
01568   return QString();
01569 }
01570 
01571 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01572 {
01573   if ( !msg || !journal ) {
01574     return QString();
01575   }
01576 
01577   switch ( msg->method() ) {
01578   case iTIPPublish:
01579     return i18n( "This journal has been published" );
01580   case iTIPRequest:
01581     return i18n( "You have been assigned this journal" );
01582   case iTIPRefresh:
01583     return i18n( "This journal was refreshed" );
01584   case iTIPCancel:
01585     return i18n( "This journal was canceled" );
01586   case iTIPAdd:
01587     return i18n( "Addition to the journal" );
01588   case iTIPReply:
01589   {
01590     Attendee::List attendees = journal->attendees();
01591     if ( attendees.count() == 0 ) {
01592       kDebug() << "No attendees in the iCal reply!";
01593       return QString();
01594     }
01595     if( attendees.count() != 1 ) {
01596       kDebug() << "Warning: attendeecount in the reply should be 1 "
01597                << "but is " << attendees.count();
01598     }
01599     Attendee *attendee = *attendees.begin();
01600 
01601     switch( attendee->status() ) {
01602     case Attendee::NeedsAction:
01603       return i18n( "Sender indicates this journal assignment still needs some action" );
01604     case Attendee::Accepted:
01605       return i18n( "Sender accepts this journal" );
01606     case Attendee::Tentative:
01607       return i18n( "Sender tentatively accepts this journal" );
01608     case Attendee::Declined:
01609       return i18n( "Sender declines this journal" );
01610     case Attendee::Delegated:
01611       return i18n( "Sender has delegated this request for the journal" );
01612     case Attendee::Completed:
01613       return i18n( "The request for this journal is now completed" );
01614     case Attendee::InProcess:
01615       return i18n( "Sender is still processing the invitation" );
01616     case Attendee::None:
01617       return i18n( "Unknown response to this journal" );
01618     }
01619     break;
01620   }
01621   case iTIPCounter:
01622     return i18n( "Sender makes this counter proposal" );
01623   case iTIPDeclineCounter:
01624     return i18n( "Sender declines the counter proposal" );
01625   case iTIPNoMethod:
01626     return i18n( "Error: Journal iTIP message with unknown method" );
01627   }
01628   kError() << "encountered an iTIP method that we do not support";
01629   return QString();
01630 }
01631 
01632 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01633 {
01634   if ( !msg || !fb ) {
01635     return QString();
01636   }
01637 
01638   switch ( msg->method() ) {
01639   case iTIPPublish:
01640     return i18n( "This free/busy list has been published" );
01641   case iTIPRequest:
01642     return i18n( "The free/busy list has been requested" );
01643   case iTIPRefresh:
01644     return i18n( "This free/busy list was refreshed" );
01645   case iTIPCancel:
01646     return i18n( "This free/busy list was canceled" );
01647   case iTIPAdd:
01648     return i18n( "Addition to the free/busy list" );
01649   case iTIPReply:
01650     return i18n( "Reply to the free/busy list" );
01651   case iTIPCounter:
01652     return i18n( "Sender makes this counter proposal" );
01653   case iTIPDeclineCounter:
01654     return i18n( "Sender declines the counter proposal" );
01655   case iTIPNoMethod:
01656     return i18n( "Error: Free/Busy iTIP message with unknown method" );
01657   }
01658   kError() << "encountered an iTIP method that we do not support";
01659   return QString();
01660 }
01661 //@endcond
01662 
01663 static QString invitationAttendees( Incidence *incidence )
01664 {
01665   QString tmpStr;
01666   if ( !incidence ) {
01667     return tmpStr;
01668   }
01669 
01670   tmpStr += i18n( "Invitation List" );
01671 
01672   int count=0;
01673   Attendee::List attendees = incidence->attendees();
01674   if ( !attendees.isEmpty() ) {
01675 
01676     Attendee::List::ConstIterator it;
01677     for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
01678       Attendee *a = *it;
01679       if ( !iamAttendee( a ) ) {
01680         count++;
01681         if ( count == 1 ) {
01682           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
01683         }
01684         tmpStr += "<tr>";
01685         tmpStr += "<td>";
01686         tmpStr += invitationPerson( a->email(), a->name(), QString() );
01687         if ( !a->delegator().isEmpty() ) {
01688           tmpStr += i18n( " (delegated by %1)", a->delegator() );
01689         }
01690         if ( !a->delegate().isEmpty() ) {
01691           tmpStr += i18n( " (delegated to %1)", a->delegate() );
01692         }
01693         tmpStr += "</td>";
01694         tmpStr += "<td>" + a->statusStr() + "</td>";
01695         tmpStr += "</tr>";
01696       }
01697     }
01698   }
01699   if ( count ) {
01700     tmpStr += "</table>";
01701   } else {
01702     tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>";
01703   }
01704 
01705   return tmpStr;
01706 }
01707 
01708 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
01709 {
01710   QString tmpStr;
01711   if ( !incidence ) {
01712     return tmpStr;
01713   }
01714 
01715   Attachment::List attachments = incidence->attachments();
01716   if ( !attachments.isEmpty() ) {
01717     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
01718 
01719     Attachment::List::ConstIterator it;
01720     for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
01721       Attachment *a = *it;
01722       tmpStr += "<li>";
01723       // Attachment icon
01724       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
01725       const QString iconStr = ( mimeType ?
01726                                 mimeType->iconName( a->uri() ) :
01727                                 QString( "application-octet-stream" ) );
01728       const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
01729       if ( !iconPath.isEmpty() ) {
01730         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
01731       }
01732       tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
01733       tmpStr += "</li>";
01734     }
01735     tmpStr += "</ol>";
01736   }
01737 
01738   return tmpStr;
01739 }
01740 
01741 //@cond PRIVATE
01742 class KCal::IncidenceFormatter::ScheduleMessageVisitor
01743   : public IncidenceBase::Visitor
01744 {
01745   public:
01746     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01747     bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01748     {
01749       mMessage = msg;
01750       return incidence->accept( *this );
01751     }
01752     QString result() const { return mResult; }
01753 
01754   protected:
01755     QString mResult;
01756     ScheduleMessage *mMessage;
01757 };
01758 
01759 class KCal::IncidenceFormatter::InvitationHeaderVisitor :
01760       public IncidenceFormatter::ScheduleMessageVisitor
01761 {
01762   protected:
01763     bool visit( Event *event )
01764     {
01765       mResult = invitationHeaderEvent( event, mMessage );
01766       return !mResult.isEmpty();
01767     }
01768     bool visit( Todo *todo )
01769     {
01770       mResult = invitationHeaderTodo( todo, mMessage );
01771       return !mResult.isEmpty();
01772     }
01773     bool visit( Journal *journal )
01774     {
01775       mResult = invitationHeaderJournal( journal, mMessage );
01776       return !mResult.isEmpty();
01777     }
01778     bool visit( FreeBusy *fb )
01779     {
01780       mResult = invitationHeaderFreeBusy( fb, mMessage );
01781       return !mResult.isEmpty();
01782     }
01783 };
01784 
01785 class KCal::IncidenceFormatter::InvitationBodyVisitor
01786   : public IncidenceFormatter::ScheduleMessageVisitor
01787 {
01788   public:
01789     InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
01790       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
01791 
01792   protected:
01793     bool visit( Event *event )
01794     {
01795       mResult = invitationDetailsEvent( event, mNoHtmlMode, mSpec );
01796       return !mResult.isEmpty();
01797     }
01798     bool visit( Todo *todo )
01799     {
01800       mResult = invitationDetailsTodo( todo, mNoHtmlMode, mSpec );
01801       return !mResult.isEmpty();
01802     }
01803     bool visit( Journal *journal )
01804     {
01805       mResult = invitationDetailsJournal( journal, mNoHtmlMode, mSpec );
01806       return !mResult.isEmpty();
01807     }
01808     bool visit( FreeBusy *fb )
01809     {
01810       mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode, mSpec );
01811       return !mResult.isEmpty();
01812     }
01813 
01814   private:
01815     bool mNoHtmlMode;
01816     KDateTime::Spec mSpec;
01817 };
01818 //@endcond
01819 
01820 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
01821 {
01822   return id;
01823 }
01824 
01825 //@cond PRIVATE
01826 class IncidenceFormatter::IncidenceCompareVisitor
01827   : public IncidenceBase::Visitor
01828 {
01829   public:
01830     IncidenceCompareVisitor() : mExistingIncidence( 0 ) {}
01831     bool act( IncidenceBase *incidence, Incidence *existingIncidence )
01832     {
01833       if ( !existingIncidence ) {
01834         return false;
01835       }
01836       Incidence *inc = dynamic_cast<Incidence *>( incidence );
01837       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() ) {
01838         return false;
01839       }
01840       mExistingIncidence = existingIncidence;
01841       return incidence->accept( *this );
01842     }
01843 
01844     QString result() const
01845     {
01846       if ( mChanges.isEmpty() ) {
01847         return QString();
01848       }
01849       QString html = "<div align=\"left\"><ul><li>";
01850       html += mChanges.join( "</li><li>" );
01851       html += "</li><ul></div>";
01852       return html;
01853     }
01854 
01855   protected:
01856     bool visit( Event *event )
01857     {
01858       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01859       compareIncidences( event, mExistingIncidence );
01860       return !mChanges.isEmpty();
01861     }
01862     bool visit( Todo *todo )
01863     {
01864       compareIncidences( todo, mExistingIncidence );
01865       return !mChanges.isEmpty();
01866     }
01867     bool visit( Journal *journal )
01868     {
01869       compareIncidences( journal, mExistingIncidence );
01870       return !mChanges.isEmpty();
01871     }
01872     bool visit( FreeBusy *fb )
01873     {
01874       Q_UNUSED( fb );
01875       return !mChanges.isEmpty();
01876     }
01877 
01878   private:
01879     void compareEvents( Event *newEvent, Event *oldEvent )
01880     {
01881       if ( !oldEvent || !newEvent ) {
01882         return;
01883       }
01884       if ( oldEvent->dtStart() != newEvent->dtStart() ||
01885            oldEvent->allDay() != newEvent->allDay() ) {
01886         mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
01887                           eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01888       }
01889       if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01890            oldEvent->allDay() != newEvent->allDay() ) {
01891         mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
01892                           eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01893       }
01894     }
01895 
01896     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01897     {
01898       if ( !oldInc || !newInc ) {
01899         return;
01900       }
01901 
01902       if ( oldInc->summary() != newInc->summary() ) {
01903         mChanges += i18n( "The summary has been changed to: \"%1\"",
01904                           newInc->richSummary() );
01905       }
01906 
01907       if ( oldInc->location() != newInc->location() ) {
01908         mChanges += i18n( "The location has been changed to: \"%1\"",
01909                           newInc->richLocation() );
01910       }
01911 
01912       if ( oldInc->description() != newInc->description() ) {
01913         mChanges += i18n( "The description has been changed to: \"%1\"",
01914                           newInc->richDescription() );
01915       }
01916 
01917       Attendee::List oldAttendees = oldInc->attendees();
01918       Attendee::List newAttendees = newInc->attendees();
01919       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01920             it != newAttendees.constEnd(); ++it ) {
01921         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01922         if ( !oldAtt ) {
01923           mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01924         } else {
01925           if ( oldAtt->status() != (*it)->status() ) {
01926             mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01927                               (*it)->fullName(), (*it)->statusStr() );
01928           }
01929         }
01930       }
01931 
01932       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01933             it != oldAttendees.constEnd(); ++it ) {
01934         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01935         if ( !newAtt ) {
01936           mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01937         }
01938       }
01939     }
01940 
01941   private:
01942     Incidence *mExistingIncidence;
01943     QStringList mChanges;
01944 };
01945 //@endcond
01946 
01947 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01948 {
01949   if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
01950     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
01951                   arg( generateLinkURL( id ), text );
01952     return res;
01953   } else {
01954     // draw the attachment links in non-bold face
01955     QString res = QString( "<a href=\"%1\">%2</a>" ).
01956                   arg( generateLinkURL( id ), text );
01957     return res;
01958   }
01959 }
01960 
01961 // Check if the given incidence is likely one that we own instead one from
01962 // a shared calendar (Kolab-specific)
01963 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01964 {
01965   CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
01966   if ( !cal || !incidence ) {
01967     return true;
01968   }
01969   ResourceCalendar *res = cal->resource( incidence );
01970   if ( !res ) {
01971     return true;
01972   }
01973   const QString subRes = res->subresourceIdentifier( incidence );
01974   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01975     return false;
01976   }
01977   return true;
01978 }
01979 
01980 Calendar *InvitationFormatterHelper::calendar() const
01981 {
01982   return 0;
01983 }
01984 
01985 static QString formatICalInvitationHelper( QString invitation,
01986                                            Calendar *mCalendar,
01987                                            InvitationFormatterHelper *helper,
01988                                            bool noHtmlMode,
01989                                            KDateTime::Spec spec )
01990 {
01991   if ( invitation.isEmpty() ) {
01992     return QString();
01993   }
01994 
01995   ICalFormat format;
01996   // parseScheduleMessage takes the tz from the calendar,
01997   // no need to set it manually here for the format!
01998   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01999 
02000   if( !msg ) {
02001     kDebug() << "Failed to parse the scheduling message";
02002     Q_ASSERT( format.exception() );
02003     kDebug() << format.exception()->message();
02004     return QString();
02005   }
02006 
02007   IncidenceBase *incBase = msg->event();
02008   incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
02009 
02010   // Determine if this incidence is in my calendar (and owned by me)
02011   Incidence *existingIncidence = 0;
02012   if ( incBase && helper->calendar() ) {
02013     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02014     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02015       existingIncidence = 0;
02016     }
02017     if ( !existingIncidence ) {
02018       const Incidence::List list = helper->calendar()->incidences();
02019       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02020         if ( (*it)->schedulingID() == incBase->uid() &&
02021              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02022           existingIncidence = *it;
02023           break;
02024         }
02025       }
02026     }
02027   }
02028 
02029   // First make the text of the message
02030   QString html;
02031   html += "<div align=\"center\" style=\"border:solid 1px;\">";
02032 
02033   IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
02034   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02035   if ( !headerVisitor.act( incBase, msg ) ) {
02036     return QString();
02037   }
02038   html += htmlAddTag( "h3", headerVisitor.result() );
02039 
02040   IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02041   if ( !bodyVisitor.act( incBase, msg ) ) {
02042     return QString();
02043   }
02044   html += bodyVisitor.result();
02045 
02046   if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well?
02047     IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
02048     if ( compareVisitor.act( incBase, existingIncidence ) ) {
02049       html +=
02050         i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
02051       html += compareVisitor.result();
02052     }
02053   }
02054 
02055   Incidence *inc = dynamic_cast<Incidence*>( incBase );
02056 
02057   // determine if I am the organizer for this invitation
02058   bool myInc = iamOrganizer( inc );
02059 
02060   // determine if the invitation response has already been recorded
02061   bool rsvpRec = false;
02062   Attendee *ea = 0;
02063   if ( !myInc ) {
02064     if ( existingIncidence ) {
02065       ea = findMyAttendee( existingIncidence );
02066     }
02067     if ( ea && ( ea->status() == Attendee::Accepted || ea->status() == Attendee::Declined ) ) {
02068       rsvpRec = true;
02069     }
02070   }
02071 
02072   // Print if RSVP needed, not-needed, or response already recorded
02073   bool rsvpReq = rsvpRequested( inc );
02074   if ( !myInc ) {
02075     html += "<br/>";
02076     html += "<i><u>";
02077     if ( rsvpRec && ( inc && inc->revision() == 0 ) ) {
02078       html += i18n( "Your response has already been recorded [%1]",
02079                     ea->statusStr() );
02080       rsvpReq = false;
02081     } else if ( msg->method() == iTIPCancel ) {
02082       html += i18n( "This invitation was declined" );
02083     } else if ( msg->method() == iTIPAdd ) {
02084       html += i18n( "This invitation was accepted" );
02085     } else {
02086       html += rsvpRequestedStr( rsvpReq );
02087     }
02088     html += "</u></i><br>";
02089   }
02090 
02091   // Add groupware links
02092 
02093   html += "<p>";
02094   html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
02095 
02096   const QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
02097   const QString tdClose = "</td>";
02098   switch ( msg->method() ) {
02099     case iTIPPublish:
02100     case iTIPRequest:
02101     case iTIPRefresh:
02102     case iTIPAdd:
02103     {
02104       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
02105         if ( inc->type() == "Todo" ) {
02106           html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
02107         } else {
02108           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
02109         }
02110       }
02111 
02112       if ( !myInc ) {
02113         if ( rsvpReq ) {
02114           // Accept
02115           html += tdOpen;
02116           html += helper->makeLink( "accept",
02117                                     i18nc( "accept invitation",
02118                                            "Accept" ) );
02119           html += tdClose;
02120 
02121           // Accept conditionally
02122           html += tdOpen;
02123           html += helper->makeLink( "accept_conditionally",
02124                                     i18nc( "Accept invitation conditionally",
02125                                            "Accept cond." ) );
02126           html += tdClose;
02127         }
02128 
02129         if ( rsvpReq ) {
02130           // Counter proposal
02131           html += tdOpen;
02132           html += helper->makeLink( "counter",
02133                                     i18nc( "invitation counter proposal",
02134                                            "Counter proposal" ) );
02135           html += tdClose;
02136         }
02137 
02138         if ( rsvpReq ) {
02139           // Decline
02140           html += tdOpen;
02141           html += helper->makeLink( "decline",
02142                                     i18nc( "decline invitation",
02143                                            "Decline" ) );
02144           html += tdClose;
02145         }
02146 
02147         if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02148           // Delegate
02149           html += tdOpen;
02150           html += helper->makeLink( "delegate",
02151                                     i18nc( "delegate inviation to another",
02152                                            "Delegate" ) );
02153           html += tdClose;
02154 
02155           // Forward
02156           html += tdOpen;
02157           html += helper->makeLink( "forward",
02158                                     i18nc( "forward request to another",
02159                                            "Forward" ) );
02160           html += tdClose;
02161 
02162           // Check calendar
02163           if ( incBase && incBase->type() == "Event" ) {
02164             html += tdOpen;
02165             html += helper->makeLink( "check_calendar",
02166                                       i18nc( "look for scheduling conflicts",
02167                                              "Check my calendar" ) );
02168             html += tdClose;
02169           }
02170         }
02171       }
02172       break;
02173     }
02174 
02175     case iTIPCancel:
02176       // Remove invitation
02177       html += tdOpen;
02178       if ( inc->type() == "Todo" ) {
02179         html += helper->makeLink( "cancel",
02180                                   i18n( "Remove invitation from my to-do list" ) );
02181       } else {
02182         html += helper->makeLink( "cancel",
02183                                   i18n( "Remove invitation from my calendar" ) );
02184       }
02185       html += tdClose;
02186       break;
02187 
02188     case iTIPReply:
02189     {
02190       // Record invitation response
02191       Attendee *a = 0;
02192       Attendee *ea = 0;
02193       if ( inc ) {
02194         a = inc->attendees().first();
02195         if ( a && helper->calendar() ) {
02196           ea = findAttendee( existingIncidence, a->email() );
02197         }
02198       }
02199       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02200         html += tdOpen;
02201         html += htmlAddTag( "i", i18n( "The response has already been recorded" ) );
02202         html += tdClose;
02203       } else {
02204         if ( inc ) {
02205           if ( inc->type() == "Todo" ) {
02206             html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
02207           } else {
02208             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02209           }
02210         }
02211       }
02212       break;
02213     }
02214 
02215     case iTIPCounter:
02216       // Counter proposal
02217       html += tdOpen;
02218       html += helper->makeLink( "accept_counter", i18n( "Accept" ) );
02219       html += tdClose;
02220 
02221       html += tdOpen;
02222       html += helper->makeLink( "decline_counter", i18n( "Decline" ) );
02223       html += tdClose;
02224 
02225       html += tdOpen;
02226       html += helper->makeLink( "check_calendar", i18n( "Check my calendar" ) );
02227       html += tdClose;
02228       break;
02229 
02230     case iTIPDeclineCounter:
02231     case iTIPNoMethod:
02232       break;
02233   }
02234 
02235   // close the groupware table
02236   html += "</tr></table>";
02237 
02238   // Add the attendee list if I am the organizer
02239   if ( myInc && helper->calendar() ) {
02240     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02241   }
02242 
02243   // close the top-level
02244   html += "</div>";
02245 
02246   // Add the attachment list
02247   html += invitationAttachments( helper, inc );
02248 
02249   return html;
02250 }
02251 //@endcond
02252 
02253 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02254                                                   Calendar *mCalendar,
02255                                                   InvitationFormatterHelper *helper )
02256 {
02257   return formatICalInvitationHelper( invitation, mCalendar, helper, false,
02258                                      KSystemTimeZones::local() );
02259 }
02260 
02261 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02262                                                         Calendar *mCalendar,
02263                                                         InvitationFormatterHelper *helper )
02264 {
02265   return formatICalInvitationHelper( invitation, mCalendar, helper, true,
02266                                      KSystemTimeZones::local() );
02267 }
02268 
02269 /*******************************************************************
02270  *  Helper functions for the Incidence tooltips
02271  *******************************************************************/
02272 
02273 //@cond PRIVATE
02274 class KCal::IncidenceFormatter::ToolTipVisitor
02275   : public IncidenceBase::Visitor
02276 {
02277   public:
02278     ToolTipVisitor()
02279       : mCalendar( 0 ), mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
02280 
02281     bool act( Calendar *calendar, IncidenceBase *incidence,
02282               const QDate &date=QDate(), bool richText=true,
02283               KDateTime::Spec spec=KDateTime::Spec() )
02284     {
02285       mCalendar = calendar;
02286       mDate = date;
02287       mRichText = richText;
02288       mSpec = spec;
02289       mResult = "";
02290       return incidence ? incidence->accept( *this ) : false;
02291     }
02292     QString result() const { return mResult; }
02293 
02294   protected:
02295     bool visit( Event *event );
02296     bool visit( Todo *todo );
02297     bool visit( Journal *journal );
02298     bool visit( FreeBusy *fb );
02299 
02300     QString dateRangeText( Event *event, const QDate &date );
02301     QString dateRangeText( Todo *todo, const QDate &date );
02302     QString dateRangeText( Journal *journal );
02303     QString dateRangeText( FreeBusy *fb );
02304 
02305     QString generateToolTip( Incidence *incidence, QString dtRangeText );
02306 
02307   protected:
02308     Calendar *mCalendar;
02309     QDate mDate;
02310     bool mRichText;
02311     KDateTime::Spec mSpec;
02312     QString mResult;
02313 };
02314 
02315 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
02316 {
02317   //FIXME: support mRichText==false
02318   QString ret;
02319   QString tmp;
02320 
02321   KDateTime startDt = event->dtStart();
02322   KDateTime endDt = event->dtEnd();
02323   if ( event->recurs() ) {
02324     if ( date.isValid() ) {
02325       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
02326       int diffDays = startDt.daysTo( kdt );
02327       kdt = kdt.addSecs( -1 );
02328       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
02329       if ( event->hasEndDate() ) {
02330         endDt = endDt.addDays( diffDays );
02331         if ( startDt > endDt ) {
02332           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
02333           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
02334         }
02335       }
02336     }
02337   }
02338 
02339   if ( event->isMultiDay() ) {
02340     tmp = dateToString( startDt, true, mSpec );
02341     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
02342 
02343     tmp = dateToString( endDt, true, mSpec );
02344     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
02345 
02346   } else {
02347 
02348     ret += "<br>" +
02349            i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
02350     if ( !event->allDay() ) {
02351       const QString dtStartTime = timeToString( startDt, true, mSpec );
02352       const QString dtEndTime = timeToString( endDt, true, mSpec );
02353       if ( dtStartTime == dtEndTime ) {
02354         // to prevent 'Time: 17:00 - 17:00'
02355         tmp = "<br>" +
02356               i18nc( "time for event", "<i>Time:</i> %1",
02357                      dtStartTime );
02358       } else {
02359         tmp = "<br>" +
02360               i18nc( "time range for event",
02361                      "<i>Time:</i> %1 - %2",
02362                      dtStartTime, dtEndTime );
02363       }
02364       ret += tmp;
02365     }
02366   }
02367   return ret.replace( ' ', "&nbsp;" );
02368 }
02369 
02370 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
02371 {
02372   //FIXME: support mRichText==false
02373   QString ret;
02374   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02375     KDateTime startDt = todo->dtStart();
02376     if ( todo->recurs() ) {
02377       if ( date.isValid() ) {
02378         startDt.setDate( date );
02379       }
02380     }
02381     ret += "<br>" +
02382            i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
02383   }
02384 
02385   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02386     KDateTime dueDt = todo->dtDue();
02387     if ( todo->recurs() ) {
02388       if ( date.isValid() ) {
02389         dueDt.addDays( todo->dtDue().date().daysTo( date ) );
02390       }
02391     }
02392     ret += "<br>" +
02393            i18n( "<i>Due:</i> %1",
02394                  dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
02395   }
02396   if ( todo->isCompleted() ) {
02397     ret += "<br>" +
02398            i18n( "<i>Completed:</i> %1", todo->completedStr() );
02399   } else {
02400     ret += "<br>" +
02401            i18nc( "percent complete", "%1% completed", todo->percentComplete() );
02402   }
02403 
02404   return ret.replace( ' ', "&nbsp;" );
02405 }
02406 
02407 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
02408 {
02409   //FIXME: support mRichText==false
02410   QString ret;
02411   if ( journal->dtStart().isValid() ) {
02412     ret += "<br>" +
02413            i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
02414   }
02415   return ret.replace( ' ', "&nbsp;" );
02416 }
02417 
02418 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
02419 {
02420   //FIXME: support mRichText==false
02421   QString ret;
02422   ret = "<br>" +
02423         i18n( "<i>Period start:</i> %1",
02424               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
02425   ret += "<br>" +
02426          i18n( "<i>Period start:</i> %1",
02427                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
02428   return ret.replace( ' ', "&nbsp;" );
02429 }
02430 
02431 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
02432 {
02433   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
02434   return !mResult.isEmpty();
02435 }
02436 
02437 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
02438 {
02439   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
02440   return !mResult.isEmpty();
02441 }
02442 
02443 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
02444 {
02445   mResult = generateToolTip( journal, dateRangeText( journal ) );
02446   return !mResult.isEmpty();
02447 }
02448 
02449 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
02450 {
02451   //FIXME: support mRichText==false
02452   mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
02453   mResult += dateRangeText( fb );
02454   mResult += "</qt>";
02455   return !mResult.isEmpty();
02456 }
02457 
02458 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
02459                                                              QString dtRangeText )
02460 {
02461   //FIXME: support mRichText==false
02462   if ( !incidence ) {
02463     return QString();
02464   }
02465 
02466   QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
02467   if ( mCalendar ) {
02468     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
02469     if ( !calStr.isEmpty() ) {
02470       tmp += "<br>" + i18n( "<i>Calendar:</i> %1", calStr );
02471     }
02472   }
02473 
02474   tmp += dtRangeText;
02475 
02476   if ( !incidence->location().isEmpty() ) {
02477     tmp += "<br>" +
02478            i18n( "<i>Location:</i> %1", incidence->richLocation() );
02479   }
02480 
02481   if ( !incidence->description().isEmpty() ) {
02482     QString desc( incidence->description() );
02483     if ( !incidence->descriptionIsRich() ) {
02484       if ( desc.length() > 120 ) {
02485         desc = desc.left( 120 ) + "...";
02486       }
02487       desc = Qt::escape( desc ).replace( '\n', "<br>" );
02488     } else {
02489       // TODO: truncate the description when it's rich text
02490     }
02491     tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
02492   }
02493   tmp += "</qt>";
02494   return tmp;
02495 }
02496 //@endcond
02497 
02498 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence,
02499                                            bool richText )
02500 {
02501   return toolTipStr( 0, incidence, QDate(), richText, KDateTime::Spec() );
02502 }
02503 
02504 QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence,
02505                                         bool richText, KDateTime::Spec spec )
02506 {
02507   ToolTipVisitor v;
02508   if ( v.act( 0, incidence, QDate(), richText, spec ) ) {
02509     return v.result();
02510   } else {
02511     return QString();
02512   }
02513 }
02514 
02515 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
02516                                         IncidenceBase *incidence,
02517                                         const QDate &date,
02518                                         bool richText, KDateTime::Spec spec )
02519 {
02520   ToolTipVisitor v;
02521   if ( v.act( calendar, incidence, date, richText, spec ) ) {
02522     return v.result();
02523   } else {
02524     return QString();
02525   }
02526 }
02527 
02528 /*******************************************************************
02529  *  Helper functions for the Incidence tooltips
02530  *******************************************************************/
02531 
02532 //@cond PRIVATE
02533 static QString mailBodyIncidence( Incidence *incidence )
02534 {
02535   QString body;
02536   if ( !incidence->summary().isEmpty() ) {
02537     body += i18n( "Summary: %1\n", incidence->richSummary() );
02538   }
02539   if ( !incidence->organizer().isEmpty() ) {
02540     body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
02541   }
02542   if ( !incidence->location().isEmpty() ) {
02543     body += i18n( "Location: %1\n", incidence->richLocation() );
02544   }
02545   return body;
02546 }
02547 //@endcond
02548 
02549 //@cond PRIVATE
02550 class KCal::IncidenceFormatter::MailBodyVisitor
02551   : public IncidenceBase::Visitor
02552 {
02553   public:
02554     MailBodyVisitor()
02555       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
02556 
02557     bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
02558     {
02559       mSpec = spec;
02560       mResult = "";
02561       return incidence ? incidence->accept( *this ) : false;
02562     }
02563     QString result() const
02564     {
02565       return mResult;
02566     }
02567 
02568   protected:
02569     bool visit( Event *event );
02570     bool visit( Todo *todo );
02571     bool visit( Journal *journal );
02572     bool visit( FreeBusy * )
02573     {
02574       mResult = i18n( "This is a Free Busy Object" );
02575       return !mResult.isEmpty();
02576     }
02577   protected:
02578     KDateTime::Spec mSpec;
02579     QString mResult;
02580 };
02581 
02582 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02583 {
02584   QString recurrence[]= {
02585     i18nc( "no recurrence", "None" ),
02586     i18nc( "event recurs by minutes", "Minutely" ),
02587     i18nc( "event recurs by hours", "Hourly" ),
02588     i18nc( "event recurs by days", "Daily" ),
02589     i18nc( "event recurs by weeks", "Weekly" ),
02590     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
02591     i18nc( "event recurs same day each month", "Monthly Same Day" ),
02592     i18nc( "event recurs same month each year", "Yearly Same Month" ),
02593     i18nc( "event recurs same day each year", "Yearly Same Day" ),
02594     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
02595   };
02596 
02597   mResult = mailBodyIncidence( event );
02598   mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
02599   if ( !event->allDay() ) {
02600     mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
02601   }
02602   if ( event->dtStart() != event->dtEnd() ) {
02603     mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
02604   }
02605   if ( !event->allDay() ) {
02606     mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
02607   }
02608   if ( event->recurs() ) {
02609     Recurrence *recur = event->recurrence();
02610     // TODO: Merge these two to one of the form "Recurs every 3 days"
02611     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
02612     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
02613 
02614     if ( recur->duration() > 0 ) {
02615       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
02616       mResult += '\n';
02617     } else {
02618       if ( recur->duration() != -1 ) {
02619 // TODO_Recurrence: What to do with all-day
02620         QString endstr;
02621         if ( event->allDay() ) {
02622           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02623         } else {
02624           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
02625         }
02626         mResult += i18n( "Repeat until: %1\n", endstr );
02627       } else {
02628         mResult += i18n( "Repeats forever\n" );
02629       }
02630     }
02631   }
02632 
02633   QString details = event->richDescription();
02634   if ( !details.isEmpty() ) {
02635     mResult += i18n( "Details:\n%1\n", details );
02636   }
02637   return !mResult.isEmpty();
02638 }
02639 
02640 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02641 {
02642   mResult = mailBodyIncidence( todo );
02643 
02644   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02645     mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
02646     if ( !todo->allDay() ) {
02647       mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
02648     }
02649   }
02650   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02651     mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
02652     if ( !todo->allDay() ) {
02653       mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
02654     }
02655   }
02656   QString details = todo->richDescription();
02657   if ( !details.isEmpty() ) {
02658     mResult += i18n( "Details:\n%1\n", details );
02659   }
02660   return !mResult.isEmpty();
02661 }
02662 
02663 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02664 {
02665   mResult = mailBodyIncidence( journal );
02666   mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
02667   if ( !journal->allDay() ) {
02668     mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
02669   }
02670   if ( !journal->description().isEmpty() ) {
02671     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
02672   }
02673   return !mResult.isEmpty();
02674 }
02675 //@endcond
02676 
02677 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02678 {
02679   return mailBodyStr( incidence, KDateTime::Spec() );
02680 }
02681 
02682 QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence,
02683                                          KDateTime::Spec spec )
02684 {
02685   if ( !incidence ) {
02686     return QString();
02687   }
02688 
02689   MailBodyVisitor v;
02690   if ( v.act( incidence, spec ) ) {
02691     return v.result();
02692   }
02693   return QString();
02694 }
02695 
02696 //@cond PRIVATE
02697 static QString recurEnd( Incidence *incidence )
02698 {
02699   QString endstr;
02700   if ( incidence->allDay() ) {
02701     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02702   } else {
02703     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02704   }
02705   return endstr;
02706 }
02707 //@endcond
02708 
02709 /************************************
02710  *  More static formatting functions
02711  ************************************/
02712 
02713 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
02714 {
02715   if ( !incidence->recurs() ) {
02716     return i18n( "No recurrence" );
02717   }
02718   QStringList dayList;
02719   dayList.append( i18n( "31st Last" ) );
02720   dayList.append( i18n( "30th Last" ) );
02721   dayList.append( i18n( "29th Last" ) );
02722   dayList.append( i18n( "28th Last" ) );
02723   dayList.append( i18n( "27th Last" ) );
02724   dayList.append( i18n( "26th Last" ) );
02725   dayList.append( i18n( "25th Last" ) );
02726   dayList.append( i18n( "24th Last" ) );
02727   dayList.append( i18n( "23rd Last" ) );
02728   dayList.append( i18n( "22nd Last" ) );
02729   dayList.append( i18n( "21st Last" ) );
02730   dayList.append( i18n( "20th Last" ) );
02731   dayList.append( i18n( "19th Last" ) );
02732   dayList.append( i18n( "18th Last" ) );
02733   dayList.append( i18n( "17th Last" ) );
02734   dayList.append( i18n( "16th Last" ) );
02735   dayList.append( i18n( "15th Last" ) );
02736   dayList.append( i18n( "14th Last" ) );
02737   dayList.append( i18n( "13th Last" ) );
02738   dayList.append( i18n( "12th Last" ) );
02739   dayList.append( i18n( "11th Last" ) );
02740   dayList.append( i18n( "10th Last" ) );
02741   dayList.append( i18n( "9th Last" ) );
02742   dayList.append( i18n( "8th Last" ) );
02743   dayList.append( i18n( "7th Last" ) );
02744   dayList.append( i18n( "6th Last" ) );
02745   dayList.append( i18n( "5th Last" ) );
02746   dayList.append( i18n( "4th Last" ) );
02747   dayList.append( i18n( "3rd Last" ) );
02748   dayList.append( i18n( "2nd Last" ) );
02749   dayList.append( i18nc( "last day of the month", "Last" ) );
02750   dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
02751   dayList.append( i18n( "1st" ) );
02752   dayList.append( i18n( "2nd" ) );
02753   dayList.append( i18n( "3rd" ) );
02754   dayList.append( i18n( "4th" ) );
02755   dayList.append( i18n( "5th" ) );
02756   dayList.append( i18n( "6th" ) );
02757   dayList.append( i18n( "7th" ) );
02758   dayList.append( i18n( "8th" ) );
02759   dayList.append( i18n( "9th" ) );
02760   dayList.append( i18n( "10th" ) );
02761   dayList.append( i18n( "11th" ) );
02762   dayList.append( i18n( "12th" ) );
02763   dayList.append( i18n( "13th" ) );
02764   dayList.append( i18n( "14th" ) );
02765   dayList.append( i18n( "15th" ) );
02766   dayList.append( i18n( "16th" ) );
02767   dayList.append( i18n( "17th" ) );
02768   dayList.append( i18n( "18th" ) );
02769   dayList.append( i18n( "19th" ) );
02770   dayList.append( i18n( "20th" ) );
02771   dayList.append( i18n( "21st" ) );
02772   dayList.append( i18n( "22nd" ) );
02773   dayList.append( i18n( "23rd" ) );
02774   dayList.append( i18n( "24th" ) );
02775   dayList.append( i18n( "25th" ) );
02776   dayList.append( i18n( "26th" ) );
02777   dayList.append( i18n( "27th" ) );
02778   dayList.append( i18n( "28th" ) );
02779   dayList.append( i18n( "29th" ) );
02780   dayList.append( i18n( "30th" ) );
02781   dayList.append( i18n( "31st" ) );
02782   int weekStart = KGlobal::locale()->weekStartDay();
02783   QString dayNames;
02784   QString txt;
02785   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
02786   Recurrence *recur = incidence->recurrence();
02787   switch ( recur->recurrenceType() ) {
02788   case Recurrence::rNone:
02789     return i18n( "No recurrence" );
02790   case Recurrence::rMinutely:
02791     if ( recur->duration() != -1 ) {
02792       txt = i18np( "Recurs every minute until %2",
02793                    "Recurs every %1 minutes until %2",
02794                    recur->frequency(), recurEnd( incidence ) );
02795       if ( recur->duration() >  0 ) {
02796         txt += i18nc( "number of occurrences",
02797                       " (<numid>%1</numid> occurrences)",
02798                       recur->duration() );
02799       }
02800       return txt;
02801     }
02802     return i18np( "Recurs every minute",
02803                   "Recurs every %1 minutes", recur->frequency() );
02804   case Recurrence::rHourly:
02805     if ( recur->duration() != -1 ) {
02806       txt = i18np( "Recurs hourly until %2",
02807                    "Recurs every %1 hours until %2",
02808                    recur->frequency(), recurEnd( incidence ) );
02809       if ( recur->duration() >  0 ) {
02810         txt += i18nc( "number of occurrences",
02811                       " (<numid>%1</numid> occurrences)",
02812                       recur->duration() );
02813       }
02814       return txt;
02815     }
02816     return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
02817   case Recurrence::rDaily:
02818     if ( recur->duration() != -1 ) {
02819       txt = i18np( "Recurs daily until %2",
02820                    "Recurs every %1 days until %2",
02821                    recur->frequency(), recurEnd( incidence ) );
02822       if ( recur->duration() >  0 ) {
02823         txt += i18nc( "number of occurrences",
02824                       " (<numid>%1</numid> occurrences)",
02825                       recur->duration() );
02826       }
02827       return txt;
02828     }
02829     return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
02830   case Recurrence::rWeekly:
02831   {
02832     bool addSpace = false;
02833     for ( int i = 0; i < 7; ++i ) {
02834       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
02835         if ( addSpace ) {
02836           dayNames.append( i18nc( "separator for list of days", ", " ) );
02837         }
02838         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
02839                                               KCalendarSystem::ShortDayName ) );
02840         addSpace = true;
02841       }
02842     }
02843     if ( dayNames.isEmpty() ) {
02844       dayNames = i18nc( "Recurs weekly on no days", "no days" );
02845     }
02846     if ( recur->duration() != -1 ) {
02847       txt = i18ncp( "Recurs weekly on [list of days] until end-date",
02848                     "Recurs weekly on %2 until %3",
02849                     "Recurs every <numid>%1</numid> weeks on %2 until %3",
02850                     recur->frequency(), dayNames, recurEnd( incidence ) );
02851       if ( recur->duration() >  0 ) {
02852         txt += i18nc( "number of occurrences",
02853                       " (<numid>%1</numid> occurrences)",
02854                       recur->duration() );
02855       }
02856       return txt;
02857     }
02858     return i18ncp( "Recurs weekly on [list of days]",
02859                    "Recurs weekly on %2",
02860                    "Recurs every <numid>%1</numid> weeks on %2",
02861                    recur->frequency(), dayNames );
02862   }
02863   case Recurrence::rMonthlyPos:
02864   {
02865     KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
02866     if ( recur->duration() != -1 ) {
02867       txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
02868                     " weekdayname until end-date",
02869                     "Recurs every month on the %2 %3 until %4",
02870                     "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
02871                     recur->frequency(),
02872                     dayList[rule.pos() + 31],
02873                     calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02874                     recurEnd( incidence ) );
02875       if ( recur->duration() >  0 ) {
02876         txt += i18nc( "number of occurrences",
02877                       " (<numid>%1</numid> occurrences)",
02878                       recur->duration() );
02879       }
02880       return txt;
02881     }
02882     return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
02883                    "Recurs every month on the %2 %3",
02884                    "Recurs every %1 months on the %2 %3",
02885                    recur->frequency(),
02886                    dayList[rule.pos() + 31],
02887                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
02888   }
02889   case Recurrence::rMonthlyDay:
02890   {
02891     int days = recur->monthDays()[0];
02892     if ( recur->duration() != -1 ) {
02893         txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
02894                       "Recurs monthly on the %2 day until %3",
02895                       "Recurs every %1 months on the %2 day until %3",
02896                       recur->frequency(),
02897                       dayList[days + 31],
02898                       recurEnd( incidence ) );
02899         if ( recur->duration() >  0 ) {
02900           txt += i18nc( "number of occurrences",
02901                         " (<numid>%1</numid> occurrences)",
02902                         recur->duration() );
02903         }
02904         return txt;
02905     }
02906     return i18ncp( "Recurs monthly on the [1st|2nd|...] day",
02907                    "Recurs monthly on the %2 day",
02908                    "Recurs every <numid>%1</numid> month on the %2 day",
02909                    recur->frequency(),
02910                    dayList[days + 31] );
02911   }
02912   case Recurrence::rYearlyMonth:
02913   {
02914     if ( recur->duration() != -1 ) {
02915       if ( !recur->yearDates().isEmpty() ) {
02916         txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
02917                       " until end-date",
02918                       "Recurs yearly on %2 %3 until %4",
02919                       "Recurs every %1 years on %2 %3 until %4",
02920                       recur->frequency(),
02921                       calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02922                       dayList[ recur->yearDates()[0] + 31 ],
02923                       recurEnd( incidence ) );
02924         if ( recur->duration() >  0 ) {
02925           txt += i18nc( "number of occurrences",
02926                         " (<numid>%1</numid> occurrences)",
02927                         recur->duration() );
02928         }
02929         return txt;
02930       }
02931     }
02932     if ( !recur->yearDates().isEmpty() ) {
02933       return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
02934                      "Recurs yearly on %2 %3",
02935                      "Recurs every %1 years on %2 %3",
02936                      recur->frequency(),
02937                      calSys->monthName( recur->yearMonths()[0],
02938                                         recur->startDate().year() ),
02939                      dayList[ recur->yearDates()[0] + 31 ] );
02940     } else {
02941       if (!recur->yearMonths().isEmpty() ) {
02942         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02943                       "Recurs yearly on %1 %2",
02944                       calSys->monthName( recur->yearMonths()[0],
02945                                          recur->startDate().year() ),
02946                       dayList[ recur->startDate().day() + 31 ] );
02947       } else {
02948         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02949                       "Recurs yearly on %1 %2",
02950                       calSys->monthName( recur->startDate().month(),
02951                                          recur->startDate().year() ),
02952                       dayList[ recur->startDate().day() + 31 ] );
02953       }
02954     }
02955   }
02956   case Recurrence::rYearlyDay:
02957     if ( recur->duration() != -1 ) {
02958       txt = i18ncp( "Recurs every N years on day N until end-date",
02959                     "Recurs every year on day <numid>%2</numid> until %3",
02960                     "Recurs every <numid>%1</numid> years"
02961                     " on day <numid>%2</numid> until %3",
02962                     recur->frequency(),
02963                     recur->yearDays()[0],
02964                     recurEnd( incidence ) );
02965       if ( recur->duration() >  0 ) {
02966         txt += i18nc( "number of occurrences",
02967                       " (<numid>%1</numid> occurrences)",
02968                       recur->duration() );
02969       }
02970       return txt;
02971     }
02972     return i18ncp( "Recurs every N YEAR[S] on day N",
02973                    "Recurs every year on day <numid>%2</numid>",
02974                    "Recurs every <numid>%1</numid> years"
02975                    " on day <numid>%2</numid>",
02976                    recur->frequency(), recur->yearDays()[0] );
02977   case Recurrence::rYearlyPos:
02978   {
02979     KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
02980     if ( recur->duration() != -1 ) {
02981       txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02982                     "of monthname until end-date",
02983                     "Every year on the %2 %3 of %4 until %5",
02984                     "Every <numid>%1</numid> years on the %2 %3 of %4"
02985                     " until %5",
02986                     recur->frequency(),
02987                     dayList[rule.pos() + 31],
02988                     calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02989                     calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02990                     recurEnd( incidence ) );
02991       if ( recur->duration() >  0 ) {
02992         txt += i18nc( "number of occurrences",
02993                       " (<numid>%1</numid> occurrences)",
02994                       recur->duration() );
02995       }
02996       return txt;
02997     }
02998     return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02999                    "of monthname",
03000                    "Every year on the %2 %3 of %4",
03001                    "Every <numid>%1</numid> years on the %2 %3 of %4",
03002                    recur->frequency(),
03003                    dayList[rule.pos() + 31],
03004                    calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
03005                    calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
03006   }
03007   default:
03008     return i18n( "Incidence recurs" );
03009   }
03010 }
03011 
03012 QString IncidenceFormatter::timeToString( const KDateTime &date,
03013                                           bool shortfmt,
03014                                           const KDateTime::Spec &spec )
03015 {
03016   if ( spec.isValid() ) {
03017 
03018     QString timeZone;
03019     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03020       timeZone = ' ' + spec.timeZone().name();
03021     }
03022 
03023     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
03024   } else {
03025     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03026   }
03027 }
03028 
03029 QString IncidenceFormatter::dateToString( const KDateTime &date,
03030                                           bool shortfmt,
03031                                           const KDateTime::Spec &spec )
03032 {
03033   if ( spec.isValid() ) {
03034 
03035     QString timeZone;
03036     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03037       timeZone = ' ' + spec.timeZone().name();
03038     }
03039 
03040     return
03041       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
03042                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
03043       timeZone;
03044   } else {
03045     return
03046       KGlobal::locale()->formatDate( date.date(),
03047                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
03048   }
03049 }
03050 
03051 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
03052                                               bool allDay,
03053                                               bool shortfmt,
03054                                               const KDateTime::Spec &spec )
03055 {
03056   if ( allDay ) {
03057     return dateToString( date, shortfmt, spec );
03058   }
03059 
03060   if ( spec.isValid() ) {
03061     QString timeZone;
03062     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03063       timeZone = ' ' + spec.timeZone().name();
03064     }
03065 
03066     return KGlobal::locale()->formatDateTime(
03067       date.toTimeSpec( spec ).dateTime(),
03068       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
03069   } else {
03070     return  KGlobal::locale()->formatDateTime(
03071       date.dateTime(),
03072       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
03073   }
03074 }
03075 
03076 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03077 {
03078   if ( !calendar || !incidence ) {
03079     return QString();
03080   }
03081 
03082   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03083   if ( !calendarResource ) {
03084     return QString();
03085   }
03086 
03087   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
03088   if ( resourceCalendar ) {
03089     if ( !resourceCalendar->subresources().isEmpty() ) {
03090       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
03091       if ( subRes.isEmpty() ) {
03092         return resourceCalendar->resourceName();
03093       } else {
03094         return resourceCalendar->labelForSubresource( subRes );
03095       }
03096     }
03097     return resourceCalendar->resourceName();
03098   }
03099 
03100   return QString();
03101 }
03102 

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