• 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       if ( inc ) {
02178         html += tdOpen;
02179         if ( inc->type() == "Todo" ) {
02180           html += helper->makeLink( "cancel",
02181                                     i18n( "Remove invitation from my to-do list" ) );
02182         } else {
02183           html += helper->makeLink( "cancel",
02184                                     i18n( "Remove invitation from my calendar" ) );
02185         }
02186         html += tdClose;
02187       }
02188       break;
02189 
02190     case iTIPReply:
02191     {
02192       // Record invitation response
02193       Attendee *a = 0;
02194       Attendee *ea = 0;
02195       if ( inc ) {
02196         a = inc->attendees().first();
02197         if ( a && helper->calendar() ) {
02198           ea = findAttendee( existingIncidence, a->email() );
02199         }
02200       }
02201       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02202         html += tdOpen;
02203         html += htmlAddTag( "i", i18n( "The response has already been recorded" ) );
02204         html += tdClose;
02205       } else {
02206         if ( inc ) {
02207           if ( inc->type() == "Todo" ) {
02208             html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
02209           } else {
02210             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02211           }
02212         }
02213       }
02214       break;
02215     }
02216 
02217     case iTIPCounter:
02218       // Counter proposal
02219       html += tdOpen;
02220       html += helper->makeLink( "accept_counter", i18n( "Accept" ) );
02221       html += tdClose;
02222 
02223       html += tdOpen;
02224       html += helper->makeLink( "decline_counter", i18n( "Decline" ) );
02225       html += tdClose;
02226 
02227       html += tdOpen;
02228       html += helper->makeLink( "check_calendar", i18n( "Check my calendar" ) );
02229       html += tdClose;
02230       break;
02231 
02232     case iTIPDeclineCounter:
02233     case iTIPNoMethod:
02234       break;
02235   }
02236 
02237   // close the groupware table
02238   html += "</tr></table>";
02239 
02240   // Add the attendee list if I am the organizer
02241   if ( myInc && helper->calendar() ) {
02242     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02243   }
02244 
02245   // close the top-level
02246   html += "</div>";
02247 
02248   // Add the attachment list
02249   html += invitationAttachments( helper, inc );
02250 
02251   return html;
02252 }
02253 //@endcond
02254 
02255 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02256                                                   Calendar *mCalendar,
02257                                                   InvitationFormatterHelper *helper )
02258 {
02259   return formatICalInvitationHelper( invitation, mCalendar, helper, false,
02260                                      KSystemTimeZones::local() );
02261 }
02262 
02263 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02264                                                         Calendar *mCalendar,
02265                                                         InvitationFormatterHelper *helper )
02266 {
02267   return formatICalInvitationHelper( invitation, mCalendar, helper, true,
02268                                      KSystemTimeZones::local() );
02269 }
02270 
02271 /*******************************************************************
02272  *  Helper functions for the Incidence tooltips
02273  *******************************************************************/
02274 
02275 //@cond PRIVATE
02276 class KCal::IncidenceFormatter::ToolTipVisitor
02277   : public IncidenceBase::Visitor
02278 {
02279   public:
02280     ToolTipVisitor()
02281       : mCalendar( 0 ), mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
02282 
02283     bool act( Calendar *calendar, IncidenceBase *incidence,
02284               const QDate &date=QDate(), bool richText=true,
02285               KDateTime::Spec spec=KDateTime::Spec() )
02286     {
02287       mCalendar = calendar;
02288       mDate = date;
02289       mRichText = richText;
02290       mSpec = spec;
02291       mResult = "";
02292       return incidence ? incidence->accept( *this ) : false;
02293     }
02294     QString result() const { return mResult; }
02295 
02296   protected:
02297     bool visit( Event *event );
02298     bool visit( Todo *todo );
02299     bool visit( Journal *journal );
02300     bool visit( FreeBusy *fb );
02301 
02302     QString dateRangeText( Event *event, const QDate &date );
02303     QString dateRangeText( Todo *todo, const QDate &date );
02304     QString dateRangeText( Journal *journal );
02305     QString dateRangeText( FreeBusy *fb );
02306 
02307     QString generateToolTip( Incidence *incidence, QString dtRangeText );
02308 
02309   protected:
02310     Calendar *mCalendar;
02311     QDate mDate;
02312     bool mRichText;
02313     KDateTime::Spec mSpec;
02314     QString mResult;
02315 };
02316 
02317 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
02318 {
02319   //FIXME: support mRichText==false
02320   QString ret;
02321   QString tmp;
02322 
02323   KDateTime startDt = event->dtStart();
02324   KDateTime endDt = event->dtEnd();
02325   if ( event->recurs() ) {
02326     if ( date.isValid() ) {
02327       KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
02328       int diffDays = startDt.daysTo( kdt );
02329       kdt = kdt.addSecs( -1 );
02330       startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
02331       if ( event->hasEndDate() ) {
02332         endDt = endDt.addDays( diffDays );
02333         if ( startDt > endDt ) {
02334           startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
02335           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
02336         }
02337       }
02338     }
02339   }
02340 
02341   if ( event->isMultiDay() ) {
02342     tmp = dateToString( startDt, true, mSpec );
02343     ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
02344 
02345     tmp = dateToString( endDt, true, mSpec );
02346     ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
02347 
02348   } else {
02349 
02350     ret += "<br>" +
02351            i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
02352     if ( !event->allDay() ) {
02353       const QString dtStartTime = timeToString( startDt, true, mSpec );
02354       const QString dtEndTime = timeToString( endDt, true, mSpec );
02355       if ( dtStartTime == dtEndTime ) {
02356         // to prevent 'Time: 17:00 - 17:00'
02357         tmp = "<br>" +
02358               i18nc( "time for event", "<i>Time:</i> %1",
02359                      dtStartTime );
02360       } else {
02361         tmp = "<br>" +
02362               i18nc( "time range for event",
02363                      "<i>Time:</i> %1 - %2",
02364                      dtStartTime, dtEndTime );
02365       }
02366       ret += tmp;
02367     }
02368   }
02369   return ret.replace( ' ', "&nbsp;" );
02370 }
02371 
02372 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
02373 {
02374   //FIXME: support mRichText==false
02375   QString ret;
02376   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02377     KDateTime startDt = todo->dtStart();
02378     if ( todo->recurs() ) {
02379       if ( date.isValid() ) {
02380         startDt.setDate( date );
02381       }
02382     }
02383     ret += "<br>" +
02384            i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
02385   }
02386 
02387   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02388     KDateTime dueDt = todo->dtDue();
02389     if ( todo->recurs() ) {
02390       if ( date.isValid() ) {
02391         dueDt.addDays( todo->dtDue().date().daysTo( date ) );
02392       }
02393     }
02394     ret += "<br>" +
02395            i18n( "<i>Due:</i> %1",
02396                  dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
02397   }
02398   if ( todo->isCompleted() ) {
02399     ret += "<br>" +
02400            i18n( "<i>Completed:</i> %1", todo->completedStr() );
02401   } else {
02402     ret += "<br>" +
02403            i18nc( "percent complete", "%1% completed", todo->percentComplete() );
02404   }
02405 
02406   return ret.replace( ' ', "&nbsp;" );
02407 }
02408 
02409 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
02410 {
02411   //FIXME: support mRichText==false
02412   QString ret;
02413   if ( journal->dtStart().isValid() ) {
02414     ret += "<br>" +
02415            i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
02416   }
02417   return ret.replace( ' ', "&nbsp;" );
02418 }
02419 
02420 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
02421 {
02422   //FIXME: support mRichText==false
02423   QString ret;
02424   ret = "<br>" +
02425         i18n( "<i>Period start:</i> %1",
02426               KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
02427   ret += "<br>" +
02428          i18n( "<i>Period start:</i> %1",
02429                KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
02430   return ret.replace( ' ', "&nbsp;" );
02431 }
02432 
02433 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
02434 {
02435   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
02436   return !mResult.isEmpty();
02437 }
02438 
02439 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
02440 {
02441   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
02442   return !mResult.isEmpty();
02443 }
02444 
02445 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
02446 {
02447   mResult = generateToolTip( journal, dateRangeText( journal ) );
02448   return !mResult.isEmpty();
02449 }
02450 
02451 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
02452 {
02453   //FIXME: support mRichText==false
02454   mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
02455   mResult += dateRangeText( fb );
02456   mResult += "</qt>";
02457   return !mResult.isEmpty();
02458 }
02459 
02460 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
02461                                                              QString dtRangeText )
02462 {
02463   //FIXME: support mRichText==false
02464   if ( !incidence ) {
02465     return QString();
02466   }
02467 
02468   QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
02469   if ( mCalendar ) {
02470     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
02471     if ( !calStr.isEmpty() ) {
02472       tmp += "<br>" + i18n( "<i>Calendar:</i> %1", calStr );
02473     }
02474   }
02475 
02476   tmp += dtRangeText;
02477 
02478   if ( !incidence->location().isEmpty() ) {
02479     tmp += "<br>" +
02480            i18n( "<i>Location:</i> %1", incidence->richLocation() );
02481   }
02482 
02483   if ( !incidence->description().isEmpty() ) {
02484     QString desc( incidence->description() );
02485     if ( !incidence->descriptionIsRich() ) {
02486       if ( desc.length() > 120 ) {
02487         desc = desc.left( 120 ) + "...";
02488       }
02489       desc = Qt::escape( desc ).replace( '\n', "<br>" );
02490     } else {
02491       // TODO: truncate the description when it's rich text
02492     }
02493     tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
02494   }
02495   tmp += "</qt>";
02496   return tmp;
02497 }
02498 //@endcond
02499 
02500 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence,
02501                                            bool richText )
02502 {
02503   return toolTipStr( 0, incidence, QDate(), richText, KDateTime::Spec() );
02504 }
02505 
02506 QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence,
02507                                         bool richText, KDateTime::Spec spec )
02508 {
02509   ToolTipVisitor v;
02510   if ( v.act( 0, incidence, QDate(), richText, spec ) ) {
02511     return v.result();
02512   } else {
02513     return QString();
02514   }
02515 }
02516 
02517 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
02518                                         IncidenceBase *incidence,
02519                                         const QDate &date,
02520                                         bool richText, KDateTime::Spec spec )
02521 {
02522   ToolTipVisitor v;
02523   if ( v.act( calendar, incidence, date, richText, spec ) ) {
02524     return v.result();
02525   } else {
02526     return QString();
02527   }
02528 }
02529 
02530 /*******************************************************************
02531  *  Helper functions for the Incidence tooltips
02532  *******************************************************************/
02533 
02534 //@cond PRIVATE
02535 static QString mailBodyIncidence( Incidence *incidence )
02536 {
02537   QString body;
02538   if ( !incidence->summary().isEmpty() ) {
02539     body += i18n( "Summary: %1\n", incidence->richSummary() );
02540   }
02541   if ( !incidence->organizer().isEmpty() ) {
02542     body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
02543   }
02544   if ( !incidence->location().isEmpty() ) {
02545     body += i18n( "Location: %1\n", incidence->richLocation() );
02546   }
02547   return body;
02548 }
02549 //@endcond
02550 
02551 //@cond PRIVATE
02552 class KCal::IncidenceFormatter::MailBodyVisitor
02553   : public IncidenceBase::Visitor
02554 {
02555   public:
02556     MailBodyVisitor()
02557       : mSpec( KDateTime::Spec() ), mResult( "" ) {}
02558 
02559     bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
02560     {
02561       mSpec = spec;
02562       mResult = "";
02563       return incidence ? incidence->accept( *this ) : false;
02564     }
02565     QString result() const
02566     {
02567       return mResult;
02568     }
02569 
02570   protected:
02571     bool visit( Event *event );
02572     bool visit( Todo *todo );
02573     bool visit( Journal *journal );
02574     bool visit( FreeBusy * )
02575     {
02576       mResult = i18n( "This is a Free Busy Object" );
02577       return !mResult.isEmpty();
02578     }
02579   protected:
02580     KDateTime::Spec mSpec;
02581     QString mResult;
02582 };
02583 
02584 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02585 {
02586   QString recurrence[]= {
02587     i18nc( "no recurrence", "None" ),
02588     i18nc( "event recurs by minutes", "Minutely" ),
02589     i18nc( "event recurs by hours", "Hourly" ),
02590     i18nc( "event recurs by days", "Daily" ),
02591     i18nc( "event recurs by weeks", "Weekly" ),
02592     i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
02593     i18nc( "event recurs same day each month", "Monthly Same Day" ),
02594     i18nc( "event recurs same month each year", "Yearly Same Month" ),
02595     i18nc( "event recurs same day each year", "Yearly Same Day" ),
02596     i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
02597   };
02598 
02599   mResult = mailBodyIncidence( event );
02600   mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
02601   if ( !event->allDay() ) {
02602     mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
02603   }
02604   if ( event->dtStart() != event->dtEnd() ) {
02605     mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
02606   }
02607   if ( !event->allDay() ) {
02608     mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
02609   }
02610   if ( event->recurs() ) {
02611     Recurrence *recur = event->recurrence();
02612     // TODO: Merge these two to one of the form "Recurs every 3 days"
02613     mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
02614     mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
02615 
02616     if ( recur->duration() > 0 ) {
02617       mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
02618       mResult += '\n';
02619     } else {
02620       if ( recur->duration() != -1 ) {
02621 // TODO_Recurrence: What to do with all-day
02622         QString endstr;
02623         if ( event->allDay() ) {
02624           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02625         } else {
02626           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
02627         }
02628         mResult += i18n( "Repeat until: %1\n", endstr );
02629       } else {
02630         mResult += i18n( "Repeats forever\n" );
02631       }
02632     }
02633   }
02634 
02635   QString details = event->richDescription();
02636   if ( !details.isEmpty() ) {
02637     mResult += i18n( "Details:\n%1\n", details );
02638   }
02639   return !mResult.isEmpty();
02640 }
02641 
02642 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02643 {
02644   mResult = mailBodyIncidence( todo );
02645 
02646   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02647     mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
02648     if ( !todo->allDay() ) {
02649       mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
02650     }
02651   }
02652   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02653     mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
02654     if ( !todo->allDay() ) {
02655       mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
02656     }
02657   }
02658   QString details = todo->richDescription();
02659   if ( !details.isEmpty() ) {
02660     mResult += i18n( "Details:\n%1\n", details );
02661   }
02662   return !mResult.isEmpty();
02663 }
02664 
02665 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02666 {
02667   mResult = mailBodyIncidence( journal );
02668   mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
02669   if ( !journal->allDay() ) {
02670     mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
02671   }
02672   if ( !journal->description().isEmpty() ) {
02673     mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
02674   }
02675   return !mResult.isEmpty();
02676 }
02677 //@endcond
02678 
02679 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02680 {
02681   return mailBodyStr( incidence, KDateTime::Spec() );
02682 }
02683 
02684 QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence,
02685                                          KDateTime::Spec spec )
02686 {
02687   if ( !incidence ) {
02688     return QString();
02689   }
02690 
02691   MailBodyVisitor v;
02692   if ( v.act( incidence, spec ) ) {
02693     return v.result();
02694   }
02695   return QString();
02696 }
02697 
02698 //@cond PRIVATE
02699 static QString recurEnd( Incidence *incidence )
02700 {
02701   QString endstr;
02702   if ( incidence->allDay() ) {
02703     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02704   } else {
02705     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02706   }
02707   return endstr;
02708 }
02709 //@endcond
02710 
02711 /************************************
02712  *  More static formatting functions
02713  ************************************/
02714 
02715 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
02716 {
02717   if ( !incidence->recurs() ) {
02718     return i18n( "No recurrence" );
02719   }
02720   QStringList dayList;
02721   dayList.append( i18n( "31st Last" ) );
02722   dayList.append( i18n( "30th Last" ) );
02723   dayList.append( i18n( "29th Last" ) );
02724   dayList.append( i18n( "28th Last" ) );
02725   dayList.append( i18n( "27th Last" ) );
02726   dayList.append( i18n( "26th Last" ) );
02727   dayList.append( i18n( "25th Last" ) );
02728   dayList.append( i18n( "24th Last" ) );
02729   dayList.append( i18n( "23rd Last" ) );
02730   dayList.append( i18n( "22nd Last" ) );
02731   dayList.append( i18n( "21st Last" ) );
02732   dayList.append( i18n( "20th Last" ) );
02733   dayList.append( i18n( "19th Last" ) );
02734   dayList.append( i18n( "18th Last" ) );
02735   dayList.append( i18n( "17th Last" ) );
02736   dayList.append( i18n( "16th Last" ) );
02737   dayList.append( i18n( "15th Last" ) );
02738   dayList.append( i18n( "14th Last" ) );
02739   dayList.append( i18n( "13th Last" ) );
02740   dayList.append( i18n( "12th Last" ) );
02741   dayList.append( i18n( "11th Last" ) );
02742   dayList.append( i18n( "10th Last" ) );
02743   dayList.append( i18n( "9th Last" ) );
02744   dayList.append( i18n( "8th Last" ) );
02745   dayList.append( i18n( "7th Last" ) );
02746   dayList.append( i18n( "6th Last" ) );
02747   dayList.append( i18n( "5th Last" ) );
02748   dayList.append( i18n( "4th Last" ) );
02749   dayList.append( i18n( "3rd Last" ) );
02750   dayList.append( i18n( "2nd Last" ) );
02751   dayList.append( i18nc( "last day of the month", "Last" ) );
02752   dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
02753   dayList.append( i18n( "1st" ) );
02754   dayList.append( i18n( "2nd" ) );
02755   dayList.append( i18n( "3rd" ) );
02756   dayList.append( i18n( "4th" ) );
02757   dayList.append( i18n( "5th" ) );
02758   dayList.append( i18n( "6th" ) );
02759   dayList.append( i18n( "7th" ) );
02760   dayList.append( i18n( "8th" ) );
02761   dayList.append( i18n( "9th" ) );
02762   dayList.append( i18n( "10th" ) );
02763   dayList.append( i18n( "11th" ) );
02764   dayList.append( i18n( "12th" ) );
02765   dayList.append( i18n( "13th" ) );
02766   dayList.append( i18n( "14th" ) );
02767   dayList.append( i18n( "15th" ) );
02768   dayList.append( i18n( "16th" ) );
02769   dayList.append( i18n( "17th" ) );
02770   dayList.append( i18n( "18th" ) );
02771   dayList.append( i18n( "19th" ) );
02772   dayList.append( i18n( "20th" ) );
02773   dayList.append( i18n( "21st" ) );
02774   dayList.append( i18n( "22nd" ) );
02775   dayList.append( i18n( "23rd" ) );
02776   dayList.append( i18n( "24th" ) );
02777   dayList.append( i18n( "25th" ) );
02778   dayList.append( i18n( "26th" ) );
02779   dayList.append( i18n( "27th" ) );
02780   dayList.append( i18n( "28th" ) );
02781   dayList.append( i18n( "29th" ) );
02782   dayList.append( i18n( "30th" ) );
02783   dayList.append( i18n( "31st" ) );
02784   int weekStart = KGlobal::locale()->weekStartDay();
02785   QString dayNames;
02786   QString txt;
02787   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
02788   Recurrence *recur = incidence->recurrence();
02789   switch ( recur->recurrenceType() ) {
02790   case Recurrence::rNone:
02791     return i18n( "No recurrence" );
02792   case Recurrence::rMinutely:
02793     if ( recur->duration() != -1 ) {
02794       txt = i18np( "Recurs every minute until %2",
02795                    "Recurs every %1 minutes until %2",
02796                    recur->frequency(), recurEnd( incidence ) );
02797       if ( recur->duration() >  0 ) {
02798         txt += i18nc( "number of occurrences",
02799                       " (<numid>%1</numid> occurrences)",
02800                       recur->duration() );
02801       }
02802       return txt;
02803     }
02804     return i18np( "Recurs every minute",
02805                   "Recurs every %1 minutes", recur->frequency() );
02806   case Recurrence::rHourly:
02807     if ( recur->duration() != -1 ) {
02808       txt = i18np( "Recurs hourly until %2",
02809                    "Recurs every %1 hours until %2",
02810                    recur->frequency(), recurEnd( incidence ) );
02811       if ( recur->duration() >  0 ) {
02812         txt += i18nc( "number of occurrences",
02813                       " (<numid>%1</numid> occurrences)",
02814                       recur->duration() );
02815       }
02816       return txt;
02817     }
02818     return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
02819   case Recurrence::rDaily:
02820     if ( recur->duration() != -1 ) {
02821       txt = i18np( "Recurs daily until %2",
02822                    "Recurs every %1 days until %2",
02823                    recur->frequency(), recurEnd( incidence ) );
02824       if ( recur->duration() >  0 ) {
02825         txt += i18nc( "number of occurrences",
02826                       " (<numid>%1</numid> occurrences)",
02827                       recur->duration() );
02828       }
02829       return txt;
02830     }
02831     return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
02832   case Recurrence::rWeekly:
02833   {
02834     bool addSpace = false;
02835     for ( int i = 0; i < 7; ++i ) {
02836       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
02837         if ( addSpace ) {
02838           dayNames.append( i18nc( "separator for list of days", ", " ) );
02839         }
02840         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
02841                                               KCalendarSystem::ShortDayName ) );
02842         addSpace = true;
02843       }
02844     }
02845     if ( dayNames.isEmpty() ) {
02846       dayNames = i18nc( "Recurs weekly on no days", "no days" );
02847     }
02848     if ( recur->duration() != -1 ) {
02849       txt = i18ncp( "Recurs weekly on [list of days] until end-date",
02850                     "Recurs weekly on %2 until %3",
02851                     "Recurs every <numid>%1</numid> weeks on %2 until %3",
02852                     recur->frequency(), dayNames, recurEnd( incidence ) );
02853       if ( recur->duration() >  0 ) {
02854         txt += i18nc( "number of occurrences",
02855                       " (<numid>%1</numid> occurrences)",
02856                       recur->duration() );
02857       }
02858       return txt;
02859     }
02860     return i18ncp( "Recurs weekly on [list of days]",
02861                    "Recurs weekly on %2",
02862                    "Recurs every <numid>%1</numid> weeks on %2",
02863                    recur->frequency(), dayNames );
02864   }
02865   case Recurrence::rMonthlyPos:
02866   {
02867     if ( !recur->monthPositions().isEmpty() ) {
02868       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
02869       if ( recur->duration() != -1 ) {
02870         txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
02871                       " weekdayname until end-date",
02872                       "Recurs every month on the %2 %3 until %4",
02873                       "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
02874                       recur->frequency(),
02875                       dayList[rule.pos() + 31],
02876                       calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
02877                       recurEnd( incidence ) );
02878         if ( recur->duration() >  0 ) {
02879           txt += i18nc( "number of occurrences",
02880                         " (<numid>%1</numid> occurrences)",
02881                         recur->duration() );
02882         }
02883         return txt;
02884       }
02885       return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
02886                      "Recurs every month on the %2 %3",
02887                      "Recurs every %1 months on the %2 %3",
02888                      recur->frequency(),
02889                      dayList[rule.pos() + 31],
02890                      calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
02891     }
02892     break;
02893   }
02894   case Recurrence::rMonthlyDay:
02895   {
02896     if ( !recur->monthDays().isEmpty() ) {
02897       int days = recur->monthDays()[0];
02898       if ( recur->duration() != -1 ) {
02899         txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
02900                       "Recurs monthly on the %2 day until %3",
02901                       "Recurs every %1 months on the %2 day until %3",
02902                       recur->frequency(),
02903                       dayList[days + 31],
02904                       recurEnd( incidence ) );
02905         if ( recur->duration() >  0 ) {
02906           txt += i18nc( "number of occurrences",
02907                         " (<numid>%1</numid> occurrences)",
02908                         recur->duration() );
02909         }
02910         return txt;
02911       }
02912       return i18ncp( "Recurs monthly on the [1st|2nd|...] day",
02913                      "Recurs monthly on the %2 day",
02914                      "Recurs every <numid>%1</numid> month on the %2 day",
02915                      recur->frequency(),
02916                      dayList[days + 31] );
02917     }
02918     break;
02919   }
02920   case Recurrence::rYearlyMonth:
02921   {
02922     if ( recur->duration() != -1 ) {
02923       if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
02924         txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
02925                       " until end-date",
02926                       "Recurs yearly on %2 %3 until %4",
02927                       "Recurs every %1 years on %2 %3 until %4",
02928                       recur->frequency(),
02929                       calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
02930                       dayList[ recur->yearDates()[0] + 31 ],
02931                       recurEnd( incidence ) );
02932         if ( recur->duration() >  0 ) {
02933           txt += i18nc( "number of occurrences",
02934                         " (<numid>%1</numid> occurrences)",
02935                         recur->duration() );
02936         }
02937         return txt;
02938       }
02939     }
02940     if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
02941       return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
02942                      "Recurs yearly on %2 %3",
02943                      "Recurs every %1 years on %2 %3",
02944                      recur->frequency(),
02945                      calSys->monthName( recur->yearMonths()[0],
02946                                         recur->startDate().year() ),
02947                      dayList[ recur->yearDates()[0] + 31 ] );
02948     } else {
02949       if (!recur->yearMonths().isEmpty() ) {
02950         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02951                       "Recurs yearly on %1 %2",
02952                       calSys->monthName( recur->yearMonths()[0],
02953                                          recur->startDate().year() ),
02954                       dayList[ recur->startDate().day() + 31 ] );
02955       } else {
02956         return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
02957                       "Recurs yearly on %1 %2",
02958                       calSys->monthName( recur->startDate().month(),
02959                                          recur->startDate().year() ),
02960                       dayList[ recur->startDate().day() + 31 ] );
02961       }
02962     }
02963     break;
02964   }
02965   case Recurrence::rYearlyDay:
02966     if ( !recur->yearDays().isEmpty() ) {
02967       if ( recur->duration() != -1 ) {
02968         txt = i18ncp( "Recurs every N years on day N until end-date",
02969                       "Recurs every year on day <numid>%2</numid> until %3",
02970                       "Recurs every <numid>%1</numid> years"
02971                       " on day <numid>%2</numid> until %3",
02972                       recur->frequency(),
02973                       recur->yearDays()[0],
02974                       recurEnd( incidence ) );
02975         if ( recur->duration() >  0 ) {
02976           txt += i18nc( "number of occurrences",
02977                         " (<numid>%1</numid> occurrences)",
02978                         recur->duration() );
02979         }
02980         return txt;
02981       }
02982       return i18ncp( "Recurs every N YEAR[S] on day N",
02983                      "Recurs every year on day <numid>%2</numid>",
02984                      "Recurs every <numid>%1</numid> years"
02985                      " on day <numid>%2</numid>",
02986                      recur->frequency(), recur->yearDays()[0] );
02987     }
02988     break;
02989   case Recurrence::rYearlyPos:
02990   {
02991     if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
02992       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
02993       if ( recur->duration() != -1 ) {
02994         txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
02995                       "of monthname until end-date",
02996                       "Every year on the %2 %3 of %4 until %5",
02997                       "Every <numid>%1</numid> years on the %2 %3 of %4"
02998                       " until %5",
02999                       recur->frequency(),
03000                       dayList[rule.pos() + 31],
03001                       calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
03002                       calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
03003                       recurEnd( incidence ) );
03004         if ( recur->duration() >  0 ) {
03005           txt += i18nc( "number of occurrences",
03006                         " (<numid>%1</numid> occurrences)",
03007                         recur->duration() );
03008         }
03009         return txt;
03010       }
03011       return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
03012                      "of monthname",
03013                      "Every year on the %2 %3 of %4",
03014                      "Every <numid>%1</numid> years on the %2 %3 of %4",
03015                      recur->frequency(),
03016                      dayList[rule.pos() + 31],
03017                      calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
03018                      calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
03019     }
03020   }
03021   break;
03022   }
03023   return i18n( "Incidence recurs" );
03024 }
03025 
03026 QString IncidenceFormatter::timeToString( const KDateTime &date,
03027                                           bool shortfmt,
03028                                           const KDateTime::Spec &spec )
03029 {
03030   if ( spec.isValid() ) {
03031 
03032     QString timeZone;
03033     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03034       timeZone = ' ' + spec.timeZone().name();
03035     }
03036 
03037     return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
03038   } else {
03039     return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03040   }
03041 }
03042 
03043 QString IncidenceFormatter::dateToString( const KDateTime &date,
03044                                           bool shortfmt,
03045                                           const KDateTime::Spec &spec )
03046 {
03047   if ( spec.isValid() ) {
03048 
03049     QString timeZone;
03050     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03051       timeZone = ' ' + spec.timeZone().name();
03052     }
03053 
03054     return
03055       KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
03056                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
03057       timeZone;
03058   } else {
03059     return
03060       KGlobal::locale()->formatDate( date.date(),
03061                                      ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
03062   }
03063 }
03064 
03065 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
03066                                               bool allDay,
03067                                               bool shortfmt,
03068                                               const KDateTime::Spec &spec )
03069 {
03070   if ( allDay ) {
03071     return dateToString( date, shortfmt, spec );
03072   }
03073 
03074   if ( spec.isValid() ) {
03075     QString timeZone;
03076     if ( spec.timeZone() != KSystemTimeZones::local() ) {
03077       timeZone = ' ' + spec.timeZone().name();
03078     }
03079 
03080     return KGlobal::locale()->formatDateTime(
03081       date.toTimeSpec( spec ).dateTime(),
03082       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
03083   } else {
03084     return  KGlobal::locale()->formatDateTime(
03085       date.dateTime(),
03086       ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
03087   }
03088 }
03089 
03090 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03091 {
03092   if ( !calendar || !incidence ) {
03093     return QString();
03094   }
03095 
03096   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03097   if ( !calendarResource ) {
03098     return QString();
03099   }
03100 
03101   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
03102   if ( resourceCalendar ) {
03103     if ( !resourceCalendar->subresources().isEmpty() ) {
03104       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
03105       if ( subRes.isEmpty() ) {
03106         return resourceCalendar->resourceName();
03107       } else {
03108         return resourceCalendar->labelForSubresource( subRes );
03109       }
03110     }
03111     return resourceCalendar->resourceName();
03112   }
03113 
03114   return QString();
03115 }
03116 

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.2-20100208
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